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 in later 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 following table summarizes many of the commands this section demonstrates:

GDB command Description

break sum

Set a breakpoint at the beginning of the function sum

break *0x0804851a

Set a breakpoint at memory address 0x0804851a

disass main

Disassemble the main function

ni

Execute the next instruction

si

Step into a function call (step instruction)

info registers

List the register contents

p $eax

Print the value stored in register %eax

p *(int *)($ebp+8)

Print out value of an int at an address (%ebp+8)

x/d $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. 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 ViewMachine 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 StatusRegisters 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

    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 of x 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