; This file contains the source code for PEEK, a CPM+ utility
; that appeared in the Feb, 1984 issue of Microsystems.  Please re-
; fer to the article for more information (the source files contain
; some information on the use of PEEK).      --Ron Fowler
;
;	title	'BDOS-PEEK RSX for CP/M 3.0'
;
;
;			***************************
;			*       PEEKBDOS.ASM      *
;			*       Version  1.0      *
;			*     Copyright (c) 1983  *
;			* 	     by           *
;			*        Ron Fowler       *
;			***************************
;
; This program is an  RSX running under CP/M  3.0 (only).   It's purpose  is
; to trace  file-related BDOS calls,  and display information regarding each
; call on the  AUX device.  It must  be combined with  the companion module,
; "PEEK.ASM"  using GENCOM,  as described in  the CP/M+  programmer's guide.
;
; PEEK's output device  defaults to  the system console,  and can be changed
; by doing  "peek a"  (aux), "peek l" (list), or  "peek c"  (console).  Note
; that  "peek p"  sets the pause flag, causing  the trace to pause after the
; call to bdos.   Note also that a "*" is printed at the pause;  the program
; then  waits for input.   Any character other than "Q" will simply cause  a
; return  to the caller.  A "Q" resets the pause flag, and the  program then
; displays without pausing.
;
; "PEEK Q" removes the RSX from memory.
;
; I find this very useful for debugging programs.  Typically, I keep a term-
; attached  to my AUX device (well, actually, it's a separate computer, run-
; ning a modem program across a serial link, but that's another story...) --
; this  RSX outputs to the  AUX device to  keep the output from  the program
; from getting mixed up with  the output from this RSX.  Hence, I get a full
; "snapshot" of file operations made by the program under test.
;
; Note that  this RSX uses one level of caller's stack.  Also, if you modify
; this code, be VERY careful with the registers;  I save only what is absol-
; utely necessary, in order to conserve memory.
;
;			Ron Fowler
;			Fort Atkinson, WI
;			08/22/83
;
; UPDATE INFORMATION:
;
; -- if you update this program, please pass along a copy to Fort Fone File
;    Folder (FFFF) RCP/M, Fort Atkinson, WI: (414) 563-9932 (no ringback).
;    Please update the version number in both modules (PEEK.ASM also).
;
; updates (in reverse order to minimize reading time):
;
; 1.0	originally written by Ron Fowler   08/22/83
;
;------------------------------------------------------------
;
; CP/M equates
;
conotf	equ	2		;output character to console
auxotf	equ	4		;aux output
lstotf	equ	5		;list output
diriof	equ	6		;direct console i/o
printf	equ	9		;print string function
openf	equ	15		;open file
closf	equ	16		;close file
srchf	equ	17		;search for file 1st occurance
srchn	equ	18		;search for file next occurance
erasf	equ	19		;erase file
readf	equ	20		;read sequential
writf	equ	21		;write sequential
creat	equ	22		;create file
renmf	equ	23		;rename file
sdmaf	equ	26		;set transfer address
rdran	equ	33		;read random
wrran	equ	34		;write random
;
; character equates
;
cr	equ	13		;carriage-return code
lf	equ	10		;linefeed code
;
;
;
; RSX prefix
;
	db	0,0,0,0,0,0	;space for CPM3 to insert serial #
	jmp	begin		;jump to entry point
next:	db	jmp		;jump instruction op-code
	dw	0		;next RSX or loader
	dw	0		;previous RSX or 0
rmvflg:	db	0		;removes RSX when non-0
	db	0		;flag for non-banked only
myname:	db	'PEEKBDOS'	;our name
	db	0		;loader flag
	db	0,0		;reserved
;
; This is the BDOS call intercept.  Here we determine if the
; call is one of those we process.  If not, we simply pass
; control on to the next module.
;
begin:	mov	a,c		;get function code
	cpi	60		;for RSX's only?
	jz	rsxfnc		;go if so
	lxi	h,fnctbl	;nope, test for one of ours
scan:	mov	a,m		;get first/next table opcode
	inx	h
	ora	a		;end-of-table?
	jz	next		;then quit now
	cmp	c		;ours?
	jz	trap		;go trap if so
	mvi	a,6		;nope, calculate next table entry
	call	addha
	jmp	scan		;continue
;
; Here when we've determined that we must display the system call.
; First save caller's stackpointer and load a local stack.
;
trap:	shld	tblptr		;save pointer to function string
	lxi	h,0		;get user's sp
	dad	sp
	shld	spsave
	lxi	sp,stack	;load local stack
;
; begin the trace: do newline, then print the function string
;
	mvi	a,cr		;carriage
	call	type
	mvi	a,lf
	call	type
	lhld	tblptr		;get string pointer
	call	prathl		;print it
	call	ilprnt		;colon, open paren
	db	': '
	db	'('+80h
;
; print fcb address (or dma address)
;
	mov	a,d		;get parameter hi
	call	hexout
	mov	a,e		;then lo
	call	hexout
	call	ilprnt		;closing paren
	db	')',' '+80h
	mov	a,c		;get function code
	cpi	sdmaf		;set-dma?
	jz	exit		;then done
;
; Not setdma function.  Print out the FCB information
;
	push	d		;save fcb pointer
	ldax	d		;get drive code
	inx	d
	call	hexsp		;print hex byte, space
;
; Print the file name
;
	mvi	b,11		;eleven characters
fnprnt:	ldax	d		;get first/next char
	inx	d		;advance fcb pointer
	call	type		;print it
	dcr	b		;all 11
	jnz	fnprnt
	mvi	a,' '		;separate filename
	call	type
;
; print ex,s1,s2,rc
;
	mvi	b,4		;four of these
	lxi	h,idbase	;ascii id's for each field
idloop:	call	prathl		;print id
	ldax	d		;get field
	inx	d		;advance fcb pointer
	call	hexsp		;print in hex, space
	dcr	b		;all 4
	jnz	idloop
;
; print the nr field
;
	call	prathl		;last id string
	lxi	h,16		;offset to nr field
	dad	d
	mov	a,m		;fetch it
	call	hexout		;print it
	pop	d		;restore fcb pointer
;
; here after fcb info printed.  Allow the call to pass upward,
; then print the returned value.
;
exit:	call	next		;give call to bdos
	shld	hlsave		;save returned registers
	push	d
	push	b
	push	psw		;save returned value
	lxi	h,rtnmsg	;print "Ret: "
	call	prathl
	pop	psw		;restore, resave returned a
	push	psw
	call	hexout		;print it in hex
	lda	pause		;do a pause?
	ora	a
	jz	nopaus		;jump if not
	call	ilprnt		;yep, print prompt
	db	' ','*'+80h
	mvi	c,diriof	;get a character
	mvi	e,0FDH		;don't come back 'till we have one
	call	next
	ani	5fh		;convert char to upper case
	cpi	'Q'		;quit-pause?
	jnz	nopaus		;no
	xra	a		;yes
	sta	pause
nopaus:	pop	psw		;restore 'em all
	pop	b
	pop	d
	lhld	spsave		;restore stackpointer
	sphl
	lhld	hlsave
	ret			;back to caller
;
; Handler for function 60: call RSX. Here we insure that the
; call is for us.  The RSX parameter block passed in DE must
; contain two parameters, the first of which is a pointer to
; our name (near absolute insurance that there won't be any
; confusion).  The second parameter is a pointer to a request
; code (one of: "Q", quit, "C", console, "L", list, "A", aux).
;
rsxfnc:	push	d		;save user de
	xchg			;rsx pb pointer to hl
	inx	h		;ignore function code
	mov	a,m		;get # parameters
	cpi	2		;two?
	jnz	pass		;quit if not
	inx	h		;skip # of parameters
	mov	e,m		;fetch parameter #1
	inx	h
	mov	d,m
	inx	h		;hl points to 2nd parm
	shld	spsave		;save that pointer
	lxi	h,myname	;check for our name
	mvi	b,8		;8 characters
ckname:	ldax	d		;compare first/next character
	inx	d
	cmp	m		;compare with our name
	inx	h
	jnz	pass		;if fails, pass to next rsx
	dcr	b		;all 8
	jnz	ckname
;
; rsx request is for us, get rsx pointer, load 2nd parameter
;
	pop	d		;recall passed parameter
;
; call next rsx's -- we may be an accidently loaded rsx (which
; will happen when the tpa module is used only for communication
; with us).  PEEK always returns 0, CP/M returns 0FFH.  So...
; unless we get an ff back, there are more of us in memory, and we
; are only a clone, and must commit suicide (does this sound
; like science fiction to you too??)
;
	call	next		;are we real?
	cpi	0ffh		;a=0ffh?
	jz	wereal		;jump if so
	lxi	h,rmvflg	;sad...
	mvi	m,0ffh
	ret
