gdb quick guide


This is a quick guide to gdb1 that mostly serves as my personal cheat sheet. There is a ton of different features and approaches on how to debug and analyze a process but my goal is not to document all of them. I just want to have a list of the things that I’m using multiple times per session and sometimes more rarely (so I forget).

For you, short (but incomplete) guides like this one might come very handy, especially when you want to get the work done fast! Of course if you really want to understand the true power of a tool like gdb it would be good (if not necessary) to go at least through some parts of its official documentation.

Debug symbols

Before we start, in order run your program under gdb you need to compile/build it with debug symbols enabled. Traditionally, for C/C++ languages this is done by passing the argument -g to your compiler. If you use some build tool like CMAKE to build your code try to find and enable the debug build option.

Additionally, it is a good idea to deactivate any optimizations like the -O2/-O3 flags during compiling. The reason is because the compiler can possibly remove parts of your code or replace function calls directly with their body (inlining) which will make stepping through the code execution impossible to follow. Of course, if you are using a build system your need to find out how to do that.

Starting gdb

For attaching gdb to a running process I like to use a bash command substitution with the pgrep 2 command which returns the process id (pid). This must be passed as argument to gdb. Note here that you should use the full name of your process in pgrep and that you should have only a single process with that name running on the system otherwise pgrep will return a list of pids.

gdb -p $(pgrep <myproc>)

Alternative you can find your process’s pid with the good old ps aux | grep <myproc> command and manually copy it in the gdb input argument.

You could also start your process directly with gdb. This is particularly useful in case you want to setup breakpoints in the initialization part of your program e.g. the main() function. With the former way you will not be able to do that, e.g. attaching to a running process.

If your process requires arguments during invocation do no worry since you can pass those in gdb too. See this example where I start myproc in gdb with 3 arguments and before starting execution I set a breakpoint in function main().

gdb --args <myproc> --foo 123 --bar 456 --buz 789
(gdb) break main
(gdb) run

Basic usage

In the next paragraphs I present some of the commands that I’m usually using. Keep in mind that almost all of them have short aliases e.g. next can be invoked simply typing n. For that reason I’ve included the optional parts of their names into parenthesis e.g. n(ext). As always if something seems wrong please check the official gdb docs.

Breakpoint management

Unless we look into the core dump3 of a crashed process we need to set the points of our code where we would like the execution to stop. This can be done either with a function name or with a filename and a valid line number separated with a colon : e.g.

b(reak) myFunc
b(reak) main.c:42

To investigate your current breakpoints, their assigned number, if they are enabled or not and how many times has the execution stopped at them try

i(nfo) b(reakpoints)

In case that you want to disable a breakpoint for a while without deleting it you can

dis(able) <num>
en(able) <num>

And if you want to delete it completely

d(elete) <num>

If you want to disable/enable/delete all breakpoints in one go simply omit the <num> argument.

If you stop your debug process your breakpoints will be lost. So you could simply export them in a file

save b(reakpoints) <filename>

And then reload them in another gdb session

source <filename>

Check the later paragraph on how to automate this process.

Once the execution is stopped in a breakpoint you can see all the functions called (call stack) before you ended up in the current point

backtrace

for faster you can also just type bt.

In some cases you would like to inspect the state of some previous functions before the point in which you have stopped (or crashed!). You can simply jump to a certain frame with

f(rame) <frame-number>

where you find the frame-number through the bt command. Without any number the frame command just gives you the information for your current frame.

Another way to jump between frames is to simply use the up and down commands!

Controlling execution

Once you are in a breakpoint and you want to execute the next line of the code try

n(ext) 

Sometimes we want to continue execution to an instruction coming from a different line. That practically means that if a line calls a function (like x = f(y)) we want to jump to the beginning of the f(y) function’s body. This is done with

s(tep)

Simply calling n(ext) in the line x = f(y) would have taken us to the next line after the function f(y) is fully executed and its result is stored in x. So you would have no chance to inspect f(y) execution.

If you find tedious going through a large for loop for every iteration when stepping your code you are not alone! Instead, you could jump out of the loop using the until command with line_number corresponding to a line outside and after the loop (usually the first line after the loop)

u(ntil) <line_number>

To continue until the point that the current function returns

fin(ish)

