GDB

Introduction

GDB is a free command-line debugger for UNIX. It supports several languages, and has an extensive list of features. Unfortunately, debugging from the command-line can be difficult for new users. This has led to the development of many graphical front ends, such as gf.

Debugging Your First Program

The Executable

Let's first compile a simple C program.

1
2
3
4
5
6
#include <stdio.h>

int main(int argc, char **argv) {
    printf("Hello, world!\n");
    return 0;
}

Compile this with:

1
gcc hello.c -o hello -g

This'll produce our executable, hello.

Now we can debug the program.

Let's start GDB and tell it to read our executable:

1
gdb hello

You should see it says "Reading symbols from hello...done.". This means it successfully loaded in the executable.

If you now type "run" and press enter, you should see your program print out the message "Hello, world!".

If your program requires command line arguments, use

1
gdb --args myprog arg1 arg2

otherwise gdb will interpret the extra arguments as being for itself.

Break and Next

Now let's try to inspect what is happening in our program. We shall set a breakpoint at the main function. This means our program will be paused when the main function starts.

1
break main

The break command takes several different types of argument. You can pass a function name, a line number (within the current file), or a line number in the format "sourcefile:linenumber" to break in another file.

You can also break directly on a memory address by prefixing it with an asterisk, i.e. "b *0x400000" or "b *main+20".

Now trying running your program. You should this text:

1
2
Breakpoint 1, main (argc=1, argv=0x7fffffffe948) at hello.c:4
4       printf("Hello, world!\n");

This means we're just about to execute the line that prints out our message - but we haven't done it yet. We're also shown the arguments that were passed to the main function, the breakpoint's number, and the source file we're in.

To see the surrounding code, try the "list" command.

To run a line of code, we can use the "next" command.

1
next

This can be abbreviated to "n". Also, in GDB, the last command can be repeated by typing nothing and just pressing enter.

Continue

After using the next command thrice, you'll be greeted with the following cryptic message.

1
0x00007ffff7a41f4a in __libc_start_main () from /usr/lib/libc.so.6

This means we've left the main function and are in libc territory. To exit the paused state, we can use the continue command.

1
continue

This can abbreviated to "c".

This'll continue the program until another breakpoint is hit, or it exits. In our case the program exits.

To exit GDB, use the "quit" command.

Conditional breakpoints

To add a new breakpoint only when a certain condition is met, add an "if" statement after the break line, e.g. "break myfunc if myarg == 2". In this example myfunc is assumed to take a parameter named myarg, but variables referenced in the if statement can be anything in scope, not just arguments.

To add a condition to a breakpoint that was already set up, use "cond" followed by the breakpoint number and condition, e.g:

(gdb) b myfunc
Breakpoint 1 at 0x400000
(gdb) cond 1 somevar == 4

Subroutines

To look at how GDB interacts with subroutines in our program, let's expand it.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include <stdio.h>

int AnotherFunction(int a, int b) {
    int c = a + b;
    return c;
}

int main(int argc, char **argv) {
    printf("Hello, world!\n");

    int x = AnotherFunction(3, 5);
    printf("x = %d\n", x);

    return 0;
}

Compile this as before, and get it running in GDB, breaking at the main function.

Now we can look at a new command, "step" (shortened to "s"). When the step command is used, the debugger will go inside any subroutine that is being called, rather than going over like the next command would.

Use the next command to go past the first print statement, and then use the step command to go into the other function we made. You should see this:

1
2
AnotherFunction (a=3, b=5) at hello.c:4
4       int c = a + b;

We are now inside another function. We are also shown the parameters that were passed to it.

We can also see the current stack trace, i.e. the list of functions that were called to get here. This requires the use of the "bt" command.

1
2
#0  AnotherFunction (a=3, b=5) at hello.c:4
#1  0x00005555555546de in main (argc=1, argv=0x7fffffffe968) at hello.c:11

Printing Variables

Once you've used the next command to go past the line that calculates the variable "c", how can you see what its value is?

Here's where the print command comes in handy. Type "p" and then the expression. So in this case, type "p c", and GDB will display the value 8.

To print the value of a register, prefix the registers name with a dollar symbol, e.g. "p $eax".