;
wereal:	lhld	spsave		;get rsx pointer
	mov	a,m		;fetch next parameter
	inx	h
	mov	h,m		;into hl
	mov	l,a		;hl points to request
	inx	h		;point to pause flag
	mov	a,m		;fetch it
	cpi	0ffh		;null? (don't alter pauseflag)
	jz	pntreq		;jump if so
	sta	pause		;no, stuff it
pntreq:	dcx	h		;point to request
	mov	a,m		;fetch request
;
; a now has the function request character passed from the tpa
;
	cpi	'Q'		;quit?
	jz	quit
	cpi	'A'		;output to aux?
	mvi	c,auxotf	;prep function code
	jz	setout
	cpi	'C'		;output to console?
	mvi	c,conotf	;prep code
	jz	setout
	mvi	c,lstotf	;process of elimination (list)
setout:	mov	a,c		;get new output device code
	sta	curdev		;stuff it
	xra	a		;return a=0
	ret
;
; here when call 60 is not for us
;
pass:	pop	d		;clear stack
	jmp	next		;to next rsx
;
; here to quit
;
quit:	mvi	a,0ffh		;flag loader to remove us
	sta	rmvflg
	xra	a		;we still return 0
	ret
;
;	***************
;	* subroutines *
;	***************
;
; print in-line message terminated by char w/bit7=1
;
ilprnt:	xthl			;msg pointer to hl
	call	prathl		;print it
	xthl
	ret
;
; print string pointed to by hl, until char w/bit7=1
;
prathl:	mov	a,m		;fetch first/next char
	inx	h		;advance pointer
	call	type		;print it
	ani	80h		;terminator?
	jz	prathl		;continue if not
	ret
;
; output a in hex, followed by space
;
hexsp:	call	hexout		;print it
	mvi	a,' '
	jmp	type
;
; output a as 2 hex digits
;
hexout:	push	psw		;save it
	rrc			;hi nybble into lo
	rrc
	rrc
	rrc
	call	nybble		;print it
	pop	psw		;now restore, fall into lo
nybble:	ani	0fh		;zap any garbage in hi nybble
	adi	90h		;this little beauty...
	daa			;...is from the old (and maybe
	aci	40h		;...still existing) Intel library
	daa			;fall into type
;
; type char in a on console via current output device.
;
type:	push	psw		;save all
	push	h
	push	d
	push	b
	ani	7fh		;allow no parity bits to pass
	mov	e,a		;align character
	lda	curdev		;get device function code
	mov	c,a
	call	next		;output the character
	pop	b		;clean up
	pop	d
	pop	h
	pop	psw
	ret
;
; add a to hl
;
addha:	add	l
	mov	l,a
	rnc
	inr	h
	ret
;
; Table of locally-processed system function calls. Format  is function
; code  followed by id string. Note that more functions may be added to
; this table, but if they are not fcb-related functions, you'll have to
; modify the TRAP routine to process the new data types.
;
fnctbl:	db	openf,'opnfi','l'+80H
	db	closf,'clsfi','l'+80H
	db	readf,'rd se','q'+80H
	db	writf,'wr se','q'+80H
	db	sdmaf,'setdm','a'+80H
	db	srchf,'srch ','1'+80H
	db	srchn,'srch ','n'+80H
	db	renmf,'renam','e'+80H
	db	erasf,'erase',' '+80H
	db	rdran,'rd ra','n'+80H
	db	wrran,'wr ra','n'+80H
	db	creat,'creat','e'+80H
;
	db	0
;
; table of fcb id strings
;
idbase:	db	'ex','='+80h
	db	's1','='+80h
	db	's2','='+80h
	db	'rc','='+80h
	db	'nr','='+80h
;
rtnmsg:	db	' Ret:',' '+80h
;
; data area
;
curdev:	db	auxotf		;system-call # for output
pause:	db	0		;non-0 = pause after bdos calls
tblptr:	dw	0		;saved pointer into fnctbl
hlsave:	dw	0		;save for hl
spsave:	dw	0		;stackpointer save
	ds	24		;12-level stack
stack:
;
;	
	end
inter into fnctbl
hlsave:	dw	0		;save for hl
spsave:	dw	0		;stackpointer save
	ds	24		;12-le