#dwarf #backtrace #no-std

no-std mini-backtrace

Backtrace support for no_std and embedded programs

5 releases

0.1.4 May 7, 2023
0.1.3 Sep 25, 2022
0.1.2 Sep 8, 2021
0.1.1 Jun 11, 2021
0.1.0 May 12, 2021

#320 in Embedded development

35 downloads per month

Apache-2.0/MIT

230KB
5K SLoC

GNU Style Assembly 2K SLoC // 0.1% comments C++ 1.5K SLoC // 0.2% comments C 1K SLoC // 0.2% comments Rust 194 SLoC // 0.0% comments Python 83 SLoC // 0.7% comments

mini-backtrace

Crates.io Documentation

This crate provides backtrace support for no_std and embedded programs.

This is done through by compiling LLVM's libunwind with certain flags to remove all OS dependencies, including libc and any memory allocations.

Usage

Setup

There are two prerequisites for using this crate:

  1. Unwind tables must be built into the binary, even with unwinding is set to abort. This can be done with the -C force-unwind-tables flag.

  2. Several __eh_frame_* symbols need to be defined by the linker so that the the unwinding tables can be located by libunwind. This can be done by including the eh_frame.ld linker script fragment.

Both of these can be done by setting RUSTFLAGS:

export RUSTFLAGS="-Cforce-unwind-tables -Clink-arg=-Wl,eh_frame.ld"

Note that these flags also apply to build-dependencies and proc macros by default. This can be worked around by explicitly specifying a target when invoking cargo:

# Applies RUSTFLAGS to everything
cargo build

# Doesn't apply RUSTFLAGS to build dependencies and proc macros
cargo build --target x86_64-unknown-linux-gnu

Capturing backtraces

Add the mini-backtrace crate as a dependency to your program:

[dependencies]
mini-backtrace = "0.1"

You can capture a backtrace by using the Backtrace type which returns a list of frames as an ArrayVec of instruction pointer addresses.

use mini_backtrace::Backtrace;

// Capture up to 16 frames. This is returned using an ArrayVec that doesn't
// perform any dynamic memory allocation.
let bt = Backtrace::<16>::capture();
println!("Backtrace:");
for frame in bt.frames {
    println!("  {:#x}", frame);
}
if bt.frames_omitted {
    println!(" ... <frames omitted>");
}

This will output:

Backtrace:
  0x5587058c3eb1
  0x5587058c3cdb
  0x5587058c491e
  0x5587058c38b1
  0x5587058daf1a
  0x5587058c3890
  0x5587058c414c

Position-independent code

If your code is executing at a different address than the one it is linked at then you will need to fix up the frame pointer addresses to be relative to the module base address. This can be done with the following function:

fn adjust_for_pic(ip: usize) -> usize {
    extern "C" {
        // Symbol defined by the linker
        static __executable_start: [u8; 0];
    }
    let base = unsafe { __executable_start.as_ptr() as usize };
    ip - base
}

After post-processing, the output should look like this:

Backtrace:
  0x8eb1
  0x8cdb
  0x999e
  0x88b1
  0x1ffba
  0x8890
  0x91cc

Have a look at examples/backtrace.rs for a complete example.

Note that adjust_for_pic should only be called for position-independent binaries. Statically-linked binaries should emit unadjusted addresses so that the backtraces can be correctly resolved.

Resolving backtraces

The addresses generated by Backtrace can be converted to human-readable function names, filenames and line numbers by using the addr2line tool from LLVM or binutils with rustfilt to demangle Rust symbol names.

Simply run addr2line -fipe /path/to/binary | rustfilt in a terminal and then paste the addresses from the backtrace:

$ llvm-addr2line -fipe target/x86_64-unknown-linux-gnu/debug/examples/backtrace | rustfilt
  0x8ed1
  0x8ea6
  0x8e96
  0x8cdb
  0x99be
  0x88b1
  0x1ffda
  0x8890
  0x91ec
backtrace::bar at /home/amanieu/code/mini-backtrace/examples/backtrace.rs:15
backtrace::foo at /home/amanieu/code/mini-backtrace/examples/backtrace.rs:10
backtrace::main at /home/amanieu/code/mini-backtrace/examples/backtrace.rs:5
core::ops::function::FnOnce::call_once at /home/amanieu/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:227
std::sys_common::backtrace::__rust_begin_short_backtrace at /home/amanieu/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys_common/backtrace.rs:128
std::rt::lang_start::{{closure}} at /home/amanieu/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:49
std::panicking::try at /rustc/676ee14729462585b969bbc52f32c307403f4126/library/std/src/panicking.rs:344
 (inlined by) std::panic::catch_unwind at /rustc/676ee14729462585b969bbc52f32c307403f4126/library/std/src/panic.rs:431
 (inlined by) std::rt::lang_start_internal at /rustc/676ee14729462585b969bbc52f32c307403f4126/library/std/src/rt.rs:34
std::rt::lang_start at /home/amanieu/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:48
main at ??:0

Backtraces from signal/interrupt handlers

The libunwind unwinder used by this crate is usually unable to unwind past signal handler or interrupt handler frames. Instead, you can use Backtrace::capture_from_context and pass in the register state at the point where the exception occurred. In a signal handler this can be obtained through the uc_mcontext field of ucontext_t.

This is currently only implemented for:

  • AArch64
  • RISC-V (RV32 & RV64)

Change log

License

Licensed under either of:

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Dependencies

~0–2.8MB
~47K SLoC