; GRDb
;
; Copyright(c) LADsoft
;
; David Lindauer, camille@bluegrass.net
;
;
; DOS.ASM
;
; Function: DOS interfac
;
	;MASM MODE
	.MODEL SMALL
	.386


include eprints.inc 
include eenv.inc
include emtrap.inc 
include ememory.inc
include eoptions.inc
include eloader.inc
include eints.inc
include ebreaks.inc
include eswift.inc

	PUBLIC SetUserPSP, SetDebugPSP, userpsp, UnloadProgram
	PUBLIC int20handle,int21handle, KillFiles
	PUBLIC int1bh,CtrlBrkPressedInDOS

	extrn	trapcount : dword
	.data
CtrlBrkPressedInDOS	db	0	;flag this event
userpsp			dw	0	;their PSO

	.CODE
;
; PSP switching
;
SetDebugPSP	PROC
	mov	ah,51h		;undocumented call to get PSP
	int	21h
	mov	[userpsp],bx	;save user's psp segment
	mov	bx,[psp]	;and set up ours
	mov	ah,50h		;using another undocumented call
	int	21h
	ret
SetDebugPSP	ENDP


SetUserPSP	PROC
	mov	bx,[userpsp]	;get target pgm PSP
	mov	ah,50h		;and set as current
	int	21h
	ret
SetUserPSP	ENDP
;
; ctrl-break handling
;
int1bh	PROC
	pushf
	push	es
	push	bp
	call	untrace
int1bcont:
	mov	bp,dgroup		;point ES at our data
	mov	es,bp
	mov	es:[CtrlBrkPressedInDOS],1	; assume in dos
	push	es			;save our data seg 
	les	bp,es:[indos]		;inDOS flag at ES:BP
	test	byte ptr es:[bp],0FFh	;see if any bits set
	pop	es			;restore our data seg
	jnz	int1bhid		; in dos is easy, break when it exits
					; via int 21h handler
	mov	es:[CtrlBrkPressedInDOS],0	; else start a single step handler
	push	0			;set ES to 0
	pop	es

;point int1 at our stepper routine

	mov	word ptr es:[4],offset cs:stepper ; don't worry about killing
	mov	word ptr es:[6],cs	; primary tracer, it will get fixed
	mov	bp,sp			;point BP at stack

;Um. [bp+6] points to the return address, so [bp+A] points to the flags
;pushed by the int.  Flag bit 8 is TF, and we set it to enable single-step
;debugging

	or	word ptr [bp+6+4],0100h	;set trap flag upon IRET
int1bhid:
	pop	bp
	pop	es
	popf
	iret
;
; ctrl-break single-stepper.  When ctrl-break comes in the IRET frame
; on the stack will point into the BIOS area.  The following routine
; single-steps the processor until we are out of the BIOS and back
; in the area bounded by GRDB's PSP and the EOM
;

stepper:
	pushf					
	push	es
	push	bp
	mov	bp,sp
	les	bp,[bp + 6]			;point ES:BP at return adr
	cmp	byte ptr es:[bp],0cfh		;see if next inst is IRET
	jne	int1bpopf			;jmp if it isn't
	mov	bp,sp				;point back at stack


;Let me think. If the next instruction is IRET, we must be nested inside an
;interrupt handler. So we skip past what we pushed, plus the 6 bytes pushed
;by the INT to get here, to the frame of the interrupt handler we are inside.
;We set HIS trap flag.
; Else, if the next instruction is POPF, we OR on a bit at [bp+C]. Now lets
;see. BP+6 points to the IP of this int, so BP+C points to the contents of
;the stack just past our int frame. Since the instruction is POPF, what must
;be on the stack is the flags (whatever it was, it will surely become the
;flags after the POPF). So we set the trap flag in those flags.
; Since POPF and IRET are the only instructions that might wipe our trace
;flag, these are the only cases we need to handle. All other instructions
;will cause this routine to be invoked immediately after execution. So this
;process continues until we're back from both DOS and BIOS and in the
;domain of ourselves or the program we are debugging.

	or	word ptr [bp+4+6+6],100h	; set trace flag in frame
	jmp	int1bhid
