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.
There are two prerequisites for using this crate:
-
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. -
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 theeh_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
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
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.
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
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)
Licensed under either of:
- Apache License, Version 2.0, (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
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.