Skip to content

Commit 2bb900b

Browse files
authored
Merge pull request #245 from cramertj/fuchsia-bt
Custom backtrace output for Fuchsia
2 parents 36edbfe + c792eb7 commit 2bb900b

File tree

9 files changed

+698
-77
lines changed

9 files changed

+698
-77
lines changed

.github/workflows/main.yml

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -178,17 +178,20 @@ jobs:
178178
run: rustup update stable && rustup default stable && rustup component add rustfmt
179179
- run: cargo fmt --all -- --check
180180

181-
wasm:
182-
name: WebAssembly
181+
build:
182+
name: Build Targets
183183
runs-on: ubuntu-latest
184+
strategy:
185+
matrix:
186+
target: [wasm32-unknown-unknown, x86_64-fuchsia]
184187
steps:
185188
- uses: actions/checkout@master
186189
with:
187190
submodules: true
188191
- name: Install Rust
189192
run: rustup update stable && rustup default stable
190-
- run: rustup target add wasm32-unknown-unknown
191-
- run: cargo build --target wasm32-unknown-unknown
193+
- run: rustup target add ${{ matrix.target }}
194+
- run: cargo build --target ${{ matrix.target }}
192195

193196
msrv:
194197
name: MSRV

Cargo.toml

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,10 @@ cpp_demangle = { default-features = false, version = "0.2.3", optional = true }
3333
addr2line = { version = "0.10.0", optional = true, default-features = false, features = ['std'] }
3434
findshlibs = { version = "0.5.0", optional = true }
3535
memmap = { version = "0.7.0", optional = true }
36-
goblin = { version = "0.0.24", optional = true, default-features = false, features = ['std'] }
36+
goblin = { version = "0.0.24", optional = true, default-features = false, features = ['elf32', 'elf64', 'mach32', 'mach64', 'pe32', 'pe64', 'std'] }
3737

3838
[target.'cfg(windows)'.dependencies]
3939
winapi = { version = "0.3.3", optional = true }
40-
goblin = { version = "0.0.24", optional = true, default-features = false, features = ['pe32', 'pe64'] }
41-
[target.'cfg(target_os = "macos")'.dependencies]
42-
goblin = { version = "0.0.24", optional = true, default-features = false, features = ['mach32', 'mach64'] }
43-
[target.'cfg(not(any(target_os = "macos", windows)))'.dependencies]
44-
goblin = { version = "0.0.24", optional = true, default-features = false, features = ['elf32', 'elf64'] }
4540

4641
# Each feature controls the two phases of finding a backtrace: getting a
4742
# backtrace and then resolving instruction pointers to symbols. The default

