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 or Debug 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! and eprintln! macros delegates to print!(), which calls std::io::_print, which calls print_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 locked stdout increases runtime in debug (to ~1000ms) but lowers it significantly in release mode (to ~650ms).
  • Wrapping a BufWriter around the locked stdout 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.

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

LLDB

RR

RD