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

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 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 ViewMachine 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 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 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,
                        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 of x 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 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 and d