TITLE PART1 DEBUGGER COMMANDS ; Routines to perform debugger commands except ASSEMble and UASSEMble .xlist .xcref INCLUDE DEBEQU.ASM INCLUDE DOSSYM.ASM .cref .list CODE SEGMENT PUBLIC BYTE 'CODE' CODE ENDS CONST SEGMENT PUBLIC BYTE EXTRN SYNERR:BYTE EXTRN DISPB:WORD,DSIZ:BYTE,DSSAVE:WORD if sysver EXTRN CIN:DWORD,PFLAG:BYTE endif CONST ENDS DATA SEGMENT PUBLIC BYTE EXTRN DEFLEN:WORD,BYTEBUF:BYTE,DEFDUMP:BYTE DATA ENDS DG GROUP CODE,CONST,DATA CODE SEGMENT PUBLIC BYTE 'CODE' ASSUME CS:DG,DS:DG,ES:DG,SS:DG PUBLIC HEXCHK,GETHEX1,PRINT,DSRANGE,ADDRESS,HEXIN,PERROR PUBLIC GETHEX,GET_ADDRESS,GETEOL,GETHX,PERR PUBLIC PERR,MOVE,DUMP,ENTER,FILL,SEARCH,DEFAULT if sysver PUBLIC IN EXTRN DISPREG:NEAR,DEVIOCALL:NEAR endif EXTRN OUT:NEAR,CRLF:NEAR,OUTDI:NEAR,OUTSI:NEAR,SCANP:NEAR EXTRN SCANB:NEAR,BLANK:NEAR,TAB:NEAR,PRINTMES:NEAR,COMMAND:NEAR EXTRN HEX:NEAR,BACKUP:NEAR DEBCOM1: ; RANGE - Looks for parameters defining an address range. ; The first parameter is the starting address. The second parameter ; may specify the ending address, or it may be preceded by ; "L" and specify a length (4 digits max), or it may be ; omitted and a length of 128 bytes is assumed. Returns with ; segment in AX, displacement in DX, and length in CX. DSRANGE: MOV BP,[DSSAVE] ; Set default segment to DS MOV [DEFLEN],128 ; And default length to 128 bytes RANGE: CALL ADDRESS PUSH AX ; Save segment PUSH DX ; Save offset CALL SCANP ; Get to next parameter MOV AL,[SI] CMP AL,"L" ; Length indicator? JE GETLEN MOV DX,[DEFLEN] ; Default length CALL HEXIN ; Second parameter present? JC GetDef ; If not, use default MOV CX,4 CALL GETHEX ; Get ending address (same segment) MOV CX,DX ; Low 16 bits of ending addr. POP DX ; Low 16 bits of starting addr. SUB CX,DX ; Compute range JAE DSRNG2 DSRNG1: JMP PERROR ; Negative range DSRNG2: INC CX ; Include last location JCXZ DSRNG1 ; Wrap around error POP AX ; Restore segment RET GetDef: POP CX ; get original offset PUSH CX ; save it NEG CX ; rest of segment JZ RngRet ; use default CMP CX,DX ; more room in segment? JAE RngRet ; yes, use default JMP RngRet1 ; no, length is in CX GETLEN: INC SI ; Skip over "L" to length MOV CX,4 ; Length may have 4 digits CALL GETHEX ; Get the range RNGRET: MOV CX,DX ; Length RngRet1: POP DX ; Offset MOV AX,CX ADD AX,DX JNC OKRET CMP AX,1 JAE DSRNG1 ; Look for wrap error OKRET: POP AX ; Segment RET DEFAULT: ; DI points to default address and CX has default length CALL SCANP JZ USEDEF ; Use default if no parameters MOV [DEFLEN],CX CALL RANGE JMP GETEOL USEDEF: MOV SI,DI LODSW ; Get default displacement MOV DX,AX LODSW ; Get default segment RET ; Dump an area of memory in both hex and ASCII DUMP: MOV BP,[DSSAVE] MOV CX,DISPB MOV DI,OFFSET DG:DEFDUMP CALL DEFAULT ; Get range if specified MOV DS,AX ; Set segment MOV SI,DX ; SI has displacement in segment IF ZIBO PUSH SI ; save SI away AND SI,0FFF0h ; convert to para number CALL OutSI ; display location POP SI ; get SI back MOV AX,SI ; move offset MOV AH,3 ; spaces per byte AND AL,0Fh ; convert to real offset MUL AH ; compute (AL+1)*3-1 OR AL,AL ; set flag JZ InRow ; if xero go on PUSH CX ; save count MOV CX,AX ; move to convenient spot CALL Tab ; move over POP CX ; get back count JMP InRow ; display line ENDIF ROW: CALL OUTSI ; Print address at start of line InRow: PUSH SI ; Save address for ASCII dump CALL BLANK BYTE0: CALL BLANK ; Space between bytes BYTE1: LODSB ; Get byte to dump CALL HEX ; and display it POP DX ; DX has start addr. for ASCII dump DEC CX ; Drop loop count JZ ToAscii ; If through do ASCII dump MOV AX,SI TEST AL,CS:(BYTE PTR DSIZ) ; On 16-byte boundary? JZ ENDROW PUSH DX ; Didn't need ASCII addr. yet TEST AL,7 ; On 8-byte boundary? JNZ BYTE0 MOV AL,"-" ; Mark every 8 bytes CALL OUT JMP SHORT BYTE1 ENDROW: CALL ASCII ; Show it in ASCII JMP SHORT ROW ; Loop until count is zero ToAscii: MOV AX,SI ; get offset AND AL,0Fh ; real offset JZ ASCII ; no loop if already there SUB AL,10h ; remainder NEG AL MOV CL,3 MUL CL MOV CX,AX ; number of chars to move CALL Tab ASCII: PUSH CX ; Save byte count MOV AX,SI ; Current dump address MOV SI,DX ; ASCII dump address SUB AX,DX ; AX=length of ASCII dump IF NOT ZIBO ; Compute tab length. ASCII dump always appears on right side ; screen regardless of how many bytes were dumped. Figure 3 ; characters for each byte dumped and subtract from 51, which ; allows a minimum of 3 blanks after the last byte dumped. MOV BX,AX SHL AX,1 ; Length times 2 ADD AX,BX ; Length times 3 MOV CX,51 SUB CX,AX ; Amount to tab in CX CALL TAB MOV CX,BX ; ASCII dump length back in CX ELSE MOV CX,SI ; get starting point DEC CX AND CX,0Fh INC CX AND CX,0Fh ADD CX,3 ; we have the correct number to tab PUSH AX ; save count CALL TAB POP CX ; get count back ENDIF ASCDMP: LODSB ; Get ASCII byte to dump AND AL,7FH ; ASCII uses 7 bits CMP AL,7FH ; Don't try to print RUBOUT JZ NOPRT CMP AL," " ; Check for control characters JNC PRIN NOPRT: MOV AL,"." ; If unprintable character PRIN: CALL OUT ; Print ASCII character LOOP ASCDMP ; CX times POP CX ; Restore overall dump length MOV ES:WORD PTR [DEFDUMP],SI MOV ES:WORD PTR [DEFDUMP+2],DS ; Save last address as default CALL CRLF ; Print CR/LF and return RET ; Block move one area of memory to another. Overlapping moves ; are performed correctly, i.e., so that a source byte is not ; overwritten until after it has been moved. MOVE: CALL DSRANGE ; Get range of source area PUSH CX ; Save length PUSH AX ; Save segment PUSH DX ; Save source displacement CALL ADDRESS ; Get destination address (same segment) CALL GETEOL ; Check for errors POP SI MOV DI,DX ; Set dest. displacement POP BX ; Source segment MOV DS,BX MOV ES,AX ; Destination segment POP CX ; Length CMP DI,SI ; Check direction of move SBB AX,BX ; Extend the CMP to 32 bits JB COPYLIST ; Move forward into lower mem. ; Otherwise, move backward. Figure end of source and destination ; areas and flip direction flag. DEC CX ADD SI,CX ; End of source area ADD DI,CX ; End of destination area STD ; Reverse direction INC CX COPYLIST: MOVSB ; Do at least 1 - Range is 1-10000H not 0-FFFFH DEC CX REP MOVSB ; Block move RET1: RET ; Fill an area of memory with a list values. If the list ; is bigger than the area, don't use the whole list. If the ; list is smaller, repeat it as many times as necessary. FILL: CALL DSRANGE ; Get range to fill PUSH CX ; Save length PUSH AX ; Save segment number PUSH DX ; Save displacement CALL LIST ; Get list of values to fill with POP DI ; Displacement in segment POP ES ; Segment POP CX ; Length CMP BX,CX ; BX is length of fill list MOV SI,OFFSET DG:BYTEBUF ; List is in byte buffer JCXZ BIGRNG JAE COPYLIST ; If list is big, copy part of it BIGRNG: SUB CX,BX ; How much bigger is area than list? XCHG CX,BX ; CX=length of list PUSH DI ; Save starting addr. of area REP MOVSB ; Move list into area POP SI ; The list has been copied into the beginning of the ; specified area of memory. SI is the first address ; of that area, DI is the end of the copy of the list ; plus one, which is where the list will begin to repeat. ; All we need to do now is copy [SI] to [DI] until the ; end of the memory area is reached. This will cause the ; list to repeat as many times as necessary. MOV CX,BX ; Length of area minus list PUSH ES ; Different index register POP DS ; requires different segment reg. JMP SHORT COPYLIST ; Do the block move ; Search a specified area of memory for given list of bytes. ; Print address of first byte of each match. SEARCH: CALL DSRANGE ; Get area to be searched PUSH CX ; Save count PUSH AX ; Save segment number PUSH DX ; Save displacement CALL LIST ; Get search list DEC BX ; No. of bytes in list-1 POP DI ; Displacement within segment POP ES ; Segment POP CX ; Length to be searched SUB CX,BX ; minus length of list SCAN: MOV SI,OFFSET DG:BYTEBUF ; List kept in byte buffer LODSB ; Bring first byte into AL DOSCAN: SCASB ; Search for first byte LOOPNE DOSCAN ; Do at least once by using LOOP JNZ RET1 ; Exit if not found PUSH BX ; Length of list minus 1 XCHG BX,CX PUSH DI ; Will resume search here REPE CMPSB ; Compare rest of string MOV CX,BX ; Area length back in CX POP DI ; Next search location POP BX ; Restore list length JNZ TEST ; Continue search if no match DEC DI ; Match address CALL OUTDI ; Print it INC DI ; Restore search address CALL CRLF TEST: JCXZ RET1 JMP SHORT SCAN ; Look for next occurrence ; Get the next parameter, which must be a hex number. ; CX is maximum number of digits the number may have. GETHX: CALL SCANP GETHX1: XOR DX,DX ; Initialize the number CALL HEXIN ; Get a hex digit JC HXERR ; Must be one valid digit MOV DL,AL ; First 4 bits in position GETLP: INC SI ; Next char in buffer DEC CX ; Digit count CALL HEXIN ; Get another hex digit? JC RETHX ; All done if no more digits STC JCXZ HXERR ; Too many digits? SHL DX,1 ; Multiply by 16 SHL DX,1 SHL DX,1 SHL DX,1 OR DL,AL ; and combine new digit JMP SHORT GETLP ; Get more digits GETHEX: CALL GETHX ; Scan to next parameter JMP SHORT GETHX2 GETHEX1: CALL GETHX1 GETHX2: JC PERROR RETHX: CLC HXERR: RET ; Check if next character in the input buffer is a hex digit ; and convert it to binary if it is. Carry set if not. HEXIN: MOV AL,[SI] ; Check if AL has a hex digit and convert it to binary if it ; is. Carry set if not. HEXCHK: SUB AL,"0" ; Kill ASCII numeric bias JC RET2 CMP AL,10 CMC JNC RET2 ; OK if 0-9 AND AL,5FH SUB AL,7 ; Kill A-F bias CMP AL,10 JC RET2 CMP AL,16 CMC RET2: RET ; Process one parameter when a list of bytes is ; required. Carry set if parameter bad. Called by LIST. LISTITEM: CALL SCANP ; Scan to parameter CALL HEXIN ; Is it in hex? JC STRINGCHK ; If not, could be a string MOV CX,2 ; Only 2 hex digits for bytes CALL GETHEX ; Get the byte value MOV [BX],DL ; Add to list INC BX GRET: CLC ; Parameter was OK RET STRINGCHK: MOV AL,[SI] ; Get first character of param CMP AL,"'" ; String? JZ STRING CMP AL,'"' ; Either quote is all right JZ STRING STC ; Not string, not hex - bad RET STRING: MOV AH,AL ; Save for closing quote INC SI STRNGLP: LODSB ; Next char of string CMP AL,13 ; Check for end of line JZ PERR ; Must find a close quote CMP AL,AH ; Check for close quote JNZ STOSTRG ; Add new character to list CMP AH,[SI] ; Two quotes in a row? JNZ GRET ; If not, we're done INC SI ; Yes - skip second one STOSTRG: MOV [BX],AL ; Put new char in list INC BX JMP SHORT STRNGLP ; Get more characters ; Get a byte list for ENTER, FILL or SEARCH. Accepts any number ; of 2-digit hex values or character strings in either single ; (') or double (") quotes. LIST: MOV BX,OFFSET DG:BYTEBUF ; Put byte list in the byte buffer LISTLP: CALL LISTITEM ; Process a parameter JNC LISTLP ; If OK, try for more SUB BX,OFFSET DG:BYTEBUF ; BX now has no. of bytes in list JZ PERROR ; List must not be empty ; Make sure there is nothing more on the line except for ; blanks and carriage return. If there is, it is an ; unrecognized parameter and an error. GETEOL: CALL SCANB ; Skip blanks JNZ PERROR ; Better be a RETURN RET3: RET ; Command error. SI has been incremented beyond the ; command letter so it must decremented for the ; error pointer to work. PERR: DEC SI ; Syntax error. SI points to character in the input buffer ; which caused error. By subtracting from start of buffer, ; we will know how far to tab over to appear directly below ; it on the terminal. Then print "^ Error". PERROR: SUB SI,OFFSET DG:(BYTEBUF-1); How many char processed so far? MOV CX,SI ; Parameter for TAB in CX CALL TAB ; Directly below bad char MOV SI,OFFSET DG:SYNERR ; Error message ; Print error message and abort to command level PRINT: CALL PRINTMES JMP COMMAND ; Gets an address in Segment:Displacement format. Segment may be omitted ; and a default (kept in BP) will be used, or it may be a segment ; register (DS, ES, SS, CS). Returns with segment in AX, OFFSET in DX. ADDRESS: CALL GET_ADDRESS JC PERROR ADRERR: STC RET GET_ADDRESS: CALL SCANP MOV AL,[SI+1] CMP AL,"S" JZ SEGREG MOV CX,4 CALL GETHX JC ADRERR MOV AX,BP ; Get default segment CMP BYTE PTR [SI],":" JNZ GETRET PUSH DX GETDISP: INC SI ; Skip over ":" MOV CX,4 CALL GETHX POP AX JC ADRERR GETRET: CLC RET SEGREG: MOV AL,[SI] MOV DI,OFFSET DG:SEGLET MOV CX,4 REPNE SCASB JNZ ADRERR INC SI INC SI SHL CX,1 MOV BX,CX CMP BYTE PTR [SI],":" JNZ ADRERR PUSH [BX+DSSAVE] JMP SHORT GETDISP SEGLET DB "CSED" ; Short form of ENTER command. A list of values from the ; command line are put into memory without using normal ; ENTER mode. GETLIST: CALL LIST ; Get the bytes to enter POP DI ; Displacement within segment POP ES ; Segment to enter into MOV SI,OFFSET DG:BYTEBUF ; List of bytes is in byte 2uffer MOV CX,BX ; Count of bytes REP MOVSB ; Enter that byte list RET ; Enter values into memory at a specified address. If the ; line contains nothing but the address we go into "enter ; mode", where the address and its current value are printed ; and the user may change it if desired. To change, type in ; new value in hex. Backspace works to correct errors. If ; an illegal hex digit or too many digits are typed, the ; bell is sounded but it is otherwise ignored. To go to the ; next byte (with or without change), hit space bar. To ; back CLDto a previous address, type "-". On ; every 8-byte boundary a new line is started and the address ; is printed. To terminate command, type carriage return. ; Alternatively, the list of bytes to be entered may be ; included on the original command line immediately following ; the address. This is in regular LIST format so any number ; of hex values or strings in quotes may be entered. ENTER: MOV BP,[DSSAVE] ; Set default segment to DS CALL ADDRESS PUSH AX ; Save for later PUSH DX CALL SCANB ; Any more parameters? JNZ GETLIST ; If not end-of-line get list POP DI ; Displacement of ENTER POP ES ; Segment GETROW: CALL OUTDI ; Print address of entry CALL BLANK ; Leave a space CALL BLANK GETBYTE: MOV AL,ES:[DI] ; Get current value CALL HEX ; And display it PUTDOT: MOV AL,"." CALL OUT ; Prompt for new value MOV CX,2 ; Max of 2 digits in new value MOV DX,0 ; Intial new value GETDIG: CALL IN ; Get digit from user MOV AH,AL ; Save CALL HEXCHK ; Hex digit? XCHG AH,AL ; Need original for echo JC NOHEX ; If not, try special command MOV DH,DL ; Rotate new value MOV DL,AH ; And include new digit LOOP GETDIG ; At most 2 digits ; We have two digits, so all we will accept now is a command. DWAIT: CALL IN ; Get command character NOHEX: CMP AL,8 ; Backspace JZ BS CMP AL,7FH ; RUBOUT JZ RUB CMP AL,"-" ; Back CLDto previous address JZ PREV CMP AL,13 ; All done with command? JZ EOL CMP AL," " ; Go to next address JZ NEXT MOV AL,8 CALL OUT ; Back CLDover illegal character CALL BACKUP JCXZ DWAIT JMP SHORT GETDIG RUB: MOV AL,8 CALL OUT BS: CMP CL,2 ; CX=2 means nothing typed yet JZ PUTDOT ; Put back the dot we backed CLDover INC CL ; Accept one more character MOV DL,DH ; Rotate out last digit MOV DH,CH ; Zero this digit CALL BACKUP ; Physical backspace JMP SHORT GETDIG ; Get more digits ; If new value has been entered, convert it to binary and ; put into memory. Always bump pointer to next location STORE: CMP CL,2 ; CX=2 means nothing typed yet JZ NOSTO ; So no new value to store ; Rotate DH left 4 bits to combine with DL and make a byte value PUSH CX MOV CL,4 SHL DH,CL POP CX OR DL,DH ; Hex is now converted to binary MOV ES:[DI],DL ; Store new value NOSTO: INC DI ; Prepare for next location RET NEXT: CALL STORE ; Enter new value INC CX ; Leave a space plus two for INC CX ; each digit not entered CALL TAB MOV AX,DI ; Next memory address AND AL,7 ; Check for 8-byte boundary JNZ GETBYTE ; Take 8 per line NEWROW: CALL CRLF ; Terminate line JMP GETROW ; Print address on new line PREV: CALL STORE ; Enter the new value ; DI has been bumped to next byte. Drop it 2 to go to previous addr DEC DI DEC DI JMP SHORT NEWROW ; Terminate line after backing CLD EOL: CALL STORE ; Enter the new value JMP CRLF ; CR/LF and terminate ; Console input of single character IF SYSVER IN: PUSH DS PUSH SI LDS SI,CS:[CIN] MOV AH,4 CALL DEVIOCALL POP SI POP DS CMP AL,3 JNZ NOTCNTC INT 23H NOTCNTC: CMP AL,'P'-'@' JZ PRINTON CMP AL,'N'-'@' JZ PRINTOFF CALL OUT RET PRINTOFF: PRINTON: NOT [PFLAG] JMP SHORT IN ELSE IN: MOV AH,1 INT 21H RET ENDIF CODE ENDS END DEBCOM1