CHAPTER 5 COMMAND LANGUAGE In addition to immediate-execution assembly language commands, there is a set of commands recognized by the debugger. They are identified by the first keyword on the line being a single letter (i.e., the second character of the line is a non-letter, usually a comma or ENTER). General Operands to Debugger Commands Most of the debugger commands consist of their single-letter identifier, followed by a comma, followed by one or more general operands, separated by commas. General operands can be one of the following: a. a numeric constant, whose format is just as in the assembly language (leading zero means default hex, otherwise default decimal) b. a register name c. a user symbol from the assembly language program being debugged. Format of Debugger Command Examples Many of the examples given below will be given in double quotes. Note that the double quotes are not part of the command. You are encouraged to try out the example on the debugger, by typing the string within the quotes, not including the quotes, and always followed by the ENTER key. Note further that the double-quoted string may be broken across two lines of this manual, but that does not mean you should type an ENTER where the string is broken --debugger commands always consist of a single line, always terminated by ENTER. The Debugger Command Set Following is a description of the debugger commands recognized: B sets and clears the fixed breakpoints of the program. The debugger has four breakpoints. Two are transitory; they are automatically cleared after each return from the program to the debugger. They can be set by the G command. The other two are fixed-- they will remain in effect until you explicitly clear them. The fixed breakpoints are controlled by this B command. You follow the B with zero, one, or two general operands. If there are zero operands (the B is followed immediately by an ENTER), then both fixed breakpoints are cleared. If there are one or two operands, then the fixed breakpoints are set to the operands. 5-2 Note that previously-set breakpoints can be implicitly cleared, by overwriting them with other breakpoints. If your B command has one operand, and there was one breakpoint previously set, the debugger sets the unused breakpoint, so that both remain in effect. If your B command has one operand, and both breakpoints were previously set, the most recently set breakpoint is saved, and the older breakpoint is overwritten. The status screen, displayed by typing Ctrl-S, shows you the B-command breakpoints in effect. Examples: if you type "b,numout", the debugger will set a breakpoint at location NUMOUT, which should be a label in the program being debugged. You may start and stop the program many times, and the breakpoint will stay there. You may even allow the program to stop at NUMOUT repeatedly; the breakpoint is not cleared even if the program stops there. If you subsequently type the command "b,01000", then there will be breakpoints at both NUMOUT and location hex 01000. If you then type "b,01200", the first breakpoint NUMOUT is overwritten; the two breakpoints now in effect are 01000 and 01200. The 01000 breakpoint will be next in line to be overwritten. You may clear both breakpoints by typing "b". There is no way to clear one breakpoint at a time. D sets or clears a data breakpoint. This command is available only to registered D86 users running on a 386-based machine. A data breakpoint causes the program to trap to the debugger whenever a specified memory location is accessed. The trap occurs after the instruction causing the access, so you should press the Up-arrow key to see the instruction. You follow the "D" with a comma, followed by a specification parameter. The parameter consists of up to three characters, at most one each from the following categories: 1. A letter giving the size of the memory element being checked: B for byte, W for word, D for doubleword, or a minus sign if you are clearing the breakpoint. Default is B. 2. The letter R if you wish to trap if the memory location is either written to or read from. If you leave the R out, the trap will occur only if the memory location is written to. 3. A digit, (0,1,2, or 3) giving the number of the 386 breakpoint register you are using to set the trap. Default is 0. 5-3 You terminate the specification parameter with a comma, then provide one or two numbers to specify the memory location you are trapping. If you provide two numbers, the first is the segment register value and the second is the offset. If you provide only one number, it is the offset-- the value of DS is used as the segment register value. As with all value parameters in D86, you can give a register name or a label instead of a number. You can also leave out the address entirely, to preserve the previous address setting of that breakpoint register. Note that the 386 requires Word and Doubleword breakpoints to be aligned in memory. If you provide an odd address for a Word breakpoint, the 386 will ignore the bottom bit of your address. Similarly, the 386 will ignore the bottom two bits of your address for a Doubleword breakpoint. Examples: D,R1,ES,0400 sets a byte data read-or-write breakpoint, using the 386's register number 1, at memory location ES:0400. D,-1 would clear that breakpoint. D,R1 would set it again with its previous value. D,W,MY_VAR sets a Word breakpoint, using the 386's register number 0, at location DS:MY_VAR-- the trap will occur if either byte of the variable MY_VAR is written to (but MY_VAR should be aligned to an even address for this to work). If the D command is enabled, you'll get a one-line display of the data breakpoint registers in the status window (invoked via Ctrl-S). The registers are presented in order: 0,1,2,3. The breakpoint type is given, followed by the 5-digit absolute memory address of the breakpoint. F finds a string of memory bytes. The memory to be searched starts at the current CS:IP location. The string being sought is contained in memory at the CS:IP location marked with the last Shift-F7 command. The number of bytes in the target string is given as the first operand to the F command. For example, "F,1" finds the next instance of a single byte value after the current CS:IP. If the marked location points to a NOP, "F,1" will find the next NOP code. If you provide a second operand to F, it is a "retreat number". For example, "F 2,10" assumes that you are looking for a 2-byte sequence, and you have retreated 10 bytes from the starting location for your search. When the string is found, F will retreat 10 bytes from that string. That way you can view the instructions that preceded the found string. I use this feature when I am searching for BIOS and DOS interrupt calls in a program. I want to retreat before the calls, to see what function numbers were loaded into registers. I can use the F3 key to repeat the searches, giving me a sequence of disassembly displays with the interrupt in the middle. 5-4 F with no operands returns CS:IP to the marked location, in case you want to use F7 to deposit another string to be searched. If you have never pressed Shift-F7 in this session, the marked location is 0C000 of the program's starting segment. That's often a good "scratchpad" area for small programs, far from both the program and the stack. G starts the user program. You can give one or two operands to G, specifying locations within the program at which you wish to return to the debugger. These are "transitory breakpoints"; both of them are cleared when the program returns to the debugger for any reason. Whenever you start the program, at least one instruction from the program will be executed, even if there is a breakpoint at the current instruction pointer location. This means you can set a breakpoint at the current location; instructing the program to return to the debugger the next time it gets back to the current location. J jumps to the location indicated by the operand, within the current code segment. J is useful when you are exploring memory outside of your program's memory area. In that case, the immediate JMP command is executed from a buffer within your program's original code segment. JMP would therefore return you to that segment. J will keep you in the distant segment. L creates a disassembly listing, with addresses, hex bytes, and disassembled code. You can output either the entire COM program, or a section of memory beginning with the current CS:IP location. You omit the first operand if you want the entire COM program. If you want a section of memory, you provide the offset beyond the memory section as the first operand. You give the name of the output file for the listing as the second operand. If you omit the second operand, the listing goes to the printer. Examples: L,,FOO.LST outputs the entire COM program to FOO.LST. L,0200,SEG.LST outputs the section of memory from CS:IP up to CS:0200 to SEG.LST. 5-5 O sets a special fixed breakpoint. Whenever your program calls MSDOS via INT 021, the debugger will monitor the function number passed in the AH register. If the function number falls within the range specified by this command, the program will trap back to the debugger. If you give two operands to O, the operands are the lower and upper bounds for the range of trapped functions. If you give one operand, only that function number will be trapped. If you give no operands, any previous O-trap setting is cleared. For example, note that function 3F hex is the READ function for MSDOS version 2. If you want to trap whenever this READ function is invoked, you can issue the command O,03F and then start up your program with the G command. Another example: suppose you want to insure that a program does not make any of the new Version 3 DOS calls, 59 hex and above. You can issue the command O,059,0FF and then start your program. NOTE: if the second operand is less than the first, then the range wraps around through zero. For example, O,059,030 traps on 059 through 0FF, and also 0 through 030-- both version 3 and version 1 calls. SECOND NOTE: The EXIT function, hex 4C is always trapped by D86, regardless of your O-command settings. The only way you should be able to exit from D86 is via the Q-command. (If you do succeed in exiting some other way, I want to hear about it. In that case, D86 will become very confused if you reinvoke it before rebooting the computer.) Q exits the debugger and goes back to the operating system. W writes the program (if it was a COM format) and the symbol table back to the disk. In this present version, you don't have any options as to what to name the files. The program name given when D86 was invoked is always used, except that the files are always written to the current directory. The program file has the same extension as the file that was loaded, and the symbols file has the SYM extension. D86 writes the program from location 0100 in the original code segment, up to the end-of-file location saved when the program was loaded, and possibly extended by a patch-memory operation while at the end of the program. Any symbols added while in the patch-memory mode are saved in the symbols file, so that you can "reverse engineer" programs for which you do not have source, and save the symbol table results you have gleaned.