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