CSC231,
Microprocessors and Assembly Language
Smith College, Fall
2002
Index
- Getting ready to debug
- Starting the debugger
- Configuring DDD
- Setting breakpoints
- Debugging
- Useful links
In order to be debugged, a program needs to contain some special
debugging information that is generated by the assembler and
included in the output object file. NASM generates this information
when used with the "-F"
option. The example below
shows how we would assemble hello.asm
with debugging
information.
$ nasm -f elf -F stabs hello.asm
The -f elf
option specifies the format of the
binary file to be generated - Linux uses ELF, the Executable
Linking Format. The second option, -F
stabs
, tells the assembler to include debugging
information in the output file. DDD and GDB use the STABS
debugging format.
Below we show a complete example:
$ nasm -f elf -F stabs hello.asm
$ ld -o hello hello.o
As you can see, the linker (ld
) does not require any
special option.
To start debugging a program just type ddd
and the
name of the program. For example:
$ ddd hello &
The DDD window should pop up as shown in Figure 2.1.
Figure 2.1 - The DDD debugging environment
The debugging environment consists of a source frame (top), a GDB
shell frame (bottom), and a command toolbox (right).
Before we can start using DDD, we need to change some of its
default settings.
Select "Edit->Preferences"
from the main menu. On the
window that will appear, open the "Source"
tab (top
of the window). Then, check the "Display Source Line
Numbers"
option (Figure 3.1). Click OK to close the window.
Figure 3.1 - Display lines in source code
Next, select "Edit->GDB Settings"
from the main menu.
In the window that pops up, scroll approximately half way down
through the list of options, until you find the option
"Disassembly flavor"
. Change its value to
"Intel"
(Figure 3.2) and click on CLOSE.
Figure 3.2 - Disassembly Flavor
Finally, click on the "Edit->Save Options"
menu to
save these changes.
The job of a debugger is to let us analyze a program's state at a
certain point during its execution. The state of a program is given
by the program's registers (EAX, EBX, etc), its stack and memory
images.
We can check the state of a program at any point during its
execution by setting breakpoints into its source code. A
breakpoint identifies an instruction at which execution of the
program will be suspended. This temporary suspension allows us to
look into the program's registers and examine its memory
image. Notice that the debugger will stop before executing
the breakpoint instruction.
To set a breakpoint in DDD, right-click on the instruction where
you want to set the breakpoint, in the source window. Then, select
"Set Breakpoint"
from the popup menu, as shown below.
Figure 4.1 - Setting a breakpoint with DDD
The breakpoint will appear as a "stop" sign on the left of the
instruction (Figure 4.2).
Figure 4.2 - A breakpoint
The breakpoint show in Figure 4.2 will interrupt the program at line
40, before executing the "mov ebx, STDOUT_FILENO"
instruction.
What would happen if you set a breakpoint at line 39?
Your program is now ready to be debugged.
First, open an execution window by selecting the
"View->Execution Window"
menu or by pressing
ALT+F9
. This window will show any message that your
program writes to standard output.
Then click on the "Run
" button in the command toolbox
on the right of the screen. As expected, execution stops at line
40, where we set the breakpoint. This is indicated by the green
arrow next to the stop signal (Figure 5.1).
Figure 5.1 - Execution is suspended at the
breakpoint
At this point we can examine the state of the program by first
looking into its registers. This is done by selecting the
Status->Registers
menu. A window will pop up showing
the value of all of the program's registers (Figure 5.2). Notice
that you can keep this window open as you continue your debug
process later on.
Figure 5.2 - Registers of a program at a
breakpoint
As expected, the EAX register contains the value 4, which
corresponds to the SYS_WRITE
symbol. All of the other
registers contain irrelevant values.
If instead of a register we are interested in examining a
particular memory location (such as msg
), we can use
the "Data->Memory"
menu (Figure 5.3). A window will pop
up asking for the number of bytes to examine, and the start memory
address. In the example below we choose to examine 5 bytes starting
from the address of msg
. Because msg
contains a string of characters, we choose char
as the
format to print these 5 bytes. When you click on
"Display"
, a new frame inside the DDD window will
appear, showing the contents of memory (Figure 5.4). Notice that in
Figure 5.3 we used &msg
to indicate the address
of msg
.
Figure 5.3 - Show the contents of memory at
the address of msg
Figure 5.4 - The contents of memory at
msg
It is often helpful, once reached a breakpoint, to continue
execution of your program one instruction at the time to observe
how the status of the program changes. The Next
and
Step
commands in the toolbox serve at this purpose.
Open the registers window as explained earlier, and observe how the
values of the registers change as we step through the instructions
of the program using Step
.
The difference between Next
and Step
is
that Next
treats function calls (such as "call
myFunction"
) as a whole instruction, while Step
jumps into the code of the function.