Skip to main content

Awesome GDB debugging and automation

· 6 min read
Quentin Faidide

This article lists use cases to demonstrate how awesome debugging with the help of GDB can be.

We will go through a handfull of very convenient GDB tricks, starting with two very basics reminders on how to navigate around the GDB console. Note that you need to compile your software with debugging symbols.

Compiling with Debugging symbols

If you are using CMake, either add set(CMAKE_BUILD_TYPE Debug) to your CMakeLists.txt file or call cmake with cmake -DCMAKE_BUILD_TYPE=Debug ....

If you are directly calling the compiler or are using a Makefile, simply add the -g or -ggdb flag to your envar for flags or to the compiler command.

Whenever a breakpoint is met, a Unix signal is received, or when an exception is thrown, GDB will let you access its console.

To print the call stack in you current thread, you can use the bt or backtrace commands. Then, you can navigate to the level you want to inspect with f 5 or frame 5, the number 5 being the identifier of the level you want to move to as shown in backtrace. Then, you are free to print any variable you like with p my_var or print my_var. If you are inside a breakpoint or more generally if your program is stopped, you can type continue to resume its execution, continue 100 to run it and ignore 100 breakpoints, or ignore 5 to ignore this breakpoint five times.

These commands show the list of threads and switch to the thread number 5 in the list:

info thread
t 5

Attaching to an already running binary

What if you execute a binary without gdb and notice some strange behaviours afterward? You can simply attach to your already running binary by providing the executable and PID.

First get the pid:

ps -aux

Pick the pid from the PID column at the row corresponding to your executable file.

Then you can attack to it:

gdb ./My/Binary/Path -p ${MY_PID}

Starting a binary and breaking immediately

You might want GDB to know about all code and symbols before adding breakpoints. To do it, simply call start instead of run when opening gdb.

Run your code line by line

Type n or next inside the prompt.

Continuing until a specific line is reached

When hitting a breakpoint, and going line by line with next, you might get stuck in a loop that requires a lot of iterations. You might think about setting a temporary or normal breakpoint to continue untill right after the loop, but there's actually a command for that: until. Simply type until myFile.cpp:48 to continue untill the line 48 of myFile.cpp with a one-liner and without setting unecessary breakpoints. It's also a convenient way to move around.

Scripting your breakpoint to print stack traces

Any breakpoint can be scripted with GDB. This is especially convenient if you are using an old version of C++ where the standard library does not include tools to print stack traces. It also makes sense to avoid adding redundant stdout logs to debug a specific issue and to instead do it from GDB (eventually to restore the breakpoint scripts later).

After setting a breakpoint:

b my_source_file.cpp:45

Use the following commands:

commands
printf "This thread wants to take the lock!\n"
backtrace
continue
end

The backtrace command print the stack trace, and the continue one will immediately resume execution. The end commands tells gdb that the breakpoint script is over.

Now you can run continue to resume the execution and when your breakpoint will be triggered, you will get a stack trace and resume the execution.

info

You can also use the breakpoint number that are shown in info breakpoints as an argument to commands.

Interrupting a running program to inspect it

If you are running a binary inside gdb, hit CTRL+C at any time to send a Unix signal that will temporarly interrupt it and give you access to the GDB prompt as if you were hitting a breakpoint.

Using variables inside scripts

GDB provides convenience variables, which you can set the following way: set $myvar = 3.0.

As an example, to record and display cache hit rate, we could set two scripted breakpoints inside a loop to catch cache hit or miss, and have a third one after the loop to print the cache hit rate. The gdb script would look like the following:

# define hit/miss variables (note that we chose not to reset them)
set $hit = 0.0
set $miss = 0.0

# set the breakpoint for the cache hit
b loop_that_use_caching.cpp:5
commands
> set $hit++
> continue
> end

# set the breakpoint for the cache miss
b loop_that_use_caching.cpp:8
commands
> set $miss++
> continue
> end

# display the cache hit
b loop_that_use_caching.cpp:15
commands
> p $hit/($hit+$miss)
> continue
> end

Saving and restoring your gdb session

You can save and restore all breakpoint with their script using the following commands:

save breakpoints my_breakpoints
source my_breakpoints

This effectively create a gdb script that you can reload later, saving you from redefining everything after you decided to modify and recompile your binary.

Retaining breakpoints after changing your code

If you compiled your binary with -ggdb instead of -g, you will get DWARF debugging symbols which should allow you to set breakpoint on C labels.

If you want a more general solution, you can create empty functions inside your code, and set a breakpoint inside their code, in a dedicated file. This will allow you to restore your breakpoint and always retain the line location after the code is changed. For example, a popular stack overflow thread listing solutions to print stack traces demonstrates how to create an empty print_stacktrace function to then add a GDB breakpoint with the bt command inside.

Conditional breakpoints

Whenever you set a breakpoint, you can define a condition for it, using either the definition-time b my_code.cpp:45 if n == 100 or by using cond [breakpoint-id] [condition] if your breakpoint already exists.

Conclusion

I hope that this cheatsheet did a good job at demonstrating how powerfull gdb actually is and why learning it may be an important investment for C/C++ beginners. If you're an advanced GDB user, I hope that at least one of the trick was new to you or that you enjoyed reading.

I encourage everyone to refer to the GDB documentation and take the time to read it, and also to read other much cooler gdb articles that exists online.

If you liked this article, feel free to share it on LinkedIn, send it to your friends, or review it. You are also welcome to report any error, share your feedback or drop a message to say hi.