And finally if you want to continue execution (until you hit another breakpoint)

c(ontinue)

Inspecting data

To read the values of a variable you can simply

p(rint) my_var

If this is a complex type like a struct you can of course check individual members and/or dereference pointers

p(rint) *my_var_ptr
p(rint) my_var.counter
p(rint) my_var_struct->data

If you try to dereference void pointers gdb will not know how to print them so you can simply cast them to the appropriate type!

p(rint) *(data_type*)my_var.data_void_ptr

Except from printing you might want to call a function on some variables that you have at hand. This can be done with

call myFunc(var1, var2)

Other tips

Multiple threads

Your might have an executable that runs multiple threads. If you want to see what each thread is doing you can check their call stacks with

thread apply all bt

To jump into another thread’s stack

thr(ead) <num>

Automatic variable naming

When you print a variable gdb stores the result in a variable with automatic naming. The variable names are starting with the dollar sign and have an increasing number starting from 1. You can use them to make your life easier in case you want to investigate deeper in a complex type.

p my_var
$1 = {index = 2, data = 0xcb45}

p $1.index
$2 = 2

p $1.data
$3 = <whatever-the-member2-data-is>

Again note, if my_var.data type is void* you will have to cast it to its proper type before printing p *(data_type*)$1.data.

Watchpoint comfort

Sometimes we want to conditionally stop the execution in case a variable is changed. Since we cannot predict when this will happen we cannot use a breakpoint. The watchpoint feature can do this job for us!

The execution will stop when the value of an expression changes. This expression can be simply a variable, an address or something more complex.

Imagine that you have a simply linked list of events where you store a tag and a counter per event.

typedef struct node {
    void *data;
    struct node *next;
} node;

typedef struct event {
    int cnt;
    char *tag;
} event;

If the value change that we want to detect with a watchpoint is stored in a variable deep down in a complex struct hierarchy gdb will possibly complain after we try to continue execution. For example imagine that we want to detect a cnt change in the third entry of the linked list that we just defined

watch ((event*)list->next->next->data)->cnt
continue

Warning:
Hardware watchpoint 1: Could not insert watchpoint

To work around this you could simply watch straight at the address of the variable under consideration after you first delete the watchpoint 1 4!

del 1
watch -l(ocation) ((event*)list->next->next->data)->cnt

Settings

TUI mode

For viewing the source code while debugging you can simply use the l[ist] command. This will print source code lines around your current breakpoint but it will not follow your stepping. In order to constantly see how you move through the source execution you can use the text ui (tui) mode.

To toggle the tui simply try

tu(i) d(isable)/e(nable)

To be able to use your arrow keys for the command window and not scrolling through the source you can set the tui focus with

f(ocu)s cmd

In case you want to change any of the tui windows (cmd, src or other) you can simply do

tui window height <window-name> +/-<num-lines>

I do not use that since I’m usually familiar enough with the structs that I print but if you are not then maybe you want to see each struct’s member on a different line

set print pretty on

For being able to print very long C-strings

set print repeats 0

Dynamically loaded libraries

In case you would like to set a breakpoint in parts of a library that is not loaded yet gdb will complain with

b <libfunc>
Function "<libfunc>" not defined.

In this case you could simply let gdb now that this function will exist in the future by enabling the breakpoint pending setting.

set breakpoint pending on

Persist settings

You can put all your settings in an initialization script .gdbinit so you can have them enabled every time that you start a new debug session. For more info you can always check its manual page man gdbinit.

What I usually do for “complex” gdb sessions with multiple breakpoints is to dump my breakpoints in a file with a specific name e.g. gbps short for gdb breakpoints. Then in my gdbinit I have

define db
    save breakpoints gbps
end

# NOTE: order matters for the next two commands
set breakpoint pending on
source gbps

Good luck!


  1. stands for GNU Debugger ↩︎

  2. check https://linux.die.net/man/1/pgrep ↩︎

  3. You can “debug” a crashed process by loading its core in gdb. This is not as dynamic as debugging since the program is not running anymore. But you can get some useful information on the reason of your crash by examining its call stack. See also https://en.wikipedia.org/wiki/Core_dump ↩︎

  4. Watchpoints are listed together with breakpoints and are activated/deactivated/deleted with the same way. ↩︎