Next Generation WASM Microkernel Operating System
1// Copyright 2025 Jonas Kruckenberg
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// http://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8mod print;
9mod symbolize;
10
11use crate::backtrace::print::BacktraceFmt;
12use crate::mem::VirtualAddress;
13use arrayvec::ArrayVec;
14use core::str::FromStr;
15use core::{fmt, slice};
16use fallible_iterator::FallibleIterator;
17use loader_api::BootInfo;
18use spin::OnceLock;
19use symbolize::SymbolizeContext;
20use unwind2::FrameIter;
21
22static BACKTRACE_INFO: OnceLock<BacktraceInfo> = OnceLock::new();
23
24#[cold]
25pub fn init(boot_info: &'static BootInfo, backtrace_style: BacktraceStyle) {
26 BACKTRACE_INFO.get_or_init(|| BacktraceInfo::new(boot_info, backtrace_style));
27}
28
29/// Information about the kernel required to build a backtrace
30struct BacktraceInfo {
31 /// The base virtual address of the kernel ELF. ELF debug info expects zero-based addresses,
32 /// but the kernel is located at some address in the higher half. This offset is used to convert
33 /// between the two.
34 kernel_virt_base: u64,
35 /// The memory of our own ELF
36 elf: &'static [u8],
37 /// The actual state required for converting addresses into symbols. This is *very* heavy to
38 /// compute though, so we only construct it lazily in [`BacktraceInfo::symbolize_context`].
39 symbolize_context: OnceLock<SymbolizeContext<'static>>,
40 backtrace_style: BacktraceStyle,
41}
42
43#[derive(Debug)]
44pub struct UnknownBacktraceStyleError;
45
46#[derive(Debug, Default, Copy, Clone, PartialEq)]
47pub enum BacktraceStyle {
48 #[default]
49 Short,
50 Full,
51}
52
53#[derive(Clone)]
54pub struct Backtrace<'a, const MAX_FRAMES: usize> {
55 symbolize_ctx: Option<&'a SymbolizeContext<'static>>,
56 pub frames: ArrayVec<usize, MAX_FRAMES>,
57 pub frames_omitted: bool,
58 style: BacktraceStyle,
59}
60
61// === impl BacktraceInfo ===
62
63impl BacktraceInfo {
64 fn new(boot_info: &'static BootInfo, backtrace_style: BacktraceStyle) -> Self {
65 BacktraceInfo {
66 kernel_virt_base: boot_info.kernel_virt.start as u64,
67 // Safety: we have to trust the loaders BootInfo here
68 elf: unsafe {
69 let base = boot_info
70 .physical_address_offset
71 .checked_add(boot_info.kernel_phys.start)
72 .unwrap() as *const u8;
73
74 slice::from_raw_parts(
75 base,
76 boot_info
77 .kernel_phys
78 .end
79 .checked_sub(boot_info.kernel_phys.start)
80 .unwrap(),
81 )
82 },
83 symbolize_context: OnceLock::new(),
84 backtrace_style,
85 }
86 }
87
88 fn symbolize_context(&self) -> &SymbolizeContext<'static> {
89 self.symbolize_context.get_or_init(|| {
90 tracing::debug!("Setting up symbolize context...");
91
92 let elf = xmas_elf::ElfFile::new(self.elf).unwrap();
93 SymbolizeContext::new(elf, self.kernel_virt_base).unwrap()
94 })
95 }
96}
97
98// === impl Backtrace ===
99
100impl<const MAX_FRAMES: usize> Backtrace<'_, MAX_FRAMES> {
101 /// Captures a backtrace at the callsite of this function, returning an owned representation.
102 ///
103 /// The returned object is almost entirely self-contained. It can be cloned, or send to other threads.
104 ///
105 /// Note that this step is quite cheap, contrary to the `Backtrace` implementation in the standard
106 /// library this resolves the symbols (the expensive step) lazily, so this struct can be constructed
107 /// in performance sensitive codepaths and only later resolved.
108 ///
109 /// # Errors
110 ///
111 /// Returns the underlying [`unwind2::Error`] if walking the stack fails.
112 #[inline]
113 pub fn capture() -> Result<Self, unwind2::Error> {
114 Self::new_inner(FrameIter::new())
115 }
116
117 /// Constructs a backtrace from the provided register context, returning an owned representation.
118 ///
119 /// The returned object is almost entirely self-contained. It can be cloned, or send to other threads.
120 ///
121 /// Note that this step is quite cheap, contrary to the `Backtrace` implementation in the standard
122 /// library this resolves the symbols (the expensive step) lazily, so this struct can be constructed
123 /// in performance sensitive codepaths and only later resolved.
124 ///
125 /// # Errors
126 ///
127 /// Returns the underlying [`unwind2::Error`] if walking the stack fails.
128 #[inline]
129 pub fn from_registers(
130 regs: unwind2::Registers,
131 pc: VirtualAddress,
132 ) -> Result<Self, unwind2::Error> {
133 let iter = FrameIter::from_registers(regs, pc.get());
134 Self::new_inner(iter)
135 }
136
137 fn new_inner(iter: FrameIter) -> Result<Self, unwind2::Error> {
138 let mut frames = ArrayVec::new();
139
140 let mut iter = iter.take(MAX_FRAMES);
141
142 while let Some(frame) = iter.next()? {
143 frames.try_push(frame.ip()).unwrap();
144 }
145 let frames_omitted = iter.next()?.is_some();
146
147 Ok(Self {
148 symbolize_ctx: BACKTRACE_INFO.get().map(|info| info.symbolize_context()),
149 frames,
150 frames_omitted,
151 style: BACKTRACE_INFO
152 .get()
153 .map(|info| info.backtrace_style)
154 .unwrap_or_default(),
155 })
156 }
157}
158
159impl<const MAX_FRAMES: usize> fmt::Display for Backtrace<'_, MAX_FRAMES> {
160 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
161 writeln!(f, "stack backtrace:")?;
162
163 let style = if f.alternate() {
164 BacktraceStyle::Full
165 } else {
166 self.style
167 };
168
169 let mut bt_fmt = BacktraceFmt::new(f, style);
170
171 let mut omitted_count: usize = 0;
172 let mut first_omit = true;
173 // If we're using a short backtrace, ignore all frames until we're told to start printing.
174 let mut print = style != BacktraceStyle::Short;
175
176 for ip in &self.frames {
177 let mut any = false; // did we print any symbols?
178
179 // if the symbolication state isn't setup, yet we can't print symbols the addresses will have
180 // to suffice...
181 if let Some(symbolize_ctx) = self.symbolize_ctx {
182 let mut syms = symbolize_ctx.resolve_unsynchronized(*ip as u64).unwrap();
183
184 while let Some(sym) = syms.next().unwrap() {
185 any = true;
186 // `__rust_end_short_backtrace` means we are done hiding symbols
187 // for now. Print until we see `__rust_begin_short_backtrace`.
188 if style == BacktraceStyle::Short {
189 if let Some(sym) = sym.name().map(|s| s.as_raw_str()) {
190 if sym.contains("__rust_end_short_backtrace") {
191 print = true;
192 break;
193 }
194 if print && sym.contains("__rust_begin_short_backtrace") {
195 print = false;
196 break;
197 }
198 if !print {
199 omitted_count += 1;
200 }
201 }
202 }
203
204 if print {
205 if omitted_count > 0 {
206 debug_assert!(style == BacktraceStyle::Short);
207 // only print the message between the middle of frames
208 if !first_omit {
209 let _ = writeln!(
210 bt_fmt.formatter(),
211 " [... omitted {} frame{} ...]",
212 omitted_count,
213 if omitted_count > 1 { "s" } else { "" }
214 );
215 }
216 first_omit = false;
217 omitted_count = 0;
218 }
219 bt_fmt.frame().print_symbol(*ip, sym)?;
220 }
221 }
222 } else {
223 // no symbolize context always means no symbols
224 any = false;
225 }
226
227 if !any && print {
228 bt_fmt.frame().print_raw(*ip)?;
229 }
230 }
231
232 if style == BacktraceStyle::Short {
233 writeln!(
234 f,
235 "note: Some details are omitted, \
236 run with `backtrace=full` bootarg for a verbose backtrace."
237 )?;
238 }
239 if self.symbolize_ctx.is_none() {
240 writeln!(
241 f,
242 "note: backtrace subsystem wasn't initialized, no symbols were printed."
243 )?;
244 }
245
246 Ok(())
247 }
248}
249
250// === impl BacktraceStyle ===
251
252impl FromStr for BacktraceStyle {
253 type Err = UnknownBacktraceStyleError;
254
255 fn from_str(s: &str) -> Result<Self, Self::Err> {
256 match s {
257 "short" => Ok(BacktraceStyle::Short),
258 "full" => Ok(BacktraceStyle::Full),
259 _ => Err(UnknownBacktraceStyleError),
260 }
261 }
262}
263
264impl fmt::Display for UnknownBacktraceStyleError {
265 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
266 writeln!(f, "unknown backtrace style")
267 }
268}
269
270impl core::error::Error for UnknownBacktraceStyleError {}
271
272/// Fixed frame used to clean the backtrace with `backtrace=short`.
273#[inline(never)]
274pub fn __rust_begin_short_backtrace<F, T>(f: F) -> T
275where
276 F: FnOnce() -> T,
277{
278 let result = f();
279
280 // prevent this frame from being tail-call optimised away
281 core::hint::black_box(());
282
283 result
284}
285
286/// Fixed frame used to clean the backtrace with `backtrace=short`.
287#[inline(never)]
288pub fn __rust_end_short_backtrace<F, T>(f: F) -> T
289where
290 F: FnOnce() -> T,
291{
292 let result = f();
293
294 // prevent this frame from being tail-call optimised away
295 core::hint::black_box(());
296
297 result
298}