;       SCCSID = @(#)dosdbprt.asm	6.1 90/11/17
;      SCCSID = @(#)dosdbprt.asm	13.11 90/09/17
PAGE	,132
NAME	DOSDBPRT
TITLE	DOSDBPRT.ASM - MS-DOS debug print formatting

;***	DOSDBPRT.ASM
;
;
;	Contains the debugger printf routines for formatted
;	printing.
;

.xlist
	include dosseg.inc
	include basemaca.inc
	include osmaca.inc
	include protmode.inc
	include debseg.inc
	include debmac.inc
	include debequ.inc
.list

Extern	RegSA, BYTE
extrn	BUGBITS:byte

; far and near interfaces for DOS debug print macros

DATA segment
lastchar	db	?
DATA ends

	FARCODE LOW
CODE	segment

	public far_dprintfRM, far_doutcharRM
far_dprintfRM	dd	far_dprintf
far_doutcharRM	dd	far_doutchar

;**	dbtest - Test Debug Flags
;
;	If one or more of the bits in AL is set in the low byte of BUGBITS,
;	and if one or more of the bits in AH is set in the high byte of
;	BUGBITS, then clear the Zero flag.
;
;	In other words:
;	    If both masks show a "hit" clear 'Z' else set 'Z'
;
;	ENTRY	(AH)	High byte bit mask
;		(AL)	Low byte bit mask
;	EXIT	Zero Flag set if no bits match
;		Zero Flag clear if any bits match
;	USES	FLAGS

GenHybrid dbtest

	public	dbtest
dbtest	proc	near
	test	byte ptr cs:[BUGBITS],al	; are any low bits on?
	jz	dbtx				; jmp if not

;	convert al from bit to bx byte offset,
;	if al = 1/2/4/8/16/32/64/128, then
;	set bx = 1/2/3/4/5/6/7/8, else
;	set bx = 0

	push	bx
	xor	bx,bx
	or	al,al
	jz	dbt10
dbt00:	inc	bx
	shr	al,1
	jnc	dbt00
	or	al,al
	jz	dbt10
	xor	bx,bx
dbt10:	test	byte ptr cs:[BUGBITS+bx],ah	; are any high bits on?
	pop	bx
dbtx:	ret
dbtest	endp


GenHybrid DPRINTF

	public	DPRINTF
DPRINTF proc	near
    assume ds:nothing, es:nothing, ss:nothing
	push	ds
	push	DOSGROUP
	pop	ds
	whatmode
	jc	short dpf20
	call	[far_dprintfRM]
	jmp	short dpf30

dpf20:	call	far ptr far_dprintf
dpf30:	pop	ds
	ret
DPRINTF endp


TABLE	SEGMENT
strfmt	db	"$S",0
TABLE	ENDS

GenHybrid dputs

	public	dputs
dputs	proc	near
    assume ds:nothing, es:nothing, ss:nothing
	push	bp
	push	offset DOSGROUP:strfmt
	mov	bp,sp		; (BP) = pointer to str fmt
	push	ds
	push	si
	call	DPRINTF
	add	sp,6		; remove arguments and format string addr
	pop	bp
	clc
	ret
dputs	endp

;***	dputchar - output a character on the debugger serial port
;***	dputcharnl - same, but skip redundant new-lines
;
;	ENTRY	(AL) = character
;	EXIT	NONE
;	USES	Flags

GenHybrid dputchar
    	public dputchar
dputchar proc near
    assume ds:nothing, es:nothing, ss:nothing
	whatmode
	jc	short dpc20
	call	[far_doutcharRM]
	jmp	short dpc30

dpc20:	call	far ptr far_doutchar
dpc30:	ret
dputchar endp

    	public dputcharnl
dputcharnl proc near
    assume ds:nothing, es:nothing, ss:nothing
	cmp	al,0dh
	jz	short dputchar		; always output CRs
	push	ds
	DosContext ds
	cmp	al,0ah			; check for LFs
	jnz	short dcn20
	xchg	[lastchar],al		; save new char, fetch last char
	cmp	al,0ah			; set Zero Flag if redundant
	mov	al,0ah			; restore char
	jmp	short dcn30

dcn20:	mov	[lastchar],al		; save non-LF char
dcn30:	pop	ds
	jnz	short dputchar		; output char if not redundant LF
	ret
dputcharnl endp


CODE	ends

CodeSegmentStart

Extern	ExitDebug, NEAR

;***	dprintf - Debug Printf
;
;	dprintf is a kernel debug print formatting package.  It is intended
;	to produce conveniently formatted output.
;
;	dprintf is called, indirectly, by a macro:
;
;	DBPRT	n,m,"string",<a1,...,an>
;
;	string = format string
;	a1     = first argument
;	an     = last argument
;
;	The format string is an ASCIZ string which can contain 2 types of
;	specifications:	 data-format specifications and literal characters.
;	Data format specifications always begin with a '$' character; all
;	characters not part of a data format specification are treated as
;	literal characters.
;
;	Literal characters
;	    - any character not part of a format specification.  Special
;	      non-printing characters are:
;		\n	- CRLF
;		\t	- tab
;		\b	- bell
;		\\	- \
;		\$	- $
;
;	Format Specifications
;
;	A format specification takes the form:
;	     $ [@] <char>
;
;	where <char> =
;
;	    x	    - print argument as a hex word
;	    d	    - print argument as decimal word
;	    c	    - print argument as ascii character
;	    b	    - print argument as hex byte
;		For each of the above formats, the supplied argument
;		is a 16-bit word - the value to be printed.  The optional @
;		(described below) allows a segmented address to be supplied,
;		instead.
;
;	    s[nn]   - print argument as asciz string; if optional decimal
;		      argument follows the format character this specifies
;		      a maximum string length.	Non printing characters are
;		      printed in the form \nnn where "nnn" is the octal byte
;		      value.
;		      Note that this format character cannot be directly
;		      followed by a digit unless that digit is to be taken
;		      as the start of a length argument.
;		      The argument is interpreted as a DS relative offset
;		      to the string to be printed.
;
;	   S[nn]    - Same as above except that the argument is a far
;		      pointer to the string to be printed.
;
;	    Bnn	    - print argument as hex bytes.  The required following
;		      decimal argument is the number of bytes to print.
;
;		The '@' character is invalid for the above three formats.
;
;	WARNINGS
;	    As befitting a debug routine, dprintf does not have a whole lot
;	    of "failsafe" code in it.  Supplying screwed up formats can
;	    muck things up.  Specifically:
;		The @ argument must NOT be specified with the 's' or 'B'
;		     format
;		A string/byte-length argument of 0 is taken as 65536
;	    The string "%% BAD FMT %%" appears in the output when
;		1) an illegal format specifier is given, or
;		2) the B format is given a 0 or missing length
;
;	ENTRY	(DS)   = DOSGROUP
;		(BP)   = address of format string (offset from DS)
;		(BP-2) = first argument word
;		(BP-4) = second argument word
;		    . . .
;		(BP-n) = last argument word
;
;	EXIT	NONE
;
;	USES	Flags
;
	ASSUME	DS:NOTHING, ES:NOTHING, SS:NOTHING

