CHAPTER 3 REQUIREMENTS AND OPERATION System Requirements for D86 D86 currently requires either an IBM-PC or compatible computer, a Texas Instruments TI-PC, a Wang PC, a Tandy 2000, a DEC Rainbow, a Sirius/Vector 9000, a Zenith Z-100--ET-100, or a Sanyo 550 or 555 computer. The computer must be running MS-DOS V2.00 or later. The IBM compatibility must exist at the BIOS and video interface levels: D86 calls the BIOS to obtain keystrokes and video status information; and, on an IBM-PC, D86 writes directly to video memory at segment 0B000 (if the BIOS says monochrome) or 0B800 (if color). If your MS-DOS machine is not one of the above-mentioned specific models, chances are it's an IBM-compatible and you'll run D86 just fine. For example, all of the "clone" computers released in the last several years are IBM-compatible. If it is one of the above non-IBM models, D86 has special BIOS code for your computer, invoked via the +B switch described the section "BIOS Switching" later in this Chapter. D86 is fairly flexible about memory management. If there is enough memory, D86 will take the combined sizes of D86.COM and A86.COM (currently about 38K bytes), plus 64K bytes for its own stack, and leave the rest for the program being debugged. If memory is tight, D86 will reduce the memory allocated to its own stack, down to a minimum of 16K bytes. The segment occupied by the program being debugged will be similarly reduced. If the program is a COM file, you can tell this by the initial SP value, which is 0FFFE if there is a full 64K, less if there isn't. Thus, D86 will work with as little as 70K bytes beyond the operating system; but the symbols capacity and the program's memory will be severely limited in that case. It is best to have at least 166K bytes of memory available when D86 is running. Invoking D86 You invoke D86 by issuing the command D86 [+Bn] [+V] [progname [command-tail]] where progname is the name of the program you are debugging. In other words, you type a program invocation line just as if you were about to execute the program without a debugger, except that you append D86 before the line. The B and V switches can appear in either order. The following sections describe in detail the elements of the D86 invocation line, and how D86 acts on them. 3-2 Finding the Program File On most other debuggers, you have to give the full file name, with an explicit extension and a specific directory. With D86, you don't: D86 uses almost the same algorithm for locating a program file that COMMAND.COM does: 1. Look for COM, then EXE, then BAT in the current directory. 2. Look for COM, then EXE, then BAT in each directory in turn given in the PATH environment variable. The one difference is that D86 will look only for one extension if you give an explicit extension (and it doesn't have to be COM, EXE, or BAT). COMMAND.COM ignores the extension you give-- I thought that was just too absurd, and didn't duplicate it. A strange feature that I did duplicate is COMMAND.COM's lack of concern for whether the program is named COM or EXE. If the program file begins with a valid EXE header, it's treated as an EXE no matter what it is named. If not, then it's treated as a COM file. D86 provides limited support for BAT files. (That's better than other debuggers, which provide no support.) If your program is a BAT file, D86 reads the first line of the file and pretends that that was what you typed following "D86" in your invocation. The D86 status screen (gotten via Ctrl-S) gives you this line, and tells you what BAT file it came from. The BAT file limitations are that D86 doesn't skip over remark lines, doesn't substitute batch-file parameters, and doesn't perform console redirection specified in the batch-file line. You can also invoke D86 with no progname. The debugger comes up with no program loaded, allowing you to simply poke around the machine. If D86 had a problem loading your program, you'll see all NOPs in memory instead of instructions. You can type Ctrl-S to get the status screen that tells you what the problem was. Finding the Symbols File D86 is a symbolic debugger. It uses a special .SYM file produced in one of three ways: First, if your program was produced by A86, then the .SYM file was produced by A86 at the same time. Second, if your program was produced by a high-level language such as Pascal or C, you can feed the linker's .MAP listing to the special MAPD86 tool, available to registered D86 users only. Third, you can "reverse engineer" a program by adding symbols while in D86's patch mode, then create a .SYM file with D86's W command. 3-3 When invoked, D86 looks for a file with the program's name and a .SYM extension. D86 first looks in the current directory for this file, and then in each directory specified in the PATH environment variable. It is not necessary for the SYM file to exist. If there is no SYM file, the debugger simply comes up with no user symbols defined. You'll also get no user symbols if the SYM file was not of a correct format (it wasn't produced in one of the ways mentioned in the previous paragraph, or it has been corrupted in some way). If you were expecting symbols and didn't get any, you can press Ctrl-S to get the status screen that tells you what the problem was. BIOS Switching Some of the non-IBM computers named at the start of this chapter are automatically detected by D86, so you shouldn't need any special action to use D86 with them. Others have no obvious identifying features to them, so that you need to tell D86 that you're using that computer. You do so with the switch +B following by one of the following machine numbers: 4 for the IBM-PC (automatic) 5 for the Wang-PC (automatic) 6 for the Texas Instruments PC (automatic) 7 for the Tandy 2000 (automatic) 8 for the Sanyo 550 or 555 9 for the Sirius/Victor 9000 10 for the DEC Rainbow (automatic) 12 for the Zenith Z-100 and ET-100 Machines marked "automatic" are automatically detected by D86, so you shouldn't need the B switch for them (but I give one anyway just for safety). For example, if you're running on a Sanyo, you invoke D86 via D86 +B8 progname Incidentally, the Sanyo interface is of interest because the BIOS is IBM-compatible but the video isn't. So invoking +B8 causes all video output to go through the BIOS, which is slow but it works on all IBM compatibles as well. So if D86 doesn't work on your BIOS-compatible machine, you can try the +B8 switch. The D86 Environment Variable You can make your +B setting (and, for that matter, a +V setting) automatic by placing it into a DOS environment variable called D86. For example, if you run on a Sanyo, you issue the command SET D86=+B8 to the DOS prompt. You can make the setting permanent by placing the above line into the AUTOEXEC.BAT file on your system disk. 3-4 Two-Screen Debugging with +V The +V option can be used if you have both a monochrome and a color monitor. You invoke D86 when the operating system is on one monitor-- with the +V switch, the debugger will appear on the other monitor, and program console output will appear on the operating system's monitor. In order for the +V option to work, you must initialize both screens by MODEing to them sometime after powering up the machine. You should also make sure that the blinking cursor is at the bottom of the screen on which the debugger will appear (the simplest way to do this is to type ENTER until the prompt gets to the bottom). If you can suppress the blinking cursor, that's even better. See in your DOS operating manual for instructions on how to use MODE to switch between screens. D86 doesn't do the initialization, because I couldn't figure out how to get the BIOS to do so without blanking the screen, and you might not want the screen blanked every time you start a D86 session. The D86 Screen Display When D86 starts up, it generates a full-screen display, and awaits your debugger commands. In the top part of the screen is a symbolic disassembly of the A86 program, with the screen cursor positioned next to the instruction pointed to by the 8086 instruction pointer. In the lower left corner is a fixed display of the complete 8086 register set. At the top of the second column of the register-set display is a display of the 8086 flags. Each flag displays as blank if the flag is off; a lower case letter if the flag is on: o for overflow, d for direction, i for interrupts enabled, s for sign, z for zero, a for auxiliary carry, e for parity even, and c for carry. Across the bottom line of the screen is a display of the contents of the user stack. The display begins next to the SP register value, with the number of elements on the stack. (The stack is assumed to have 0 elements when SP is at its original value, which is 0FFFE for COM files, and a value specified in the header record for EXE files). The number of elements is followed by a colon, followed by as many of the top stack elements as fits on the line. The initial display will have zero elements; nothing is yet on the stack. 3-5 To the right of the registers are 6 lines, numbered 1 through 6. On these lines, you can generate windows into 8086 memory, displaying bytes, words, or ASCII text in a variety of formats. The windows can be located either at absolute memory locations, or be pointed to by any of the 8086 registers. The commands you issue to generate these windows are described in Chapter 6. D86 Commands There are 5 kinds of activities you perform in D86: 1. Issuing assembly language commands for immediate execution 2. Issuing debugger commands via lines that begin with a single letter 3. Issuing debugger commands via the function, Ctrl, and cursor-control keys on your keyboard 4. Setting up windows that display memory 5. Issuing assembly language commands to enter into memory (PatchMem) Immediate Assembly Language Commands A primary part of the D86 command language is the A86 assembly language. With it, you can jump to different areas of your program, set your registers, perform arithmetic, and call any of the procedures of your program. Simply type in any legal A86 instruction, and it will be executed immediately. JMP and RET instructions cause the program counter (and thus also the disassembly) to move to the indicated destination. CALL instructions cause the entire procedure to be executed. WARNING: The immediate-execution feature is a little tricky if you are in a multi-segment program, or if you jump to exotic parts of the 86 memory space (i.e., into MSDOS, ROM, video memory, or the interrupts table). This is because D86 needs a buffer in which to put the immediate-execution command. The buffer should be in your program's CS segment, so that commands such as jumps and near calls execute correctly. So D86 must always search in CS for a satisfactory buffer. Here is how D86 finds it: 1. If you declare a label D86_BUFFER, pointing to a buffer within your program, then D86 will use that buffer as the offset for its immediate instruction. 2. If not, then if the program's CS register is the same as its SS register, D86 will use (SP-300) as its immediate buffer. Thus, your stack should have plenty of room (a good practice in general). 3-6 3. As a last resort, D86 uses offset 00E0, which points to the last 32 bytes of the Program Segment Prefix (PSP). In that case, if you were to issue an immediate command that reads from a disk file, you would be in trouble, because the disk-read operation clobbers the PSP, and the command would not be trapped back to the debugger. In cases 1 and 3 above, the segment containing the buffer is the program's CS segment, unless that is out of range (below the program's original CS, or above the top of available program memory). If the program CS is out of range, the program's original CS is used instead. In that case, immediate instructions such JMP, RET, and CALL will not work correctly. Note that the above caveats do not apply to single stepping. Entering Instructions Into Memory D86 allows you to alter 8086 memory in two ways: first, you can issue immediate assembly language commands which, when executed, store values in memory. The other method is to enter a special mode, in which you enter instructions directly into 8086 memory. You enter this mode by typing the F7 (PatchMem) key. The screen cursor jumps from the left edge of the line at the current program counter, into the middle of the line where the instruction is. You start typing over the instruction, to signal that you are clobbering the instruction that was there. If you did not intend to enter this mode, you can backspace back to the start of the instruction, and type a carriage return. Each line you type in is checked for errors. If there was an error, a message is displayed, the cursor remains at the same location, and you try again. If there was no error, the cursor moves beyond the newly-assembled line, and you can type in another line. To exit the memory programming mode, you type any of the control key commands at the beginning of the line. Entering Data into 8086 Memory You can deposit data into the 8086 memory space by using the programming mode described in the above section. Simply enter DB and/or DW statements instead of instructions. Note that ASCII strings can be entered with the DB instruction; and that arrays can be initialized via the DUP operator in a DB or DW statement. 3-7 Adding Symbols to a Program Patch mode also allows you to do something that you cannot do in immediate execution mode: add symbols to the program. You can do so by either: 1. Typing in a new symbol name, followed by a colon; or 2. Typing in an EQU directive. There are several uses for this. First, you might want to create an abbreviation, by equating a short name to a long one, or to a hard-to-remember constant value. Second, you might want to "reverse engineer" a program for which you have a .COM file, but not the A86 source code. Each time you add a label, the disassembly becomes more readable. Third, you might want to label code that you have added in patch mode. After you have added symbols to the table, you can save the resulting expanded table with the W command. Simply type W followed by the ENTER key at the main debugger command level. Forward referencing is allowed when you are in patch mode. You must be careful, however, to resolve any forward references you have made. In particular, you will cause the assembler to become very confused if you overwrite a forward reference with some other code before you resolve the reference. So don't!