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.
Navigating the stack
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>
Print formatting
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!
-
stands for GNU Debugger ↩︎
-
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 ↩︎
-
Watchpoints are listed together with breakpoints and are activated/deactivated/deleted with the same way. ↩︎