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
8//! Printing of backtraces. This is adapted from the standard library.
9
10use core::fmt;
11
12use crate::backtrace::BacktraceStyle;
13use crate::backtrace::symbolize::{Symbol, SymbolName};
14
15const HEX_WIDTH: usize = 2 + 2 * size_of::<usize>();
16
17/// A formatter for backtraces.
18///
19/// This type can be used to print a backtrace regardless of where the backtrace
20/// itself comes from. If you have a `Backtrace` type then its `Debug`
21/// implementation already uses this printing format.
22pub struct BacktraceFmt<'a, 'b> {
23 fmt: &'a mut fmt::Formatter<'b>,
24 frame_index: usize,
25 format: BacktraceStyle,
26}
27impl<'a, 'b> BacktraceFmt<'a, 'b> {
28 /// Create a new `BacktraceFmt` which will write output to the provided
29 /// `fmt`.
30 ///
31 /// The `format` argument will control the style in which the backtrace is
32 /// printed, and the `print_path` argument will be used to print the
33 /// `BytesOrWideString` instances of filenames. This type itself doesn't do
34 /// any printing of filenames, but this callback is required to do so.
35 pub fn new(fmt: &'a mut fmt::Formatter<'b>, format: BacktraceStyle) -> Self {
36 BacktraceFmt {
37 fmt,
38 frame_index: 0,
39 format,
40 }
41 }
42
43 /// Adds a frame to the backtrace output.
44 ///
45 /// This commit returns an RAII instance of a `BacktraceFrameFmt` which can be used
46 /// to actually print a frame, and on destruction it will increment the
47 /// frame counter.
48 pub fn frame(&mut self) -> BacktraceFrameFmt<'_, 'a, 'b> {
49 BacktraceFrameFmt {
50 fmt: self,
51 symbol_index: 0,
52 }
53 }
54
55 /// Return the inner formatter.
56 ///
57 /// This is used for writing custom information between frames with `write!` and `writeln!`,
58 /// and won't increment the `frame_index` unlike the `frame` method.
59 pub fn formatter(&mut self) -> &mut fmt::Formatter<'b> {
60 self.fmt
61 }
62}
63
64/// A formatter for just one frame of a backtrace.
65///
66/// This type is created by the `BacktraceFmt::frame` function.
67pub struct BacktraceFrameFmt<'fmt, 'a, 'b> {
68 fmt: &'fmt mut BacktraceFmt<'a, 'b>,
69 symbol_index: usize,
70}
71
72impl BacktraceFrameFmt<'_, '_, '_> {
73 /// Prints a symbol with this frame formatter.
74 pub fn print_symbol(&mut self, frame_ip: usize, sym: Symbol<'_>) -> fmt::Result {
75 self.print_raw_with_column(
76 frame_ip,
77 sym.name(),
78 sym.filename(),
79 sym.lineno(),
80 sym.colno(),
81 )
82 }
83
84 /// Print a raw (read un-symbolized) address with this frame formatter. A raw address
85 /// will just show up as the address and `<unknown>` and should be used in places where we
86 /// couldn't find any symbol information for an address.
87 pub fn print_raw(&mut self, frame_ip: usize) -> fmt::Result {
88 self.print_raw_with_column(frame_ip, None, None, None, None)
89 }
90
91 fn print_raw_with_column(
92 &mut self,
93 frame_ip: usize,
94 symbol_name: Option<SymbolName<'_>>,
95 filename: Option<&str>,
96 lineno: Option<u32>,
97 colno: Option<u32>,
98 ) -> fmt::Result {
99 self.print_raw_generic(frame_ip, symbol_name, filename, lineno, colno)?;
100 self.symbol_index += 1;
101 Ok(())
102 }
103
104 // #[allow(unused_mut)]
105 fn print_raw_generic(
106 &mut self,
107 frame_ip: usize,
108 symbol_name: Option<SymbolName<'_>>,
109 filename: Option<&str>,
110 lineno: Option<u32>,
111 colno: Option<u32>,
112 ) -> fmt::Result {
113 // No need to print "null" frames, it basically just means that the
114 // system backtrace was a bit eager to trace back super far.
115 if let BacktraceStyle::Short = self.fmt.format
116 && frame_ip == 0
117 {
118 return Ok(());
119 }
120
121 // Print the index of the frame as well as the optional instruction
122 // pointer of the frame. If we're beyond the first symbol of this frame
123 // though we just print appropriate whitespace.
124 if self.symbol_index == 0 {
125 write!(self.fmt.fmt, "{:4}: ", self.fmt.frame_index)?;
126
127 // Print the instruction pointer. If the symbol name is None we always print
128 // the address. Those weird frames don't happen that often and are always the most
129 // interesting in a backtrace, so we need to make sure to print at least the address so
130 // we have somewhere to start investigating!
131 if self.fmt.format == BacktraceStyle::Full || symbol_name.is_some() {
132 write!(self.fmt.fmt, "{frame_ip:HEX_WIDTH$x?} - ")?;
133 }
134 } else {
135 write!(self.fmt.fmt, " ")?;
136 if let BacktraceStyle::Full = self.fmt.format {
137 write!(self.fmt.fmt, "{:1$}", "", HEX_WIDTH + 3)?;
138 }
139 }
140
141 // Next up write out the symbol name, using the alternate formatting for
142 // more information if we're a full backtrace. Here we also handle
143 // symbols which don't have a name,
144 match (symbol_name, &self.fmt.format) {
145 (Some(name), BacktraceStyle::Short) => write!(self.fmt.fmt, "{name:#}")?,
146 (Some(name), BacktraceStyle::Full) => write!(self.fmt.fmt, "{name}")?,
147 (None, _) => write!(self.fmt.fmt, "<unknown>")?,
148 }
149 self.fmt.fmt.write_str("\n")?;
150
151 // And last up, print out the filename/line number if they're available.
152 if let (Some(file), Some(line)) = (filename, lineno) {
153 self.print_fileline(file, line, colno)?;
154 }
155
156 Ok(())
157 }
158
159 fn print_fileline(&mut self, file: &str, line: u32, colno: Option<u32>) -> fmt::Result {
160 // Filename/line are printed on lines under the symbol name, so print
161 // some appropriate whitespace to sort of right-align ourselves.
162 if let BacktraceStyle::Full = self.fmt.format {
163 write!(self.fmt.fmt, "{:1$}", "", HEX_WIDTH)?;
164 }
165 write!(self.fmt.fmt, " at ")?;
166
167 // Print the filename and line number.
168 write!(self.fmt.fmt, "at {file}")?;
169 write!(self.fmt.fmt, ":{line}")?;
170
171 // Add column number, if available.
172 if let Some(colno) = colno {
173 write!(self.fmt.fmt, ":{colno}")?;
174 }
175
176 writeln!(self.fmt.fmt)?;
177 Ok(())
178 }
179}
180
181impl Drop for BacktraceFrameFmt<'_, '_, '_> {
182 fn drop(&mut self) {
183 self.fmt.frame_index += 1;
184 }
185}