DefProc far_dprintf, FAR, LOCAL
	Pcall	EnterDebugRETF
    assumeDATA	<ds, es, ss>
	Pcall	InitComPort

	mov	bp,RegSA.BPsav
	mov	ds,RegSA.DSsav
    ASSUME   DS:NOTHING
	mov	es,ss:RegSA.SSsav
    ASSUME   ES:NOTHING
	mov	si,es:[bp]	; get address of format string
	sub	bp,2

;	Scan format string for next character
;
;	(ds:si) = address of format string
;	(es:bp) = address of next argument

dpt1:	mov	es,ss:RegSA.SSsav
	lodsb			; (al) = format string byte
	and	al,al
	je	dpt4		; all done
	cmp	al,'$'
	je	dpt5		; is data escape
	cmp	al,'\'
	jnz	dpt3		; got the character

;	it's an "\" escape code - crack the argument character

	lodsb
	and	al,al
	je	dpt4		; all done, ignore hanging \
	xchg	ah,al
	mov	al,0Ch
	cmp	ah,'n'
	jne	dpt2		; not \n
	mov	al,0dH
	Pcall	doutchar
	mov	al,0aH
	jmp	short dpt3	; print LF

dpt2:	cmp	ah,'t'
	mov	al,9
	je	dpt3		; is \t
	cmp	ah,'b'
	mov	al,7
	je	dpt3		; is \b
	xchg	ah,al
dpt3:	Pcall	doutchar
	jmp	dpt1

dpt4:	jmp	dptx

;*	Have a '$' character - is data format escape
;
;	Get address of data into es:di
;
;	(es:bp) = address of data value

dpt5:	mov	di,bp		; (es:di) = address of data value
	sub	bp,2		; point to next argument
	lodsb			; (al) = format specifier
	cmp	al,'@'
	jne	dpt6		; not an indirect flag
	les	di,es:[bp]
	sub	bp,2		; have an extra 2 for @
	lodsb
dpt6:	cmp	al,'x'
	jne	dpt7		; not 'x'

;	is 'x' format - print hex word

	mov	dx,es:[di]
	Pcall	out16		; type hex word
	jmp	dpt1

dpt7:	cmp	al,'d'
	jnz	dpt8		; not 'd'

;	is 'd' format - print decimal word

	Pcall	OutRadix, <es:[di], 0, 10, 0, 0> ; type decimal word
	jmp	dpt1

dpt8:	cmp	al,'c'
	jne	dpt9

;	is 'c' format - print character

	mov	al,es:[di]
	Pcall	doutchar
	jmp	dpt1

dpt9:	cmp	al,'b'
	jne	dpt10

;	is 'b' format - print hex byte

	mov	al,es:[di]
	Pcall	hex		; type hex byte
	jmp	dpt1

