mini_backtrace/
lib.rs

1//! This crate provides backtrace support for `no_std` and embedded programs.
2//!
3//! This is done through by compiling LLVM's libunwind with certain flags to remove
4//! all OS dependencies, including libc and any memory allocations.
5//!
6//! # Usage
7//!
8//! ### Setup
9//!
10//! There are two prerequisites for using this crate:
11//!
12//! 1. Unwind tables must be built into the binary, even with unwinding is set to
13//!    abort. This can be done with the `-C force-unwind-tables` flag.
14//!
15//! 2. Several `__eh_frame_*` symbols need to be defined by the linker so that the
16//!    the unwinding tables can be located by libunwind. This can be done by
17//!    including the [`eh_frame.ld`] linker script fragment.
18//!
19//! Both of these can be done by setting `RUSTFLAGS`:
20//!
21//! ```sh
22//! export RUSTFLAGS="-Cforce-unwind-tables -Clink-arg=-Wl,eh_frame.ld"
23//! ```
24//!
25//! Note that these flags also apply to build-dependencies and proc
26//! macros by default. This can be worked around by explicitly
27//! specifying a target when invoking cargo:
28//!
29//! ```sh
30//! # Applies RUSTFLAGS to everything
31//! cargo build
32//!
33//! # Doesn't apply RUSTFLAGS to build dependencies and proc macros
34//! cargo build --target x86_64-unknown-linux-gnu
35//! ```
36//!
37//! [`eh_frame.ld`]: https://github.com/Amanieu/mini-backtrace/blob/master/eh_frame.ld
38//!
39//! ### Capturing backtraces
40//!
41//! Add the `mini-backtrace` crate as a dependency to your program:
42//!
43//! ```toml
44//! [dependencies]
45//! mini-backtrace = "0.1"
46//! ```
47//!
48//! You can capture a backtrace by using the `Backtrace` type which returns a list
49//! of frames as an `ArrayVec` of instruction pointer addresses.
50//!
51//! ```rust
52//! use mini_backtrace::Backtrace;
53//!
54//! // Capture up to 16 frames. This is returned using an ArrayVec that doesn't
55//! // perform any dynamic memory allocation.
56//! let bt = Backtrace::<16>::capture();
57//! println!("Backtrace:");
58//! for frame in bt.frames {
59//!     println!("  {:#x}", frame);
60//! }
61//! if bt.frames_omitted {
62//!     println!(" ... <frames omitted>");
63//! }
64//! ```
65//!
66//! This will output:
67//!
68//! ```text
69//! Backtrace:
70//!   0x5587058c3eb1
71//!   0x5587058c3cdb
72//!   0x5587058c491e
73//!   0x5587058c38b1
74//!   0x5587058daf1a
75//!   0x5587058c3890
76//!   0x5587058c414c
77//! ```
78//!
79//! ### Position-independent code
80//!
81//! If your code is executing at a different address than the one it is linked at
82//! then you will need to fix up the frame pointer addresses to be relative to the
83//! module base address. This can be done with the following function:
84//!
85//! ```rust
86//! fn adjust_for_pic(ip: usize) -> usize {
87//!     extern "C" {
88//!         // Symbol defined by the linker
89//!         static __executable_start: [u8; 0];
90//!     }
91//!     let base = unsafe { __executable_start.as_ptr() as usize };
92//!     ip - base
93//! }
94//! ```
95//!
96//! After post-processing, the output should look like this:
97//!
98//! ```text
99//! Backtrace:
100//!   0x8eb1
101//!   0x8cdb
102//!   0x999e
103//!   0x88b1
104//!   0x1ffba
105//!   0x8890
106//!   0x91cc
107//! ```
108//!
109//! Have a look at `examples/backtrace.rs` for a complete example.
110//!
111//! Note that `adjust_for_pic` should *only* be called for position-independent
112//! binaries. Statically-linked binaries should emit unadjusted addresses so that
113//! the backtraces can be correctly resolved.
114//!
115//! ### Resolving backtraces
116//!
117//! The addresses generated by `Backtrace` can be converted to human-readable
118//! function names, filenames and line numbers by using the `addr2line` tool from
119//! LLVM or binutils with [rustfilt] to demangle Rust symbol names.
120//!
121//! Simply run `addr2line -fipe /path/to/binary | rustfilt` in a terminal and then
122//! paste the addresses from the backtrace:
123//!
124//! ```text
125//! $ llvm-addr2line -fipe target/x86_64-unknown-linux-gnu/debug/examples/backtrace | rustfilt
126//!   0x8ed1
127//!   0x8ea6
128//!   0x8e96
129//!   0x8cdb
130//!   0x99be
131//!   0x88b1
132//!   0x1ffda
133//!   0x8890
134//!   0x91ec
135//! backtrace::bar at /home/amanieu/code/mini-backtrace/examples/backtrace.rs:15
136//! backtrace::foo at /home/amanieu/code/mini-backtrace/examples/backtrace.rs:10
137//! backtrace::main at /home/amanieu/code/mini-backtrace/examples/backtrace.rs:5
138//! 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
139//! 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
140//! 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
141//! std::panicking::try at /rustc/676ee14729462585b969bbc52f32c307403f4126/library/std/src/panicking.rs:344
142//!  (inlined by) std::panic::catch_unwind at /rustc/676ee14729462585b969bbc52f32c307403f4126/library/std/src/panic.rs:431
143//!  (inlined by) std::rt::lang_start_internal at /rustc/676ee14729462585b969bbc52f32c307403f4126/library/std/src/rt.rs:34
144//! 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
145//! main at ??:0
146//! ```
147//!
148//! [rustfilt]: https://github.com/luser/rustfilt
149//!
150//! ### Backtraces from signal/interrupt handlers
151//!
152//! The libunwind unwinder used by this crate is usually unable to unwind past
153//! signal handler or interrupt handler frames. Instead, you can use
154//! `Backtrace::capture_from_context` and pass in the register state at the point
155//! where the exception occurred. In a signal handler this can be obtained through
156//! the `uc_mcontext` field of `ucontext_t`.
157//!
158//! This is currently only implemented for:
159//! - AArch64
160//! - RISC-V (RV32 & RV64)
161
162#![no_std]
163
164use arrayvec::ArrayVec;
165use core::mem::MaybeUninit;
166
167#[allow(non_upper_case_globals)]
168#[allow(non_camel_case_types)]
169#[allow(non_snake_case)]
170#[allow(dead_code)]
171mod uw {
172    include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
173}
174
175cfg_if::cfg_if! {
176    if #[cfg(target_arch = "aarch64")] {
177        mod aarch64;
178        pub use aarch64::Context;
179    } else if #[cfg(any(target_arch = "riscv64", target_arch = "riscv32"))] {
180        mod riscv;
181        pub use riscv::Context;
182    }
183}
184
185/// A backtrace consisting of a list of instruction pointer addresses.
186///
187/// The backtrace does not allocate any memory, which allows it to be used in
188/// environments where dynamic allocation cannot be used such as signal handlers
189/// or interrupt handlers.
190///
191/// The `N` generic constant controls the maximum number of entries that should
192/// be included in the backtrace. Usually 16 frames are enough to get sufficient
193/// context from a crash.
194#[derive(Clone, Debug, Default)]
195pub struct Backtrace<const N: usize> {
196    /// List of instruction pointer addresses in each frame, from most recent to
197    /// oldest.
198    ///
199    /// These are not precise return address: the addresses are adjusted so that
200    /// they point within the bounds of the caller function. This avoids issues
201    /// when a call instruction is the last instruction in a function, which
202    /// would otherwise result in a return address pointing at the start of the
203    /// next function.
204    pub frames: ArrayVec<usize, N>,
205
206    /// Whether any frames have been omitted due to exceeding the capacity of
207    /// the `ArrayVec`.
208    pub frames_omitted: bool,
209}
210
211impl<const N: usize> Backtrace<N> {
212    /// Captures a backtrace from the current call point.
213    ///
214    /// The first frame of the backtrace is the caller of `Backtrace::capture`.
215    #[inline(never)]
216    pub fn capture() -> Self {
217        unsafe {
218            let mut unw_context = MaybeUninit::uninit();
219            let mut unw_cursor = MaybeUninit::uninit();
220            uw::unw_getcontext(unw_context.as_mut_ptr());
221            uw::unw_init_local(unw_cursor.as_mut_ptr(), unw_context.as_mut_ptr());
222
223            let mut result = Self::default();
224            result.fill_from_cursor(unw_cursor.as_mut_ptr());
225            result
226        }
227    }
228
229    /// Captures a backtrace from the given register context.
230    ///
231    /// The first frame of the backtrace is the instruction pointer address in
232    /// the register context.
233    ///
234    /// This function is useful for capturing backtraces from signal handlers
235    /// since the unwinder may not be able to unwind past the signal frame.
236    ///
237    /// If no unwinding information is found for the instruction pointer address
238    /// in the context then `None` is returned.
239    #[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
240    pub fn capture_from_context(ctx: &Context) -> Option<Self> {
241        unsafe {
242            let mut unw_context = MaybeUninit::uninit();
243            let mut unw_cursor = MaybeUninit::uninit();
244            uw::unw_getcontext(unw_context.as_mut_ptr());
245            uw::unw_init_local(unw_cursor.as_mut_ptr(), unw_context.as_mut_ptr());
246
247            // Apply the register state to the cursor.
248            ctx.apply(unw_cursor.as_mut_ptr());
249
250            // Check if we actually have unwind info for the fault address. We
251            // don't generate a backtrace if the fault happened outside our
252            // executable.
253            let mut unw_proc_info = MaybeUninit::uninit();
254            if uw::unw_get_proc_info(unw_cursor.as_mut_ptr(), unw_proc_info.as_mut_ptr())
255                != uw::UNW_ESUCCESS
256            {
257                return None;
258            }
259
260            // Add the instruction pointer address from the context as the first
261            // frame of the backtrace.
262            let mut result = Self::default();
263            result.frames.push(ctx.ip());
264            result.fill_from_cursor(unw_cursor.as_mut_ptr());
265            Some(result)
266        }
267    }
268
269    unsafe fn fill_from_cursor(&mut self, cursor: *mut uw::unw_cursor_t) {
270        while uw::unw_step(cursor) > 0 {
271            let mut ip = 0;
272            uw::unw_get_reg(cursor, uw::UNW_REG_IP, &mut ip);
273
274            // Adjust the IP to point within the function symbol. This should
275            // only be done if the frame is not a signal frame.
276            let is_signal_frame = uw::unw_is_signal_frame(cursor) > 0;
277            if !is_signal_frame {
278                ip -= 1;
279            }
280
281            if self.frames.try_push(ip).is_err() {
282                self.frames_omitted = true;
283                break;
284            }
285        }
286    }
287}
288
289#[test]
290fn capture() {
291    let bt = Backtrace::<16>::capture();
292    assert!(bt.frames.len() > 1);
293}