3. Using DEBUG

Back Home Up Next

My intent is to focus on using the ML assembler, understanding DOS programming, and perhaps being able to use the assembler to make routines that work with other languages like Quick BASIC or C. Although the debuggers can be used to create programs as we've already done, they aren't very easy to use for anything more than rather trivial programming. The don't support symbolic labels, for example, and you need to anticipate where things will be and count bytes, manually. The assembler is much better to use for creating programs of any significance.

However, debuggers are quite useful for testing bits of code and learning exactly what happens in the process. Sometimes, it's not clear what the documentation from Intel is saying about an instruction, for example, and dropping into the debugger to write that instruction and test it with some register values is rather easy and it tells you right away what is going on. In that sense, they can supplement the documentation. They can also help you find out where you went wrong in writing a routine, too, or in narrowing down where a problem resides so that you can better focus your attention.

Let's first walk though the creation of the second program more thoroughly, now, using DEBUG.

Using DEBUG

First, we started up DEBUG by typing in the DOS command to invoke it:

C>debug «

DOS first has to find the DEBUG.EXE program. It does this by looking in the current directory and, if not found, then by looking in each directory mentioned in your DOS PATH variable. Once found, DOS runs it like it does any other program. When it does, DEBUG sets up a memory segment for you to use and then displays the '-' prompt, waiting for your input to it.

At this point, we typed 'a' and hit ENTER. This tells the DEBUG program that you want to try assembling some instructions into the memory. Other than the first time we enter this command, DEBUG will start assembling at the place we left off last time we tried assembling code. But since this is the first time we entered this command, DEBUG starts at offset 0100h, which is where all .COM programs start executing.

-a «
102D:0100

After the 'a' command is entered, DEBUG displays that address and waiting for input. The line shows two four-digit hexidecimal numbers, separated by a colon ':'. The first number is the segment and the second is the offset. Each time you start up DEBUG, you may get a different segment value. But that's not usually important. It's the offset that counts. All .COM programs will start at offset 0100h, because that's how DOS arranges things in memory when it runs them.

We then entered an instruction to place a number into the DX register.

102D:0100 mov dx, 10b

This number, it turns out, is the offset address for the message that will be printed. If you go back to the previous page and look at the DOS documentation for Function 09h, you'll see that this is a pointer to the literal string to be printed.  How do I know what the offset address will be, here? Well, I cheated by looking ahead. Let's continue on and you'll see what I mean. Also note that DEBUG always takes its numbers in hexadecimal format -- the "10B" shown above is the hexadecimal address, not some decimal value.

The next instruction was then entered:

102D:0103 mov ah, 9

This instruction sets the DOS Function number, so that DOS will provide the desired service (printing out a string of characters onto the screen.)

The next instruction (shown next, below) actually makes a function call into DOS, asking it to perform the indicated function request:

102D:0105 int 21

The next two instructions shown below do something similar, except this time it's Function 4Ch, which is asking DOS to terminate (kill, abort, murder, whatever you want to call it) this program and to return control of the computer back to DOS:

102D:0107 mov ah, 4c
102D:0109 int 21

Again, you see that register AH is being set to the desired DOS function and then the actual instruction needed to call DOS is used. This is one of those rare DOS functions where DOS doesn't all the program to continue running. So, at this point, the program no longer continues.

Now, it's at this point that I decided to enter in the literal string of characters to be displayed -- after the last instruction asking DOS to kill the program. It's safe to add literals at this point, since it is certain that the program cannot accidentally try to execute this data (which would NOT be good.)

102D:010B db "Hello out there!", 0d, 0a, '$'
102D:011E «

This is the literal string we want printed out by DOS onto the screen. Those two hexadecimal values, 0Dh and 0Ah, are the special ASCII characters for carriage return and line feed, respectively. The last character on this line tells DOS where the literal string ends, so that DOS Function 09h will know when to stop printing. If you read the doc I included on DOS Function 09h, you'll see them mention it.

Take note that the offset address displayed by DEBUG right at the point where I start entering this string is 010Bh. Just as I'd placed into the DX register earlier! I knew I should use that value in the first instruction because I'd already tried to enter in the program (in an earlier attempt) and had discovered how much space this program would need. 010Bh is the first address that follows the end of the program's code, so I decided that this is a good place to add in the literal string.

Then ENTER is used on a blank line above (offset address 011Eh) to tell DEBUG I'm done with assembling code. DEBUG will then re-display the '-' prompt and let me enter in a new command.

Looking above at the line where I ended the assembly command by hitting ENTER on a blank line, you can see that the offset address there is 011Eh. I also know that the program starts at offset address 0100h. The difference between these is 001Eh, which is the actual size of my total program (code + data.) When I try and write the program to a disk file (shortly), DEBUG looks at value of the CX register to decide how many bytes to write -- actually, it looks at the combined BX and CX register pair, with register BX taken as the high order 16 bits of a 32-bit value, but BX is zero (we'll verify this, later.) So I need to update the value in the CX register to reflect the program size. To change it, I use the 'r' command and specify the register, CX:

-r cx «
CX 0000
:

At this point, DEBUG has given me a colon ':' prompt and it is waiting for the value to put into the CX register. I enter 1E:

:1e «

Now, I've told DEBUG how big my program is. It's time to tell DEBUG the name I want to use, for the DOS file to save it:

-n lesson02.com «

That was easy, actually. Just use the 'n' command and specify the filename. Now DEBUG knows the size and the name I want to use.

Now, just tell DEBUG to save the file. This is done with the following 'w' (write) command:

-w «
Writing 0001E bytes

It confirms the size it wrote, so just check that when you do it to make sure it matches the value placed into the CX register. You can see here that it does.

Now, I just quit from DEBUG and that takes me back to DOS:

-q «

C>

That's it, a fuller explanation of each step I used in the debugger program, DEBUG. These explanations are also quite similar to those I used in GRDB, as well. One difference in GRDB is that the write command it uses allows me to specify more information directly, so I was able to bypass a few of the above details. Other than that, it's quite similar.

Before I end here, let's quickly examine this second program we'd created using DEBUG, by asking it to display the register values at the outset (I mentioned earlier that I 'knew' that BX was zero, for example.) To do that, just start up DEBUG while specifying the name of the program:

C>debug lesson02.com «
-r «
AX=0000  BX=0000  CX=001E  DX=0000  SP=FFFE  BP=0000  SI=0000  DI=0000
DS=1091  ES=1091  SS=1091  CS=1091  IP=0100   NV UP EI PL NZ NA PO NC
1091:0100 BA0C01        MOV     DX,010B
-q «

C>

By typing in the 'r' command without specifying a register, DEBUG instead simply displays all of the register values. These values are typical for what they look like when a .COM program starts executing. Note that CX here also represents the number of bytes read by DEBUG when it loaded lesson02.com.

 

Last updated: Thursday, July 08, 2004 12:19