Skip to content

Custom backtrace output for Fuchsia #245

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Sep 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -178,17 +178,20 @@ jobs:
run: rustup update stable && rustup default stable && rustup component add rustfmt
- run: cargo fmt --all -- --check

wasm:
name: WebAssembly
build:
name: Build Targets
runs-on: ubuntu-latest
strategy:
matrix:
target: [wasm32-unknown-unknown, x86_64-fuchsia]
steps:
- uses: actions/checkout@master
with:
submodules: true
- name: Install Rust
run: rustup update stable && rustup default stable
- run: rustup target add wasm32-unknown-unknown
- run: cargo build --target wasm32-unknown-unknown
- run: rustup target add ${{ matrix.target }}
- run: cargo build --target ${{ matrix.target }}

msrv:
name: MSRV
Expand Down
7 changes: 1 addition & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,10 @@ cpp_demangle = { default-features = false, version = "0.2.3", optional = true }
addr2line = { version = "0.10.0", optional = true, default-features = false, features = ['std'] }
findshlibs = { version = "0.5.0", optional = true }
memmap = { version = "0.7.0", optional = true }
goblin = { version = "0.0.24", optional = true, default-features = false, features = ['std'] }
goblin = { version = "0.0.24", optional = true, default-features = false, features = ['elf32', 'elf64', 'mach32', 'mach64', 'pe32', 'pe64', 'std'] }

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