crates/backtrace-sys/build.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ fn main() {
1010
if target.contains("msvc") || // libbacktrace isn't used on MSVC windows
1111
target.contains("emscripten") || // no way this will ever compile for emscripten
1212
target.contains("cloudabi") ||
13-
target.contains("wasm32")
13+
target.contains("wasm32") ||
14+
target.contains("fuchsia") // fuchsia uses external out-of-process symbolization
1415
{
1516
println!("cargo:rustc-cfg=empty");
1617
return;

src/capture.rs

Lines changed: 26 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
use crate::{resolve, resolve_frame, trace, Symbol, SymbolName};
1+
use crate::PrintFmt;
2+
use crate::{resolve, resolve_frame, trace, BacktraceFmt, Symbol, SymbolName};
23
use std::ffi::c_void;
34
use std::fmt;
45
use std::path::{Path, PathBuf};
56
use std::prelude::v1::*;
67

78
#[cfg(feature = "serde")]
8-
use serde::{Serialize, Deserialize};
9+
use serde::{Deserialize, Serialize};
910

1011
/// Representation of an owned and self-contained backtrace.
1112
///
@@ -325,66 +326,36 @@ impl BacktraceSymbol {
325326

326327
impl fmt::Debug for Backtrace {
327328
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
328-
write!(fmt, "stack backtrace:")?;
329-
330-
let iter = if fmt.alternate() {
331-
self.frames.iter()
329+
let full = fmt.alternate();
330+
let (frames, style) = if full {
331+
(&self.frames[..], PrintFmt::Full)
332332
} else {
333-
self.frames[self.actual_start_index..].iter()
333+
(&self.frames[self.actual_start_index..], PrintFmt::Short)
334334
};
335335

336-
for (idx, frame) in iter.enumerate() {
337-
// To reduce TCB size in Sgx enclave, we do not want to implement symbol resolution functionality.
338-
// Rather, we can print the offset of the address here, which could be later mapped to
339-
// correct function.
340-
let ip: *mut c_void;
341-
#[cfg(target_env = "sgx")]
342-
{
343-
ip = usize::wrapping_sub(
344-
frame.ip() as _,
345-
std::os::fortanix_sgx::mem::image_base() as _,
346-
) as _;
347-
}
348-
#[cfg(not(target_env = "sgx"))]
349-
{
350-
ip = frame.ip();
351-
}
352-
353-
write!(fmt, "\n{:4}: ", idx)?;
354-
355-
let symbols = match frame.symbols {
356-
Some(ref s) => s,
357-
None => {
358-
write!(fmt, "<unresolved> ({:?})", ip)?;
359-
continue;
336+
// When printing paths we try to strip the cwd if it exists, otherwise
337+
// we just print the path as-is. Note that we also only do this for the
338+
// short format, because if it's full we presumably want to print
339+
// everything.
340+
let cwd = std::env::current_dir();
341+
let mut print_path = move |fmt: &mut fmt::Formatter, path: crate::BytesOrWideString| {
342+
let path = path.into_path_buf();
343+
if !full {
344+
if let Ok(cwd) = &cwd {
345+
if let Ok(suffix) = path.strip_prefix(cwd) {
346+
return fmt::Display::fmt(&suffix.display(), fmt);
347+
}
360348
}
361-
};
362-
if symbols.len() == 0 {
363-
write!(fmt, "<no info> ({:?})", ip)?;
364-
continue;
365349
}
350+
fmt::Display::fmt(&path.display(), fmt)
351+
};
366352

367-
for (idx, symbol) in symbols.iter().enumerate() {
368-
if idx != 0 {
369-
write!(fmt, "\n ")?;
370-
}
371-
372-
if let Some(name) = symbol.name() {
373-
write!(fmt, "{}", name)?;
374-
} else {
375-
write!(fmt, "<unknown>")?;
376-
}
377-
378-
if idx == 0 {
379-
write!(fmt, " ({:?})", ip)?;
380-
}
381-
382-
if let (Some(file), Some(line)) = (symbol.filename(), symbol.lineno()) {
383-
write!(fmt, "\n at {}:{}", file.display(), line)?;
384-
}
385-
}
353+
let mut f = BacktraceFmt::new(fmt, style, &mut print_path);
354+
f.add_context()?;
355+
for frame in frames {
356+
f.frame()?.backtrace_frame(frame)?;
386357
}
387-
358+
f.finish()?;
388359
Ok(())
389360
}
390361
}

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ mod types;
8686
#[cfg(feature = "std")]
8787
pub use crate::symbolize::clear_symbol_cache;
8888

89+
mod print;
90+
pub use print::{BacktraceFmt, PrintFmt};
91+
8992
cfg_if::cfg_if! {
9093
if #[cfg(feature = "std")] {
9194
pub use crate::backtrace::trace;

src/print.rs

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
use crate::BytesOrWideString;
2+
use core::ffi::c_void;
3+
use core::fmt;
4+
5+
const HEX_WIDTH: usize = 2 + 2 * core::mem::size_of::<usize>();
6+
7+
#[cfg(target_os = "fuchsia")]
8+
mod fuchsia;
9+
10+
/// A formatter for backtraces.
11+
pub struct BacktraceFmt<'a, 'b> {
12+
fmt: &'a mut fmt::Formatter<'b>,
13+
frame_index: usize,
14+
format: PrintFmt,
15+
print_path: &'a mut (FnMut(&mut fmt::Formatter, BytesOrWideString) -> fmt::Result + 'b),
16+
}
17+
18+
/// The styles of printing that we can print
19+
pub enum PrintFmt {
20+
/// Prints a terser backtrace which ideally only contains relevant information
21+
Short,
22+
/// Prints a backtrace that contains all possible information
23+
Full,
24+
#[doc(hidden)]
25+
__Nonexhaustive,
26+
}
27+
28+
impl<'a, 'b> BacktraceFmt<'a, 'b> {
29+
/// Create a new `BacktraceFmt` which will write output to the provided `fmt`.
30+
pub fn new(
31+
fmt: &'a mut fmt::Formatter<'b>,
32+
format: PrintFmt,
33+
print_path: &'a mut (FnMut(&mut fmt::Formatter, BytesOrWideString) -> fmt::Result + 'b),
34+
) -> Self {
35+
BacktraceFmt {
36+
fmt,
37+
frame_index: 0,
38+
format,
39+
print_path,
40+
}
41+
}
42+
43+
/// Adds the current shared library context to the output.
44+
///
45+
/// This is required for the resulting frames to be symbolized.
46+
pub fn add_context(&mut self) -> fmt::Result {
47+
self.fmt.write_str("stack backtrace:\n")?;
48+
#[cfg(target_os = "fuchsia")]
49+
fuchsia::print_dso_context(self.fmt)?;
50+
Ok(())
51+
}
52+
53+
/// Adds a frame to the backtrace output.
54+
pub fn frame(&mut self) -> Result<FrameFmt<'_, 'a, 'b>, fmt::Error> {
55+
Ok(FrameFmt {
56+
fmt: self,
57+
symbol_index: 0,
58+
})
59+
}
60+
61+
/// Completes the backtrace output.
62+
pub fn finish(&mut self) -> fmt::Result {
63+
// Currently a no-op-- including this hook to allow for future additions.
64+
Ok(())
65+
}
66+
}
67+
68+
/// A formatter for backtraces.
69+
pub struct FrameFmt<'fmt, 'a, 'b> {
70+
fmt: &'fmt mut BacktraceFmt<'a, 'b>,
71+
symbol_index: usize,
72+
}
73+
74+
impl FrameFmt<'_, '_, '_> {
75+
/// Prints a `BacktraceFrame` with this frame formatter.
76+
///
77+
/// This will recusrively print all `BacktraceSymbol` instances within the
78+
/// `BacktraceFrame`.
79+
#[cfg(feature = "std")]
80+
pub fn backtrace_frame(&mut self, frame: &crate::BacktraceFrame) -> fmt::Result {
81+
let symbols = frame.symbols();
82+
for symbol in symbols {
83+
self.backtrace_symbol(frame, symbol)?;
84+
}
85+
if symbols.is_empty() {
86+
self.print_raw(frame.ip(), None, None, None)?;
87+
}
88+
Ok(())
89+
}
90+
91+
/// Prints a `BacktraceSymbol` within a `BacktraceFrame`.
92+
#[cfg(feature = "std")]
93+
pub fn backtrace_symbol(
94+
&mut self,
95+
frame: &crate::BacktraceFrame,
96+
symbol: &crate::BacktraceSymbol,
97+
) -> fmt::Result {
98+
self.print_raw(
99+
frame.ip(),
100+
symbol.name(),
101+
symbol
102+
.filename()
103+
.and_then(|p| Some(BytesOrWideString::Bytes(p.to_str()?.as_bytes()))),
104+
symbol.lineno(),
105+
)?;
106+
Ok(())
107+
}
108+
109+
/// Prints a raw traced `Frame` and `Symbol`, typically from within the raw
110+
/// callbacks of this crate.
111+
pub fn symbol(&mut self, frame: &crate::Frame, symbol: &crate::Symbol) -> fmt::Result {
112+
self.print_raw(
113+
frame.ip(),
114+
symbol.name(),
115+
symbol.filename_raw(),
116+
symbol.lineno(),
117+
)?;
118+
Ok(())
119+
}
120+
121+
/// Adds a frame to the backtrace output.
122+
pub fn print_raw(
123+
&mut self,
124+
frame_ip: *mut c_void,
125+
symbol_name: Option<crate::SymbolName>,
126+
filename: Option<BytesOrWideString>,
127+
lineno: Option<u32>,
128+
) -> fmt::Result {
129+
if cfg!(target_os = "fuchsia") {
130+
self.print_raw_fuchsia(frame_ip)?;
131+
} else {
132+
self.print_raw_generic(frame_ip, symbol_name, filename, lineno)?;
133+
}
134+
self.symbol_index += 1;
135+
Ok(())
136+
}
137+
138+
#[allow(unused_mut)]
139+
fn print_raw_generic(
140+
&mut self,
141+
mut frame_ip: *mut c_void,
142+
symbol_name: Option<crate::SymbolName>,
143+
filename: Option<BytesOrWideString>,
144+
lineno: Option<u32>,
145+
) -> fmt::Result {
146+
// No need to print "null" frames, it basically just means that the
147+
// system backtrace was a bit eager to trace back super far.
148+
if let PrintFmt::Short = self.fmt.format {
149+
if frame_ip.is_null() {
150+
return Ok(());
151+
}
152+
}
153+
154+
// To reduce TCB size in Sgx enclave, we do not want to implement symbol
155+
// resolution functionality. Rather, we can print the offset of the
156+
// address here, which could be later mapped to correct function.
157+
#[cfg(all(feature = "std", target_env = "sgx"))]
158+
{
159+
frame_ip =
160+
usize::wrapping_sub(frame_ip, std::os::fortanix_sgx::mem::image_base() as _) as _;
161+
}
162+
163+
if self.symbol_index == 0 {
164+
write!(self.fmt.fmt, "{:4}: ", self.fmt.frame_index)?;
165+
if let PrintFmt::Full = self.fmt.format {
166+
write!(self.fmt.fmt, "{:1$?} - ", frame_ip, HEX_WIDTH)?;
167+
}
168+
} else {
169+
write!(self.fmt.fmt, " ")?;
170+
if let PrintFmt::Full = self.fmt.format {
171+
write!(self.fmt.fmt, "{:1$}", "", HEX_WIDTH + 3)?;
172+
}
173+
}
174+
175+
match (symbol_name, &self.fmt.format) {
176+
(Some(name), PrintFmt::Short) => write!(self.fmt.fmt, "{}", name)?,
177+
(Some(name), PrintFmt::Full) => write!(self.fmt.fmt, "{:#}", name)?,
178+
(None, _) | (_, PrintFmt::__Nonexhaustive) => write!(self.fmt.fmt, "<unknown>")?,
179+
}
180+
self.fmt.fmt.write_str("\n")?;
181+
182+
if let (Some(file), Some(line)) = (filename, lineno) {
183+
self.print_fileline(file, line)?;
184+
}
185+
186+
Ok(())
187+
}
188+
189+
fn print_fileline(&mut self, file: BytesOrWideString, line: u32) -> fmt::Result {
190+
match self.fmt.format {
191+
PrintFmt::Full => write!(self.fmt.fmt, "{:1$}", "", HEX_WIDTH)?,
192+
PrintFmt::Short | PrintFmt::__Nonexhaustive => {}
193+
}
194+
write!(self.fmt.fmt, " at ")?;
195+
(self.fmt.print_path)(self.fmt.fmt, file)?;
196+
write!(self.fmt.fmt, ":{}\n", line)?;
197+
Ok(())
198+
}
199+
200+
fn print_raw_fuchsia(&mut self, frame_ip: *mut c_void) -> fmt::Result {
201+
// We only care about the first symbol of a frame
202+
if self.symbol_index == 0 {
203+
self.fmt.fmt.write_str("{{{bt:")?;
204+
write!(self.fmt.fmt, "{}:{:?}", self.fmt.frame_index, frame_ip)?;
205+
self.fmt.fmt.write_str("}}}\n")?;
206+
}
207+
Ok(())
208+
}
209+
}
210+
211+
impl Drop for FrameFmt<'_, '_, '_> {
212+
fn drop(&mut self) {
213+
self.fmt.frame_index += 1;
214+
}
215+
}

0 commit comments

Comments
 (0)