Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

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) run

Recompile 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=1

Useful GDB commands

CommandDescription
run (r)Run the program until it finishes, crashes, or hits a breakpoint
startRun 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:NSet a breakpoint at line N in file.hh
break funcSet a breakpoint at function func()
deleteRemove all breakpoints
print var or p varPrint the value of variable var
list (l)Show source code around current line
info localsShow 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                       # Exit

Debugging 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.