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}