Last updated November 5th, 2019
This page details various tools and techniques we use to debug the Materialize software stack / Rust programs more generally.
There's a couple of different options for debugging problems with Rust programs
-
Printing more data / printing backtraces
-
Using a debugger
Rust provides wrappers over gdb
and lldb
called rust-gdb
and rust-lldb
.
These wrappers mainly add better pretty printing for Rust objects and limited
support for parsing Rust expressions in the debugger's REPL. On macOS,
rust-lldb
should be ready to go pretty much out of the box but rust-gdb
will require some setup. Conversely, on Linux, rust-gdb
should be available
out of the box and rust-lldb
is currently unavailable.
At this time, we don't have a clear recommendation between gdb and lldb. Sometimes, gdb provides more useful introspection than lldb. Other times, gdb hangs and is unable to debug but lldb works. The best recommendation we have at the moment is to start with the tool thats easiest on each platform (lldb for macOS, gdb for Linux) and only bother setting up gdb on macOS if lldb fails. Either way, it's good to have the option to use either tool, and any technique used on one debugger will translate to the other.
First, install gdb
with
$ brew install gdb
Verify that you have gdb
version 8.3 or higher by checking
$ gdb -v
If you don't then you may have to update Homebrew [TODO source] or build gdb
from source [TODO source]
Once you have a recent gdb
version create a file called .gdbinit
in your
home
directory and add the following line to it
set startup-with-shell disable
We're almost done setting up gdb
. Now we just need to code sign the binary.
First, we need to create a new certificate.
- Open up Keychain Access
- Select Certificate Assistant -> Create a Certificate
- Give it a name like
gdb-cert
(You can call it whatever you like just substitute that name in the rest of the instructions) - Set "type" to "Self-signed root"
- Set "Certificate Type" to "Code Signing"
- Select "Let me override defaults"
- Scroll through and select default options for everything (e.g. key size, encryption algorithm)
- For the "Location" option choose "System"
- Restart the machine (it may work without the restart but the Internet recommends it)
After we've made the certificate we need to make an entitlements file (these are formatted as XML).
cat gdb.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.cs.disable-executable-page-protection</key>
<true/>
<key>com.apple.security.cs.debugger</key>
<true/>
<key>com.apple.security.get-task-allow</key>
<true/>
</dict>
</plist>
Once you have an entitlements file (example name is gdb.xml
) all you have to
do is
codesign --entitlements gdb.xml -fs gdb-cert /usr/local/bin/gdb
We are finally ready to start debugging!
First, we need to see what binaries need to be debugged. Adding -v
to Cargo
commands should give the neccessary info. After running for example
cargo test -v
Running `/Users/Test/github/sqlparser/target/debug/deps/sqlparser-726083e10aabeebf`
The path after "Running" is the binary - to validate that just run
$ /path/to/binary
We can now use rust-gdb
(or rust-lldb
) by simply doing
$ rust-gdb /path/to/binary
There's a few more wrinkles / tips and tricks for running unit tests.
The Rust test runner (what we get when we call cargo test
) defaults to
spawning multiple unit tests in parallel. We can use the RUST_TEST_THREADS
environment variable to limit it to one thread which is easier to debug.
$ RUST_TEST_THREADS=1 rust-gdb /path/to/binary
Finally, adding breakpoints can make the debugger execution really slow it (chalk it up to Rust unoptimized builds + debugger overhead maybe?) We can pass the name of the test we want to run as a command line arg
$ RUST_TEST_THREADS=1 rust-gdb --args /path/to/binary <test_name>
The corresponding command for lldb
is:
$ RUST_TEST_THREADS=1 rust-lldb -- /path/to/binary <test_name>
In general, GDB and LLDB commands have the annoying property of being very similar, but not identical. This page maps commands from one debugger to another. At this point, we are free to do all sorts of things with our debugger - set breakpoints, inspect memory, and step through code execution to name just a few examples.
The rustc guide has a page detailing the state of debugger support in Rust. There's plenty of guides for how to use GDB/LLDB online but most of them focus on C/C++ programs. Beej's Quick Guide to GDB is a good starting point.