Keeping a variable printed

If you're stepping through your program and want to always see the value of a variable as it changes, you can use the "display" command, and the variable will automatically be re-printed after other commands you make such as step/next.

Use "undisplay <number>" to stop a variable being printed.

Examining memory

To examine a piece of memory, use the "x" command. There are various formats you can choose for how the memory should be interpreted, as well as the amount to be displayed.

These options use the format "x/<count><width><type>": e.g. "x/20wx 0x400000" will print 20 words/32-bit values (w) in hexadecimal (x) starting at the address 0x400000.

The <width> can be one of:

b - 1 byte  / 8 bits
h - 2 bytes / 16 bits
w - 4 bytes / 32 bits
g - 8 bytes / 64 bits

and <type> can be:

d - signed decimal
u - unsigned decimal
x - hex
f - float
c - char
s - string
t - binary
i - instruction (disassembly)

You can also pass variables to "x" instead of a raw addresses. The variable will always be interpreted as a pointer even if it isn't: i.e. using x on an int holding a value 0x12345 will try and display memory starting from address 0x12345.

These slash formatting options can also be used with print/p but are perhaps less useful since p already prints based on the type of its argument.

Layouts

gdb has several layout modes that allow you to see the source code / dissassembly / registers along with the normal command window.

To view the source along with the command window in a split view, type "layout src" (keyboard shorcut: <CTRL + x CTRL + a>). After doing this the up/down keys will scroll the source instead of page through command history, to fix that type "focus cmd".

Similarly, to view assembly with the command window type "layout asm", and to view both source and assembly, use "layout split".

After using "layout src" or "layout asm", you can get a panel showing the current register state by using "layout regs". It will highlight the registers that change as you step through your program.

Misc tips

Whenever you print / examine something in gdb, it'll print a line like: $1 = 1234 That '$1' is an automatic variable that you can refer to in later commands such as 'p $1 + 2'

'$' on its own refers to the last thing that was printed, and '$$' the second last. These can be useful e.g. when printing out linked lists by using p "$->next".

Wrapping Up

That's all we'll cover in this simple tutorial. To exit GDB, type "quit" or "q".

Creating custom commands

At startup GDB will read a file in your home folder called .gdbinit. This can contain custom commands, for example:

define con
    target remote :1234
end

You can store temporary values in "convenience variables". They are prefixed with $, so be careful not to overlap with system registers. For example:

define ldcx
    set $new_rip = context->rip
    set $new_rsp = context->rsp
    set $new_rbp = context->rbp
    set $rip = $new_rip
    set $rsp = $new_rsp
    set $rbp = $new_rbp
    set $rip = $new_rip
end

This command loads the new values of the registers RIP, RSP and RBP from a structure (that will be inaccessible after any of the registers are modified), and then sets their values all in one go. I set the RIP register twice because it likes to be set both before and after changing the stack.

How do I...?

Set the executable file

file <executable>

Run the executable

run / r

Attach to a process

attach <pid>

Connect to a remote process

target remote <ip>:<port>

The IP address can be ommitted if it's on the local machine.

Continue execution

c

Continue execution at a different location

jump <location>

<location> can be a line number, function, etc.

p <expression>

In the expression you can use $ to refer to the last printed value.

Set a breakpoint at the start of a function

b <function_name>

Set a breakpoint at a specific line in the current file

b <line_number>

Set a breakpoint at a specific line in another file

b <file_name>:<line_number>

Set a conditional breakpoint

b <where> if <expression>

Step in

s

Step out

finish / fin

Step over

n

Break when a variable changes

watch *<address>

See the call stack

bt

Switch frame

bt
frame <number>
up
down

See the processes' threads

info threads

Switch thread

info threads
thread <number>

Add a source directory

dir <path>

View intel disassembly

set disassembly-flavor intel

See the surrounding lines of code

list

View the source code in a separate panel

layout src    (Source code)
layout asm    (Disassembly)
layout split  (Both)

focus cmd     (Return the focus back to the command prompt)

View the registers in a separate panel

layout regs

Modify a variable

set <variable or $register> = <expression>

View locals

info locals
info args

View globals

info variables

Show fields in struct

ptype /o <struct>

Edited by nakst on