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 GDB 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 more about assembly code in later chapters.
We use the following short C program as an example:
int main(void) {
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 following table summarizes many of the commands this section demonstrates:
GDB command | Description |
---|---|
|
Set a breakpoint at the beginning of the function |
|
Set a breakpoint at memory address 0x0804851a |
|
Disassemble the |
|
Execute the next instruction |
|
Step into a function call (step instruction) |
|
List the register contents |
|
Print the value stored in register %eax |
|
Print out the 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 into or execute 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. Although 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 subwindow 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 the
print
and x
formatting options):
-
disass
: Disassemble code for a function or range of addresses.disass <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 address.break *0x80dbef10 # Sets breakpoint at the instruction at address 0x80dbef10
-
stepi
(si
),nexti
(ni
) :stepi, si # Execute next machine code instruction, # stepping into function call if it is a call instr nexti, ni # Execute next machine code instruction, # treating function call as a single instruction
-
info registers
: Lists all the register values. -
print
: Display the value of an expression.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. Remember that the format ofx
is sticky, so it needs to be explicitly changed.(gdb) x $ebp-4 # Examine memory at address: (contents of register ebp)-4 # if the location stores an address x/a, an int x/wd, ... (gdb) x/s 0x40062d # Examine the memory location 0x40062d as a string 0x40062d "Hello There" (gdb) x/4c 0x40062d # Examine the first 4 char memory locations # starting at address 0x40062d 0x40062d 72 'H' 101 'e' 108 'l' 108 'l' (gdb) x/d 0x40062d # Examine the memory location 0x40062d in decimal 0x40062d 72 # NOTE: units is 1 byte, set by previous x/4c command (gdb) x/wd 0x400000 # Examine memory location 0x400000 as 4 bytes in decimal 0x400000 100 # NOTE: units was 1 byte set, need to reset to w
-
set
: Set the contents of memory locations and registers.set $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 hit.display $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 & d