First steps using Rust GDB
I occasionally need to debug Rust programs. There are several debuggers for Rust, rustup even ships with two: rust-gdb and rust-lldb. Technically they are not debuggers but thin wrappers over gdb and lldb, the debuggers from the GCC and LLVM projects respectively. I'm partial to gdb as it has built-in integration with Emacs. The wrapper take care about teaching gdb about Rust's data types. Things like their memory layout, how to print them, etc. rustup ships with with both rust-gdb and rust-llvm.
Configuring GDB
Out of the box gdb doesn't preserve its command history across session. We need
to enable the history in gdbinit. To enable it globally for all projects we
can place our gdbinit in ~/.config/gdb/. Another setting is to change
debuginfod from ask to on so that we aren't prompted each time we start
rust-gdb to download the debug info.
set history save on
set history size 10000
set history remove-duplicates unlimited
set debuginfod enabled on
Invoking a Program
After starting rust-gdb one needs to tell it what program to run and with what
arguments. To do we use the file and set args. When using rust, most of the
time the executable we will run is in the target/ directory. Commands that run
code such as cargo test or cargo run tell which executable they are running.
As an example, lets say we are trying to debug a test in the rustbus project. To
run the test we call cargo test -p rustbus --lib -- test_busname_constraints. The line that starts with Running tells us which executable was invoked
$ cargo test --lib -p rustbus -- test_busname_constraints --no-capture
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.04s
Running unittests src/lib.rs (target/debug/deps/rustbus-ccbc95c399b490c8)
running 1 test
test params::validation::test_busname_constraints ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 41 filtered out; finished in 0.00s
To run the test under rust-gdb we would do
file target/debug/deps/rustbus-ccbc95c399b490c8
set args test_busname_constraints
Note that we don't set the -p rustbus or --lib arguments as they are
arguments to cargo, not the executable that is being invoked. To explicitly mark
the distinction between cargo arguments and arguments to the executable to be
invoked we use --.
With both file and args being set we can invoke the program by entering
run.
Debugging basics
When using gdb from the terminal it is a good idea to enable to the TUI by
pressing C-x C-a. That way we can see the source code directly in the GDB
window.
To be able to step through the code we will need to set a breakpoint. The most
straighforward way to set a breakpoint is to use the linespec location
specifier,/path/to/file:$line. gdb supports other location specifiers, you can
invoke help break to read about them. For our current example we can use b src/params/validation:99. Now when we call run the execution should stop when
it reaches that line. From that point forward we can print values using p or
step through the execution.