dpt10:	cmp	al,'S'
	je	dpt11
	cmp	al,'s'
	jne	dpt15
	mov	di,es:[di]	; dereference string pointer
	push	ds
	pop	es
	jmp	short dpt12

;	is 'S' format - print ASCIZ string.  First, check for
;		optional decimal limit

dpt11:	sub	cx,cx		; set 65536 limit
	les	di,es:[bp]	; (ES:DI) = fwa of string
	sub	bp,2		; argument to 'S' was two words
dpt12:	mov	al,[si]
	cmp	al,'0'
	jb	dpt13		; not decimal
	cmp	al,'9'
	ja	dpt13		; not decimal
	Pcall	atod		; (ax) = decimal value, (ds:si) updated
	xchg	cx,ax

;	print asciz string at es:di, max of (cx) characters
;	(cx) = 0  means max of 65536
;
;	Other sections of code in dpf jump here to print strings

dpt13:	mov	al,es:[di]
	inc	di
	and	al,al
	je	dpt14
	Pcall	doutchar
	loop	dpt13		; continue if not at limit
dpt14:	jmp	dpt1

dpt15:	cmp	al,'B'
	je	dpt18		; is 'B' format

;	error in format code - print message

dpt16:	push	cs
	pop	es
	cmp	al,'p'		; unimplemented 'p' format?
	jne	dpt17
	add	bp,2		; no argument to 'p'
dpt17:	mov	di,codeOFFSET dpfa	 ; (es:di) = error message
	sub	cx,cx
	jmp	dpt13

dpfa	db	'%% BAD FMT %%', 7, 0

;	have B format

dpt18:	Pcall	atod		; (ax) = length specifier
	jc	dpt16		; number not there - error
	xchg	cx,ax
	jcxz	dpt16		; number is 0 - error
	les	di,es:[bp]	; (ES:DI) = fwa of string
	sub	bp,2		; argument to 'B' is two words
dpt19:	mov	al,es:[di]
	Pcall	hex		; type hex byte
	mov	al,' '
	Pcall	doutchar	; space em out
	inc	di
	loop	dpt19		; do em all
	jmp	dpt1

;	have the end of the format string - exit

dptx:	DataContext <ds, es>
	jmp	ExitDebug

EndProc	NoGen


;**	atod - Convert ASCII string to decimal number
;
;	atod is called to convert an ascii string of digits to a
;	decimal number.  Digits are converted until we run out of them.
;
;	ENTRY:	(DS:SI) = address of first digit
;
;	EXIT:	Carry clear if OK
;		  (AX) = value
;		  (SI) updated to first non-digit
;		Carry set if error - no digits, or result >65535
;		  (DS:SI) points to error character
;
;	USES:	AX, SI, FLAGS
;
	ASSUME	DS:NOTHING, ES:NOTHING, SS:NOTHING

DefProc atod, NEAR, LOCAL
	push	dx
	push	cx		; save registers
	mov	al,[si]
	sub	al,'0'
	jc	short atd9	; error - no digits
	cmp	al,10
	cmc
	jc	short atd9	; error - no digits
	sub	ax,ax		; clear accumulator
	mov	cx,10		; base 10

;	crack next digit
;
;	(AX) = number accumulated so near
;	(CX) = 10
;	(DS:SI) = next character

atd1:	xchg	dx,ax		; keep accum in dx for a while
	lodsb			; (al) = character
	sub	al,'0'
	jc	short atd7	; not digit - all done
	cmp	al,9
	ja	short atd7	; not digit - all done
	sub	ah,ah		; (ax) = digit value (0 - 9)
	push	ax
	xchg	ax,dx
	mul	cx		; (ax) = 10*accum
	pop	dx		; (dx) = digit to add
	jo	short atd8	; overflow
	add	ax,dx
	jmp	short atd1	; go back for more

;	Done with number, all OK
;
;	(dx) = number
;	(ds:si) = address+1 of first unused character

atd7:	clc

;	Done with number, error
;	Carry set

atd8:	dec	si		; backup over non-decimal (or error) char
atd9:	pop	cx
	xchg	ax,dx		; (ax) = number iff no error
	pop	dx		; restore registers
EndProc


;***	doutchar - output a character on the debugger serial port
;
;	This sets up DS and ES to the debugger's data segment
;	before calling putchar.
;
;	ENTRY	(AL) = character
;	EXIT	NONE
;	USES	NONE

DefProc	doutchar, NEAR, LOCAL
    assume ds:nothing, es:nothing, ss:nothing
	SaveReg	<ds, es>
	DataContext <ds, es>
	Pcall	putchar
	RestReg	<es, ds>
EndProc


;***	far_doutchar - output a character on the debugger serial port
;
;	Enter the debugger before calling putchar.
;
;	ENTRY	(AL) = character
;	EXIT	NONE
;	USES	NONE

DefProc	far_doutchar, FAR, LOCAL
	Pcall	EnterDebugRETF
    assumeDATA	<ds, es, ss>
	mov	ax,[RegSA].AXsav
	Pcall	putchar
	jmp	ExitDebug
EndProc

CodeSegmentEnd

	end