int1bpopf:
	cmp	byte ptr es:[bp],9dh		;if next instruction is POPF
	jne	stepperfin
	mov	bp,sp
	or	word ptr [bp+6+6],100h		;do the same thing to us
	jmp	int1bhid
stepperfin:
	mov	bp,dgroup			; else check if in
	mov	es,bp				; bounds yet
	mov	bp,sp	 			;ES is our DS, BP is stack

;[BP+8] is the segment on the stack frame for the INT that invoked this
;routine.  See if it's our segment.

	mov	bp,[bp+2+6]			;get segment from stack
	cmp	bp,es:[psp]			;is it our PSP seg?
	jb	int1bhid			;nope, its bigger
	mov	es,es:[psp]			;else get our PSP into ES
	mov	bp,sp				;back to stack
	mov	bp,[bp+2+6]			;get that segment again
	cmp	bp,es:[2]			;??? in our PSP
	jae	int1bhid			;still not us?
	mov	bp,sp				;BP back to stack
	and	word ptr [bp+4+6],NOT 100h	;finally in bounds, off tracing
	mov	bp,dgroup
	mov	es,bp				;ES back to our data
	mov	es:[CtrlBrkPressedInDOS],1	;set flag
	les	bp,es:[indos]			; just make sure about DOS
	test	byte ptr es:[bp],0FFh		;see if in DOS now
	jnz	int1bhid			; run to completion if so
	push	ax				; else clear pending ints
	mov	al,20h
	out	20h,al				;ACK PIC
	pop	ax
	pop	bp
	pop	es
	popf
	jmp	entry1				; return to debugger

int1bh	ENDP
;
; check if we have a proper exit PSP, if we don't assume a spawn
; in progress
;
checkpsp	proc
	push	ds
	push	ax
	push	bx
	mov	ax,dgroup
	mov	ds,ax			;set DS to our data segment
	mov	ah,51h			;get PSP command
	pushf
	call	cs:[int21adr]		;target pgm's int 21 handler
	cmp	bx,[userbasepsp]	;see if their PSP
	pop	bx
	pop	ax
	pop	ds
	ret
checkpsp	endp
;
; int 20h exit - not understood yet. Need to determine what int20adr and
; int21adr really point to - our code, or the user's code.
;
int20handle	PROC
	call	checkpsp		;see if user's PSP is current
	jz	x20			;ZF if so
    	jmp	cs:[int20adr]		;else let user's int20 handle it?
x20:
	mov	ax,dgroup		;set DS and ES to or data
	mov	ds,ax
	mov	es,ax
	mov	bp,sp			;reach onto stack
	mov	ax,[bp+2]		;to get segment of int 20
	cmp	ax,[userbasepsp]	;is
	je	close20
	PRINT_MESSAGE	<13,10,"Warning : Int 20h with CS <> PSP (program will crash)">
close20:
	JMP	int21exit0
int20handle	ENDP
;
; int 21h handler.  Hooks 4ch,00h, 2521,3521,
; and exits to debugger if ctrl-break pressed
;
int21handle	PROC
	cmp	ah,4ch			;exit to DOS command
	je	int21exit4ch		;go if so
	cmp	ah,0			;old exit command
	je	int21exit0		;handle that
	cmp	ax,2521h		; hook setting 21h
	jne	chk35			;nope, not doing that
	mov	word ptr cs:[int21adr],dx	;snag hook address
	mov	word ptr cs:[int21adr+2],ds	;from hooking app
	jmp	didvect			;and skip around

chk35:
	cmp	ax,3521h		; hooking reading int 21h
	jne	normchain		;nope, normal?
	mov	bx,word ptr cs:[int21adr]	;return OUR int21 address
	mov	es,word ptr cs:[int21adr+2]	;in this case

;We get here if the program being debugged was either setting or getting
;the DOS dispatch vector INT 21h.

