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