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