vexide_core/
backtrace.rs

1//! Support for capturing stack backtraces.
2//!
3//! This module contains the support for capturing a stack backtrace through
4//! the [`Backtrace`] type. Backtraces are helpful to attach to errors,
5//! containing information that can be used to get a chain of where an error
6//! was created.
7//!
8//! # Platform Support
9//!
10//! The [`Backtrace`] API is only functional on the `armv7a-vex-v5` platform
11//! target. At the moment, this target only platform that vexide supports,
12//! however this may change in the future.
13//!
14//! Additionally, backtraces will be unsupported if vexide is compiled without
15//! the `unwind` feature.
16
17use alloc::vec::Vec;
18use core::{ffi::c_void, fmt::Display};
19
20#[cfg(all(target_vendor = "vex", feature = "backtraces"))]
21use vex_libunwind::{registers, UnwindContext, UnwindCursor, UnwindError};
22
23/// A captured stack backtrace.
24///
25/// This type stores the backtrace of a captured stack at a certain point in
26/// time. The backtrace is represented as a list of instruction pointers.
27///
28/// # Platform Support
29///
30/// The [`Backtrace`] API is only functional on the `armv7a-vex-v5` platform
31/// target. At the moment, this target only platform that vexide supports,
32/// however this may change in the future.
33///
34/// Additionally, backtraces will be unsupported if vexide is compiled without
35/// the `unwind` feature.
36///
37/// # Example
38///
39/// ```
40/// let backtrace = Backtrace::capture();
41/// println!("{backtrace}");
42/// ```
43///
44/// # Symbolication
45///
46/// The number stored in each frame is not particularly meaningful to humans on its own.
47/// Using a tool such as `llvm-symbolizer` or `addr2line`, it can be turned into
48/// a function name and line number to show what functions were being run at
49/// the time of the backtrace's capture.
50///
51/// ```terminal
52/// $ llvm-symbolizer -p -e ./target/armv7a-vex-v5/debug/program_name 0x380217b 0x380209b
53/// my_function at /path/to/project/src/main.rs:30:14
54///
55/// main at /path/to/project/src/main.rs:21:9
56/// ```
57#[derive(Debug, Clone, PartialEq, Eq)]
58pub struct Backtrace {
59    /// The instruction pointers of each frame in the backtrace.
60    pub frames: Vec<*const c_void>,
61}
62
63impl Backtrace {
64    /// Captures a backtrace at the current point of execution.
65    ///
66    /// If a backtrace could not be captured, an empty backtrace is returned.
67    ///
68    /// # Platform Support
69    ///
70    /// Backtraces will be empty on non-vex targets (e.g. WebAssembly) or when
71    /// the `unwind` feature is disabled.
72    #[allow(clippy::inline_always)]
73    #[inline(always)] // Inlining keeps this function from appearing in backtraces
74    #[allow(clippy::missing_const_for_fn)]
75    #[must_use]
76    pub fn capture() -> Self {
77        #[cfg(all(target_vendor = "vex", feature = "backtraces"))]
78        return Self::try_capture().unwrap_or(Self { frames: Vec::new() });
79
80        #[cfg(not(all(target_vendor = "vex", feature = "backtraces")))]
81        return Self { frames: Vec::new() };
82    }
83
84    /// Captures a backtrace at the current point of execution,
85    /// returning an error if the backtrace fails to capture.
86    ///
87    /// # Platform Support
88    ///
89    /// See [`Backtrace::capture`].
90    ///
91    /// # Errors
92    ///
93    /// This function errors when the program's unwind info is corrupted.
94    #[inline(never)] // Make sure there's always a frame to remove
95    #[cfg(all(target_vendor = "vex", feature = "backtraces"))]
96    pub fn try_capture() -> Result<Self, UnwindError> {
97        let context = UnwindContext::new()?;
98        let mut cursor = UnwindCursor::new(&context)?;
99
100        let mut frames = Vec::new();
101
102        // Procedure based on mini_backtrace crate.
103
104        // Step once before taking the backtrace to skip the current frame.
105        while cursor.step()? {
106            let mut instruction_pointer = cursor.register(registers::UNW_REG_IP)?;
107
108            // Adjust IP to point inside the function — this improves symbolization quality.
109            if !cursor.is_signal_frame()? {
110                instruction_pointer -= 1;
111            }
112
113            frames.push(instruction_pointer as *const c_void);
114        }
115
116        Ok(Self { frames })
117    }
118}
119
120impl Display for Backtrace {
121    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
122        writeln!(f, "stack backtrace:")?;
123        for (i, frame) in self.frames.iter().enumerate() {
124            writeln!(f, "{i:>3}: {frame:?}")?;
125        }
126        write!(
127            f,
128            "note: Use a symbolizer to convert stack frames to human-readable function names."
129        )?;
130        Ok(())
131    }
132}