didvect:
	push	bx			; we have to get the flags back now
	push	bp
	mov	bp,sp			;point to stack
	mov	bx,[bp+4+4]		;flags from int 21
	xchg	bx,[bp+2]		;xchg with our BX
	pop	bp			;flags now on top of stack
	jmp	int21join		;this will do popf, retf 2
int21handle	ENDP

normchain:
	push	bx			; we have to get the ie flag back
	push	bp
	mov	bp,sp			; all this is a grandiose simulated
	mov	bx,[bp +4+4]		; interrupt
	xchg	bx,[bp+2]
	pop	bp
	call	cs:[int21adr]		; call DOS
	pushf				;save DOS return flags
int21join:
	push	bp
	push	es
	mov	bp,dgroup		; check for ctrl-break
	mov	es,bp
	test	es:[CtrlBrkPressedInDOS],1
	pop	es
	jnz	i21brk			; yep- go to DOS
	pop	bp
	popf				; NO, RETURN
	retf	2

;Oh brother! Well, we pushed the DOS return flags, then BP. AX contains
;the DOS return value. We set bp to sp and push AX. NOW the stack
;contains flags, bp, ax. [bp+2] points to the flags. For reasons unknown,
;we DON'T just MOV ax,[bp+2], we XCHG! NOW the flags are in AX and [bp+2]
;contains the DOS return value. So we pop AX from the push, pop
;bp, and ADD SP,2 to THROW AWAY the AX value we carefully put on the stack
;using XCHG! WHY? Why not pop bp, pop ax and just leave out the push ax
;and the add sp,2? Thats why we used XCHG in the first place, right?
;  Anyway, we got here if control-break was pressed in DOS, so we are
;taking the DOS return flags from the REAL DOS and placing them on the
;stack for the debugger's return from this int21 handler.

i21brk:
	mov	bp,sp
	push	ax
	xchg	ax,[bp+2]		; get DOS return flags to stack
	mov	[bp+2+6],ax		; and hit the debugger
	pop	ax
	pop	bp
	add	sp,2
	jmp	entry1
;
; DOS exit request comes here
;
	
int21exit0	PROC
	sub	ax,ax
int21exit0	ENDP



int21exit4ch	PROC
	call	checkpsp
	jnz	normchain
	cld
	call	untrace
	mov	bx,dgroup
	mov	ds,bx
	mov	es,bx
	mov	ss,[stackseg]
	mov	sp,[stackofs]
	sti
	mov	[trapcount],0
	mov	[CtrlBrkPressedInDOS],0
	push	ax
	call	SetDebugPSP		; debug PSP
	mov	si,offset dgroup : veclist	; unhook ints
	call	ReleaseRMInts
	call	disableBreaks	; Disable breakpoints if not
	PRINT_MESSAGE	<13,10,"Normal exit, exit code: ">
	pop	ax
	call	PrintByte	; error code
	call	crlf
	call	UnLoadProgram	; unload the program
	test	[lastexe],0ffh	; see if was EXE
	jz	rex
	mov	ax,[lastcs]	; yep, refresh CS:EIP
	mov	[RegdumpCS],ax
	mov	eax,[lastip]
	mov	[RegdumpEIP],eax
rex:
	jmp	reentry
int21exit4ch	ENDP
;
; unload program
;
UnLoadProgram	PROC
	call	UnLoadInts	; reinit int table
	call	KillFiles	; kill their files
	call	ReleaseMemory	; release memory
	mov	si,offset dgroup : grdbname	; make an empty program
	call	MakeEmptyProg
	ret
UnLoadProgram	ENDP
;
; kill program files by reading the handle table and closing them all
;
KillFiles PROC
	call	SetUserPSP
	mov	fs,bx
	mov	cx,fs:[32h]
kf_lp:
	dec	cx
	mov	bx,cx
	mov	ah,3eh
	int	21h
	cmp	cx,6
	jae	kf_lp
	call	SetDebugPSP
	ret
KillFiles	ENDP
end