;FILENAME: UNIT19.TXT ; ; ASSEMBLY LANGUAGE COURSE, UNIT 19 ; By Don Stoner Revision: 2008.07.21 ; ORG 100H ;DOS code goes here ; ; Here we will use the "Single-Step" ; interrupt (#1) and "Break" interrupt ; (#3) to watch what happen when code ; is run. When the TF flag is set (it's ; one of the computer's "FLAGS"), the ; computer will perform an interrupt #1 ; after every instruction. If we modify ; interrupt #1, we can display the ; contents of the registers between ; execution steps. This way we can test ; it to see if our code works the way ; we expect it to. The BREAK interrupt ; (#3) works in a similar fashion, ; except that the computer runs at full ; speed until a single-byte BREAK ; instruction (INT 3 - or 0CCh) is ; encountered. It will then return so ; we can see what it did. ; ; First, we take over the interrupt ; vectors: #1 (Step) and #3 (Break) ; XOR AX,AX ;Set AX=0 MOV DS,AX ;Data seg = page 0 MOV AX,[4] ;Save old vector MOV [CS:OFFSAV],AX ;offset MOV AX,[6] MOV [CS:SEGSAV],AX ;and segment ; MOV AX,VECTOR ;Set vector MOV [4],AX ;Step offset MOV [12],AX ;Break offset MOV AX,CS MOV [6],AX ;Step segment MOV [14],AX ;Break segment ; ; Next, we need to patch some memory ; locations with segment register ; information so we will know how to ; set up our test code (to the same ; place where DOS loaded this code): ; MOV AX,CS ;Set all MOV DS,AX ;segment MOV [RCS],AX ;registers MOV [RDS],AX ;to CS MOV [RES],AX MOV [RSS],AX ; ; Next we set up the segment registers ; Each time we run (or step) test code, ; it might mess these up, so we will ; need loop back to here and keep ; putting them back where they belong: ; RESTART: MOV AX,CS ;We loop back to here MOV DS,AX ;with these messed up MOV ES,AX ;(so we reload them) CLI ;No interrupts MOV SS,AX ;while setting MOV SP,0FFFEH ;stack pointer STI ;(between SEG and OFF) ; ; This is our Main Code Loop: Like the ; Debug Monitor from Unit 13, it waits ; for some keyboard commands and lets ; us mess with the code; but instead of ; just showing the contents of memory, ; and letting us run a program, it ; shows the contents of all of the ; real-mode registers: ; MLOOP: CALL CRLF_OUT CALL REGOUT ;Output registers CALL ASCII_IN ;GET INPUT ;------------------------------ ; "Q" returns to DOS, but first ; restores STEP and BREAK. ; CMP AL,"Q" ;Q = QUIT TO DOS JNZ NTQ MOV AX,0 ;Restore interrupt MOV DS,AX ;vectors MOV AX,[CS:OFFSAV] MOV [4],AX ;Step offset MOV [12],AX ;Break offset MOV AX,[CS:SEGSAV] MOV [6],AX ;Step segment MOV [14],AX ;Break offset INT 20H ;& Exit to DOS NTQ: ;------------------------------ ; "R" runs the code starting ; from [CS:IP]. An INT 3 (0CCh) ; will return to this monitor: ; CMP AL,"R" ;R = RUN (TO BREAK) JZ RUN ;------------------------------ ; "S" executes only the single ; instruction [CS:IP] then ; returns to this monitor. ; CMP AL,"S" ;S = SINGLE STEP JZ STEP NTS: ;------------------------------ ; Any other key resets [CS:IP] ; to the start of user code: ; MOV AX,CS ;Restore segment MOV [RCS],AX MOV AX,USER_CODE ;And offset MOV [RIP],AX JMP MLOOP ;====================================== ; Put any code you want to test here: USER_CODE: INC BX MOV AX,4321h MOV AL,55h CALL ASCII_OUT INT 3 ;====================================== ; SINGLE STEP OR RUN (New interrupt) ; STEP: MOV AX,[RFG] ;Set the TF OR AX,100H ;(single step) MOV [RFG],AX ;flag bit ; RUN: MOV AX,[RSS] ;Set up the CLI ;user's stack MOV SS,AX MOV SP,[RSP] STI PUSH WORD [RFG] ;Setup on the PUSH WORD [RCS] ;user's stack PUSH WORD [RIP] ;for an "IRET" ;(The IRET is a reversed call) MOV AX,[RES] ;"Restore" all MOV ES,AX ;of the user's MOV BP,[RBP] ;registers MOV DI,[RDI] MOV SI,[RSI] MOV DX,[RDX] MOV CX,[RCX] MOV BX,[RBX] PUSH WORD [RAX] ;AX and DS are MOV AX,[RDS] ;tricky because MOV DS,AX ;we lose the DS POP AX ;segment register ; Finally, we "call" the user's code IRET ;with an inerrupt "return" ; - - - - - - - - - - - - - - - - - - - ; And "return" using an interrupt ; 1 (STEP) OR 3 (BREAK) - which puts ; the FLAGS, CS, and IP on the stack ; VECTOR: PUSH DS ;Save these on stack PUSH AX ;(we need AX and DS MOV AX,CS ;to get access to MOV DS,AX ;our local memory) ; save the user's registers: POP WORD [RAX] ;AX from stack MOV [RBX],BX MOV [RCX],CX ;Save some other MOV [RDX],DX ;registers more MOV [RSI],SI ;directly MOV [RDI],DI MOV [RBP],BP POP WORD [RDS] ;DS from stack MOV AX,ES ;ES as directly MOV [RES],AX ;as possible POP WORD [RIP] ;(IP,CD,FLAGS POP WORD [RCS] ;are all on the POP WORD [RFG] ;user's stack MOV AX,SS ;Save user's MOV [RSS],AX ;stack (segment MOV [RSP],SP ;and offset) ; MOV AX,[RFG] ;Clear the TF AND AX,0FEFFH ;(single-step) MOV [RFG],AX ;Flag bit JMP RESTART ;(Program loop) ;====================================== ; MONITOR VARIABLES ; SEGSAV: DW 0 ;Save segment:offset of OFFSAV: DW 0 ;the old interrupt vectors ; ; Save user register contents here: ; RAX: DW 1111h ;AX reg RBX: DW 2222h ;BX reg RCX: DW 3333h ;CX reg RDX: DW 4444h ;DX reg RSI: DW 5555h ;Source Index reg RDI: DW 6666h ;Destination Index reg RBP: DW 7777h ;Base Pointer RSP: DW 0FF80h ;Stack Pointer RCS: DW 1234h ;Segment registers: RDS: DW 1234h ;we set these four RES: DW 1234h ;(Code, Data, Extra & RSS: DW 1234h ;Stack) from our CS RIP: DW USER_CODE ;Instr. Pointer RFG: DW 0 ;status FLAGS ;====================================== ; SUBROUTINES: ; ; OUTPUT 14 REGISTERS AND MEMORY ; REGOUT: CALL INLINE_ASCIIZ_OUT DB "AX: BX: CX: DX: SI: " DB "DI: BP: SP: CS: DS: " DB "ES: SS: IP: FLAG: MEM:" DB 13,10,0 ; MOV CX,14 ;Count = 14 MOV SI,RAX ;Cue to data DATA_OUT_LOOP: LODSW ;Next data word CALL AX_OUT ;Output it LOOP DATA_OUT_LOOP ;CX times ; MOV SI,[RIP] ;Cue to IP MOV AL,[SI] ;Get memory CALL HEX_OUT ;HEX output JMP CRLF_OUT ;(and return) ; ; OUTPUT FOUR NIBBLES FROM AX ; AX_OUT: XCHG AL,AH ;Exchange AL & AH CALL HEX_OUT ;really "AH" out XCHG AH,AL ;Switch them back CALL HEX_OUT ;then AL out JMP SPACE_OUT ; %INCLUDE "UNIT11.TXT"