3.5. Debugging Assembly Code
In addition to high-level C and C++ debugging, GDB can debug a program at its assembly code level. Doing so enables it to list disassembled code sequences from functions, set breakpoints at the assembly instruction level, step through program execution one assembly instruction at a time, and examine the values stored in machine registers and in stack and heap memory addresses at runtime. We use IA32 as the example assembly language in this section, but the GDB commands presented here apply to any assembly language that gcc supports. We note that readers may find this subsection most useful after reading the Assembly chapter.
We use the following short C program as an example:
int main() {
int x, y;
x = 1;
x = x + 2;
x = x - 14;
y = x * 100;
x = x + y * 6;
return 0;
}
To compile to an IA32 executable, use the -m32
flag:
$ gcc -m32 -o simpleops simpleops.c
Optionally, compiling with gcc’s -fno-asynchronous-unwind-tables
command line option generates IA32 code that’s a bit easier for the
programmer to read and understand:
$ gcc -m32 -fno-asynchronous-unwind-tables -o simpleops simpleops.c
3.5.1. Using GDB to examine binary code
In this section we show some example GDB commands to debug the short C program at the assembly code level. The table below summarizes many of the commands this section demonstrates:
Example GDB Command | Description |
---|---|
|
Set a breakpoint at the beginning of the function |
|
Set a breakpoint at memory address 0x0804851a |
|
Disassemble the main function |
|
Execute the next instruction |
|
Step into a function call (step instruction) |
|
List the register contents |
|
Print the value stored in register %eax |
|
Print out value of an int at an address (%ebp+8) |
|
Examine the contents of memory at an address |
First, compile to IA32 assembly and run GDB on the IA32 executable
program simpleops
:
$ gcc -m32 -fno-asynchronous-unwind-tables -o simpleops simpleops.c
$ gdb ./simpleops
Then, set a breakpoint in main, and then start running the program
with the run
command:
(gdb) break main
(gdb) run
The disass
command disassembles (lists the assembly code associated
with) parts of the program. For example, to view the assembly
instructions of the main function:
(gdb) disass main Disassemble the main function
GDB allows a programmer to set breakpoints at individual assembly instructions by dereferencing the memory address of the instruction:
(gdb) break *0x080483c1 Set breakpoint at instruction at 0x080483c1
The program’s execution can be executed one assembly instruction at
a time using si
or ni
to step or next the next instruction:
(gdb) ni Execute the next instruction
(gdb) si Execute next instruction; if it is a call instruction,
then step into the function
The si
command steps into function calls, meaning that GDB will pause the
program at the first instruction of the called function. The ni
command skips over them, meaning that GDB will pause the program at
the next instruction following the call instruction (after the function
executes and returns to the caller).
The programmer can print values stored in machine registers using the print
command and the name of the register prefixed by $
:
(gdb) print $eax # print the value stored in register eax
The display
command automatically displays values upon reaching a breakpoint:
(gdb) display $eax
(gdb) display $edx
The info registers
command shows all of the values stored in the machine registers:
(gdb) info registers
3.5.2. Using DDD to debug at the assembly level
The DDD debugger provides a graphical interface on top of another debugger (GDB in this case). It provides a nice interface for displaying assembly code, viewing registers, and stepping through IA32 instruction execution. Because DDD has separate windows for displaying disassembled code, register values, and the GDB command prompt, it’s often easier to use than GDB when debugging at the assembly code level.
To debug with DDD, substitute ddd
for gdb
:
$ ddd ./simpleops
The GDB prompt appears in the bottom window, where it accepts GDB commands at the prompt. While it provides menu options and buttons for some GDB commands, often the GDB prompt at the bottom is easier to use.
DDD displays the assembly code view of a program by selecting the View → Machine Code Window menu option. That option creates a new sub-window with a listing of the program’s assembly code (you will likely want to resize this window to make it larger).
To view all of the program’s register values in a separate window, enable the Status → Registers menu option.
3.5.3. GDB Assembly Code Debugging Commands and Examples
Here are some details and examples of GDB commands that are
useful for debugging at the assembly code level (see the
Common GDB Commands section for
more details about some of these commands, particularly for
print
and x
formatting options):
-
disass
: disassemble code for a function or range of addressesdisass <func_name> Lists assembly code for function disass <start> <end> Lists assembly instructions between start & end address disass main Disassemble main function disass 0x1234 0x1248 Disassemble instructions between addr 0x1234 & 0x1248
-
break
: set a breakpoint at an instruction addressbreak *0x80dbef10 Sets breakpoint at the instruction at address 0x80dbef10
-
stepi (si), nexti (ni)
:stepi, si Execute next machine code instruction, step into function call if it is a call instr nexti, ni Execute next machine code instruction, treats function call as single instr
-
info registers
lists all the register values -
print
print $eax Print the value stored in the eax register print *(int *)0x8ff4bc10 Print int value stored at memory addr 0x8ff4bc10
-
x
Display the contents of the memory location given an address. NOTE: the format ofx
is sticky (need to explicitly change it)x $ebp-4 Examine memory at address: (value stored in register ebp)-4 if this location stores an address x/a, an int x/wd, a ... x/s 0x40062d Examine the memory location 0x40062d as a string 0x40062d "Hello There" x/4c 0x40062d Examine the first 4 char memory locations starting at address 0x40062d 0x40062d 72 'H' 101 'e' 108 'l' 108 'l' x/d 0x40062d Examine the memory location 0x40062d in decimal 0x40062d 72 NOTE: units is 1 byte, set by previous x/4c command x/wd 0x400000 Examine the memory location 0x400000 as 4-byte in decimal 0x400000 100 NOTE: units was 1 byte set, need to reset to w
-
set
set the contents of memory locations and registersset $eax = 10 Set the value of register eax to 10 set $esp = $esp + 4 Pop a 4-byte value off the stack set *(int *)0x8ff4bc10 = 44 Store 44 at address 0x8ff4bc10
-
display
print an expression each time a breakpoint is hitdisplay $eax Display value of register eax
3.5.4. Quick summary of common commands for assembly debugging
$ ddd ./a.out (gdb) break main (gdb) run (gdb) disass main Disassemble the main function (gdb) break sum Set a breakpoint at the beginning of a function (gdb) cont Continue execution of the program (gdb) break *0x0804851a Set a breakpoint at memory address 0x0804851a (gdb) ni Execute the next instruction (gdb) si Step into a function call (step instruction) (gdb) info registers List the register contents (gdb) p $eax Print the value stored in register %eax (gdb) p *(int *)($ebp+8) Print out value of an int at addr (%ebp+8) (gdb) x/d $ebp+8 Examine the contents of memory at the given address (/d: prints the value as an int) (gdb) x/s 0x0800004 Examine contents of memory at address as a string (gdb) x/wd 0xff5634 After x/s, the unit size is 1 byte, so if want to examine as an int specify both the width w and d