# Each feature controls the two phases of finding a backtrace: getting a
# backtrace and then resolving instruction pointers to symbols. The default
Expand Down
3 changes: 2 additions & 1 deletion crates/backtrace-sys/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ fn main() {
if target.contains("msvc") || // libbacktrace isn't used on MSVC windows
target.contains("emscripten") || // no way this will ever compile for emscripten
target.contains("cloudabi") ||
target.contains("wasm32")
target.contains("wasm32") ||
target.contains("fuchsia") // fuchsia uses external out-of-process symbolization
{
println!("cargo:rustc-cfg=empty");
return;
Expand Down
81 changes: 26 additions & 55 deletions src/capture.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use crate::{resolve, resolve_frame, trace, Symbol, SymbolName};
use crate::PrintFmt;
use crate::{resolve, resolve_frame, trace, BacktraceFmt, Symbol, SymbolName};
use std::ffi::c_void;
use std::fmt;
use std::path::{Path, PathBuf};
use std::prelude::v1::*;

#[cfg(feature = "serde")]
use serde::{Serialize, Deserialize};
use serde::{Deserialize, Serialize};

/// Representation of an owned and self-contained backtrace.
///
Expand Down Expand Up @@ -325,66 +326,36 @@ impl BacktraceSymbol {

impl fmt::Debug for Backtrace {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "stack backtrace:")?;

let iter = if fmt.alternate() {
self.frames.iter()
let full = fmt.alternate();
let (frames, style) = if full {
(&self.frames[..], PrintFmt::Full)
} else {
self.frames[self.actual_start_index..].iter()
(&self.frames[self.actual_start_index..], PrintFmt::Short)
};

for (idx, frame) in iter.enumerate() {
// To reduce TCB size in Sgx enclave, we do not want to implement symbol resolution functionality.
// Rather, we can print the offset of the address here, which could be later mapped to
// correct function.
let ip: *mut c_void;
#[cfg(target_env = "sgx")]
{
ip = usize::wrapping_sub(
frame.ip() as _,
std::os::fortanix_sgx::mem::image_base() as _,
) as _;
}
#[cfg(not(target_env = "sgx"))]
{
ip = frame.ip();
}

write!(fmt, "\n{:4}: ", idx)?;

let symbols = match frame.symbols {
Some(ref s) => s,
None => {
write!(fmt, "<unresolved> ({:?})", ip)?;
continue;
// When printing paths we try to strip the cwd if it exists, otherwise
// we just print the path as-is. Note that we also only do this for the
// short format, because if it's full we presumably want to print
// everything.
let cwd = std::env::current_dir();
let mut print_path = move |fmt: &mut fmt::Formatter, path: crate::BytesOrWideString| {
let path = path.into_path_buf();
if !full {
if let Ok(cwd) = &cwd {
if let Ok(suffix) = path.strip_prefix(cwd) {
return fmt::Display::fmt(&suffix.display(), fmt);
}
}
};
if symbols.len() == 0 {
write!(fmt, "<no info> ({:?})", ip)?;
continue;
}
fmt::Display::fmt(&path.display(), fmt)
};

for (idx, symbol) in symbols.iter().enumerate() {
if idx != 0 {
write!(fmt, "\n ")?;
}

if let Some(name) = symbol.name() {
write!(fmt, "{}", name)?;
} else {
write!(fmt, "<unknown>")?;
}

if idx == 0 {
write!(fmt, " ({:?})", ip)?;
}

if let (Some(file), Some(line)) = (symbol.filename(), symbol.lineno()) {
write!(fmt, "\n at {}:{}", file.display(), line)?;
}
}
let mut f = BacktraceFmt::new(fmt, style, &mut print_path);
f.add_context()?;
for frame in frames {
f.frame()?.backtrace_frame(frame)?;
}

f.finish()?;
Ok(())
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ mod types;
#[cfg(feature = "std")]
pub use crate::symbolize::clear_symbol_cache;

mod print;
pub use print::{BacktraceFmt, PrintFmt};

cfg_if::cfg_if! {
if #[cfg(feature = "std")] {
pub use crate::backtrace::trace;
Expand Down
215 changes: 215 additions & 0 deletions src/print.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
use crate::BytesOrWideString;
use core::ffi::c_void;
use core::fmt;

const HEX_WIDTH: usize = 2 + 2 * core::mem::size_of::<usize>();

#[cfg(target_os = "fuchsia")]
mod fuchsia;

/// A formatter for backtraces.
pub struct BacktraceFmt<'a, 'b> {
fmt: &'a mut fmt::Formatter<'b>,
frame_index: usize,
format: PrintFmt,
print_path: &'a mut (FnMut(&mut fmt::Formatter, BytesOrWideString) -> fmt::Result + 'b),
}

/// The styles of printing that we can print
pub enum PrintFmt {
/// Prints a terser backtrace which ideally only contains relevant information
Short,
/// Prints a backtrace that contains all possible information
Full,
#[doc(hidden)]
__Nonexhaustive,
}

impl<'a, 'b> BacktraceFmt<'a, 'b> {
/// Create a new `BacktraceFmt` which will write output to the provided `fmt`.
pub fn new(
fmt: &'a mut fmt::Formatter<'b>,
format: PrintFmt,
print_path: &'a mut (FnMut(&mut fmt::Formatter, BytesOrWideString) -> fmt::Result + 'b),
) -> Self {
BacktraceFmt {
fmt,
frame_index: 0,
format,
print_path,
}
}

/// Adds the current shared library context to the output.
///
/// This is required for the resulting frames to be symbolized.
pub fn add_context(&mut self) -> fmt::Result {
self.fmt.write_str("stack backtrace:\n")?;
#[cfg(target_os = "fuchsia")]
fuchsia::print_dso_context(self.fmt)?;
Ok(())
}

/// Adds a frame to the backtrace output.
pub fn frame(&mut self) -> Result<FrameFmt<'_, 'a, 'b>, fmt::Error> {
Ok(FrameFmt {
fmt: self,
symbol_index: 0,
})
}

/// Completes the backtrace output.
pub fn finish(&mut self) -> fmt::Result {
// Currently a no-op-- including this hook to allow for future additions.
Ok(())
}
}

/// A formatter for backtraces.
pub struct FrameFmt<'fmt, 'a, 'b> {
fmt: &'fmt mut BacktraceFmt<'a, 'b>,
symbol_index: usize,
}

impl FrameFmt<'_, '_, '_> {
/// Prints a `BacktraceFrame` with this frame formatter.
///
/// This will recusrively print all `BacktraceSymbol` instances within the
/// `BacktraceFrame`.
#[cfg(feature = "std")]
pub fn backtrace_frame(&mut self, frame: &crate::BacktraceFrame) -> fmt::Result {
let symbols = frame.symbols();
for symbol in symbols {
self.backtrace_symbol(frame, symbol)?;
}
if symbols.is_empty() {
self.print_raw(frame.ip(), None, None, None)?;
}
Ok(())
}

/// Prints a `BacktraceSymbol` within a `BacktraceFrame`.
#[cfg(feature = "std")]
pub fn backtrace_symbol(
&mut self,
frame: &crate::BacktraceFrame,
symbol: &crate::BacktraceSymbol,
) -> fmt::Result {
self.print_raw(
frame.ip(),
symbol.name(),
symbol
.filename()
.and_then(|p| Some(BytesOrWideString::Bytes(p.to_str()?.as_bytes()))),
symbol.lineno(),
)?;
Ok(())
}

/// Prints a raw traced `Frame` and `Symbol`, typically from within the raw
/// callbacks of this crate.
pub fn symbol(&mut self, frame: &crate::Frame, symbol: &crate::Symbol) -> fmt::Result {
self.print_raw(
frame.ip(),
symbol.name(),
symbol.filename_raw(),
symbol.lineno(),
)?;
Ok(())
}

/// Adds a frame to the backtrace output.
pub fn print_raw(
&mut self,
frame_ip: *mut c_void,
symbol_name: Option<crate::SymbolName>,
filename: Option<BytesOrWideString>,
lineno: Option<u32>,
) -> fmt::Result {
if cfg!(target_os = "fuchsia") {
self.print_raw_fuchsia(frame_ip)?;
} else {
self.print_raw_generic(frame_ip, symbol_name, filename, lineno)?;
}
self.symbol_index += 1;
Ok(())
}

#[allow(unused_mut)]
fn print_raw_generic(
&mut self,
mut frame_ip: *mut c_void,
symbol_name: Option<crate::SymbolName>,
filename: Option<BytesOrWideString>,
lineno: Option<u32>,
) -> fmt::Result {
// No need to print "null" frames, it basically just means that the
// system backtrace was a bit eager to trace back super far.
if let PrintFmt::Short = self.fmt.format {
if frame_ip.is_null() {
return Ok(());
}
}

// To reduce TCB size in Sgx enclave, we do not want to implement symbol
// resolution functionality. Rather, we can print the offset of the
// address here, which could be later mapped to correct function.
#[cfg(all(feature = "std", target_env = "sgx"))]
{
frame_ip =
usize::wrapping_sub(frame_ip, std::os::fortanix_sgx::mem::image_base() as _) as _;
}

if self.symbol_index == 0 {
write!(self.fmt.fmt, "{:4}: ", self.fmt.frame_index)?;
if let PrintFmt::Full = self.fmt.format {
write!(self.fmt.fmt, "{:1$?} - ", frame_ip, HEX_WIDTH)?;
}
} else {
write!(self.fmt.fmt, " ")?;
if let PrintFmt::Full = self.fmt.format {
write!(self.fmt.fmt, "{:1$}", "", HEX_WIDTH + 3)?;
}
}

match (symbol_name, &self.fmt.format) {
(Some(name), PrintFmt::Short) => write!(self.fmt.fmt, "{}", name)?,
(Some(name), PrintFmt::Full) => write!(self.fmt.fmt, "{:#}", name)?,
(None, _) | (_, PrintFmt::__Nonexhaustive) => write!(self.fmt.fmt, "<unknown>")?,
}
self.fmt.fmt.write_str("\n")?;

if let (Some(file), Some(line)) = (filename, lineno) {
self.print_fileline(file, line)?;
}

Ok(())
}

fn print_fileline(&mut self, file: BytesOrWideString, line: u32) -> fmt::Result {
match self.fmt.format {
PrintFmt::Full => write!(self.fmt.fmt, "{:1$}", "", HEX_WIDTH)?,
PrintFmt::Short | PrintFmt::__Nonexhaustive => {}
}
write!(self.fmt.fmt, " at ")?;
(self.fmt.print_path)(self.fmt.fmt, file)?;
write!(self.fmt.fmt, ":{}\n", line)?;
Ok(())
}

fn print_raw_fuchsia(&mut self, frame_ip: *mut c_void) -> fmt::Result {
// We only care about the first symbol of a frame
if self.symbol_index == 0 {
self.fmt.fmt.write_str("{{{bt:")?;
write!(self.fmt.fmt, "{}:{:?}", self.fmt.frame_index, frame_ip)?;
self.fmt.fmt.write_str("}}}\n")?;
}
Ok(())
}
}

impl Drop for FrameFmt<'_, '_, '_> {
fn drop(&mut self) {
self.fmt.frame_index += 1;
}
}
Loading