Debugging Rust
The println!
and eprintln!
macros
There are two main output streams in Linux (and other OSs), standard output (stdout) and standard error (stderr). Error messages, like the ones you show, are printed to standard error. The classic redirection operator (command > file) only redirects standard output, so standard error is still shown on the terminal. To redirect stderr as well, you have a few choices:
Redirect stdout to one file and stderr to another file:
command >| stdout.txt 2>| stderr.txt
Redirect stdout to a file (>out), and then redirect stderr to stdout (2>&1):
command >out 2>&1
Redirect both to a file (this isn't supported by all shells, bash and zsh support it, for example, but sh and ksh do not):
command &> out
zsh: setopt noclobber
posix >| and 2>|
tail -F
fish behaviour igaray@monolith ~> echo "test2" >| test.txt test.txt: command not found fish: echo "test2" >| test.txt ^
-
Beware the formatting cost. Turning large data structures into their
Display
orDebug
string representations may be costly. -
Beware the implicit lock.
-
If you need to output a large amount, take an explicit lock on
stdout
. -
The
println!
andeprintln!
macros delegates toprint!()
, which callsstd::io::_print
, which callsprint_to
by passing&LOCAL_STDOUT
as an argument.LOCAL_STDOUT
is defined like this:
-
#![allow(unused)] fn main() { /// Stdout used by print! and println! macros thread_local! { static LOCAL_STDOUT: RefCell<Option<Box<Write + Send>>> = { RefCell::new(None) } } }
- On an older system (2010 MBP 2.4GHz i5 520M) in debug I get ~900ms for a million
println!
in a loop piping the output to/dev/null
. - Switching to
write!
on a lockedstdout
increases runtime in debug (to ~1000ms) but lowers it significantly in release mode (to ~650ms). - Wrapping a
BufWriter
around the lockedstdout
increases the runtime even more in debug mode (to ~2000ms), but makes it essentially disappear in release mode (~70ms).
The dbg!
macro
Prints and returns the value of a given expression for quick and dirty debugging.
-
Works by using the
Debug
implementation. -
Prints to
stderr
. -
Takes ownership of params and returns them, if a move is not desired pass a reference.
-
Works in release builds.
-
Using this macro without any argument will print the current file and line number.
Backtraces
- Set
RUST_BACKTRACE=1
to capture and print backtraces in case of panic. - Try not to need backtraces in production and use them as a development tool to debug unexpected error cases.
- It's ok to enable backtraces in production, when error construction doesn’t execute costly logic to collect and store backtraces. In addition there are cases where the
libbacktrace
internals run into pathologically slow edge cases, inwhich you may want the ability to turn backtraces off. - You can
env::set_var("RUST_BACKTRACE", "1")
at specific entry points to always have backtraces in output. - If you need to programatically manipulate backtraces, use:
GDB
Setup
Basic use
Threads
pwndbg
pwndbg
make GDB more visually friendly.
- https://github.com/pwndbg/pwndbg
- https://blog.xpnsec.com/pwndbg/
- https://www.ins1gn1a.com/basics-of-gdb-and-pwndbg/
LLDB
VisualStudio Code + CodeLLDB
an extension in the Visual Studio Code.
RR
RD
GDB
- Debugging with GDB - Supported Languages: Rust
- Writing an OS in Rust - Set Up GDB
- 2014 Debugging Rust with gdb
- 2015 rust-gdb. rust-lldb.
- 2020 How to debug Rust via GDB
- 2021 Debugging Rust apps with GDB
LLDB
- 2018 Debugging Rust programs with lldb on MacOS
- 2019 Debugging Rust With LLDB
- 2020 A Future for Rust Debugging
- 2020 The Soul of a New Debugger
RR
- RR Project
- 2016 Debugging & Rust
- 2017 Using rust with rr
- 2021 Instant replay: Debugging C and C++ programs with rr