Debugging C++ with GDB
Authors: Tiago P. Peixoto1, Lena Mangold1
Affiliations: 1Inverse Complexity Lab
License: CC-BY
GDB stands for GNU Debugger, and it provides a more robust debugging infrastructure than using print statements in your code.
A simple introduction to using GDB can be read here. Please take a look there.
To debug some C++ code that is called from python, you should call gdb with the python interpreter:
$ gdb `which python`
GNU gdb (GDB) 16.3
Copyright (C) 2024 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-pc-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from /sbin/python...
Downloading separate debug info for /usr/bin/python3.13
Reading symbols from /home/count0/.cache/debuginfod_client/76195980c4ccf58a63406c313d47a475eabc0bbe/debuginfo...
(gdb)You can then load your script my_script.py with:
(gdb) set args ./my_script.py
(gdb) runRecompile with debug symbols¶
GDB works best when debug symbols are included during compilation. This can be done using the -g flag during compilation.
It is also possible to include a DEBUG flag in the Makefile, as is shown in the following example. Compiling in debug mode will then include debug symbols (-g), disable optimisation (-00), and enable assert() by omitting -DNDEBUG. Note that when compiling graph-tool, it is not recommended to compile the entire library with debugging enabled; see X below for how to only debug specific parts of the library.
CXX=g++
DEBUG ?= 0
ifeq ($(DEBUG), 1)
CXXFLAGS=-g -O0 -fopenmp -std=gnu++17 -Wall -fPIC -DPIC `pkg-config --cflags graph-tool-py` -DBOOST_ALLOW_DEPRECATED_HEADERS
else
CXXFLAGS=-O3 -fopenmp -std=gnu++17 -Wall -fPIC -DPIC `pkg-config --cflags graph-tool-py` -DBOOST_ALLOW_DEPRECATED_HEADERS -DNDEBUG
endif
LDFLAGS=`pkg-config --libs graph-tool-py` -shared
SOURCES=$(shell echo *.cc)
HEADERS=$(shell echo *.hh)
OBJECTS=$(SOURCES:.cc=.o)
TARGET=libexample.so
all: $(TARGET)
clean:
rm -f $(OBJECTS) $(TARGET)
$(OBJECTS): %.o: %.cc %.hh example.hh
$(CXX) $(CXXFLAGS) -c $< -o $@
$(TARGET): $(OBJECTS)
$(CXX) $(CXXFLAGS) $(OBJECTS) -o $@ $(LDFLAGS)
To recompile in debug mode, run
make clean
make DEBUG=1Useful GDB commands¶
| Command | Description |
|---|---|
run (r) | Run the program until it finishes, crashes, or hits a breakpoint |
start | Run and pause at the first line of main() |
next (n) | Execute the next line (step over function calls) |
step (s) | Execute the next line (step into function calls) |
continue (c) | Resume execution until the next breakpoint or crash |
break file.hh:N or b file.hh:N | Set a breakpoint at line N in file.hh |
break func | Set a breakpoint at function func() |
delete | Remove all breakpoints |
print var or p var | Print the value of variable var |
list (l) | Show source code around current line |
info locals | Show all local variables |
quit (q) | Exit GDB |
Workflow Example (for C++ code called from Python)¶
If the Makefile includes the DEBUG flag and the make clean command as in the example Makefile above, the following is an example workflow for debugging C++ code that is called from Python.
make clean # Delete compiled .o files and output .so files
make DEBUG=1 # Compile with debug flag
gdb `which python` # Start GDB
(gdb) set args ./my_script.py # Load your script
(gdb) break headerfile.hh:N # Set a breakpoint in line N in headerfile.hh
(gdb) run # Run and pause at breakpoint
(gdb) info locals # Show local variables
(gdb) next # Step over some line
(gdb) print alpha # Check value of variable alpha
(gdb) continue # Resume until crash or breakpoint
(gdb) quit # ExitDebugging in graph-tool¶
When debugging in graph-tool, we may only need debugging enabled for specific parts of the code. Compiling the entire library with debugging enabled uses a lot of memory and could even cause the build to crash; to avoid this, we can enable debugging for a portion of the code while keeping everything else compiled as usual. This works by overriding the compilation flags when make is being called; to avoid having to type these flags out every time, we can create a bash alias for this purpose.
Setup: add the following alias to your ~/.bashrc (or ~/.zshrc), then run source ~/.bashrc to activate it:
alias mkdbg_nolto='make 2>&1 EXTRA_CXXFLAGS="-Wp,-D_GLIBCXX_ASSERTIONS -D_GLIBCXX_ASSERTIONS -DBOOST_USE_VALGRIND -UNDEBUG -O0 -g -gdwarf64 -fkeep-inline-functions -fno-default-inline -fno-inline -Wl,-O0,--as-needed -fno-lto -Wl,-fno-lto" LDFLAGS="-fno-lto" EXTRA_CPPFLAGS="$EXTRA_CPPFLAGS"'Having this set up, the workflow is to compile graph-tool as usual and to call mkdbg_nolto (instead of make) whenever changes to the relevant files have been made. To force a file to be recompiled in debug mode even when no changes have been made to it, we can force it to be ‘out-of-date’ by calling touch graph-tool/src/graph/inference/blockmodel/some_filename.cc.