Skip to content

Commit

Permalink
Support btparse as the backtrace format
Browse files Browse the repository at this point in the history
  • Loading branch information
athre0z committed Jul 12, 2024
1 parent feb2cd0 commit b37bb85
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 34 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ jobs:
features: ["--all-features"]
include:
- image_name: "ubuntu-22.04"
features: "--no-default-features"
features: "--no-default-features" # can't print any traces, but should compile at least
- image_name: "ubuntu-22.04"
features: "--no-default-features --features use-btparse-crate"
steps:
- name: Clone
uses: actions/checkout@v3
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
# Changelog
All notable changes to this project will be documented in this file.

## Unreleased
- Add `Backtrace` trait to abstract over backtrace implementation
- `BacktracePrinter::print_trace` now takes `&dyn Backtrace` instead of `&backtrace::Backtrace`
- This may be API breaking when users use `default-features = false` so that `&backtrace::Backtrace` doesn't coerce to `&dyn Backtrace`
- Add experimental support for `std::backtrace::Backtrace`
- Enable via `{ default-features = false, features = ["use-btparse-crate"] }`

## [v0.6.1] (2023-10-23)
- Publicly expose some helper methods on `Frame` type

Expand Down
24 changes: 14 additions & 10 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,26 @@ description = "Colorful panic backtraces"
readme = "README.md"
rust-version = "1.70"

keywords = [
"backtrace",
"color",
"colour",
"stacktrace",
"pretty",
]
keywords = ["backtrace", "color", "colour", "stacktrace", "pretty"]

[features]
default = []
resolve-modules = ["regex"]
default = ["use-backtrace-crate"]

# Print module memory mappings. Only takes effect on Linux.
resolve-modules = ["dep:regex", "use-backtrace-crate"]

# Uses backtrace-rs crate. Reliable. Preferred over btparse if both are enabled.
use-backtrace-crate = ["dep:backtrace"]

# Uses btparse to parse the unstable debug repr of std::backtrace::Backtrace.
# Not guaranteed to work if Rust decides to change the format, but fewer dependencies.
use-btparse-crate = ["dep:btparse"]

# Deprecated, no longer has any effect: backtrace crate removed corresponding option.
gimli-symbolize = []

[dependencies]
termcolor = "1.1.2"
backtrace = "0.3.57"
backtrace = { version = "0.3.57", optional = true }
regex = { version = "1.4.6", optional = true }
btparse = { version = "*", optional = true }
110 changes: 87 additions & 23 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,77 @@ pub fn install_with_settings(printer: BacktracePrinter) {
std::panic::set_hook(printer.into_panic_handler(default_output_stream()))
}

// ============================================================================================== //
// [Backtrace abstraction] //
// ============================================================================================== //

/// Abstraction over backtrace library implementations.
pub trait Backtrace {
fn frames(&self) -> Vec<Frame>;
}

#[cfg(feature = "use-backtrace-crate")]
impl Backtrace for backtrace::Backtrace {
fn frames(&self) -> Vec<Frame> {
backtrace::Backtrace::frames(self)
.iter()
.flat_map(|frame| frame.symbols().iter().map(move |sym| (frame.ip(), sym)))
.zip(1usize..)
.map(|((ip, sym), n)| Frame {
name: sym.name().map(|x| x.to_string()),
lineno: sym.lineno(),
filename: sym.filename().map(|x| x.into()),
n,
ip: Some(ip as usize),
_private_ctor: (),
})
.collect()
}
}

#[cfg(feature = "use-btparse-crate")]
impl Backtrace for btparse::Backtrace {
fn frames(&self) -> Vec<Frame> {
self.frames
.iter()
.zip(1usize..)
.map(|(frame, n)| Frame {
n,
name: Some(frame.function.clone()),
lineno: frame.line.map(|x| x as u32),
filename: frame.file.as_ref().map(|x| x.clone().into()),
ip: None,
_private_ctor: (),
})
.collect()
}
}

// Capture a backtrace with the most reliable available backtrace implementation.
fn capture_backtrace() -> Result<Box<dyn Backtrace>, Box<dyn std::error::Error>> {
#[cfg(all(feature = "use-backtrace-crate", feature = "use-btparse-crate"))]
return Ok(Box::new(backtrace::Backtrace::new()));

#[cfg(all(feature = "use-backtrace-crate", not(feature = "use-btparse-crate")))]
return Ok(Box::new(backtrace::Backtrace::new()));

#[cfg(all(not(feature = "use-backtrace-crate"), feature = "use-btparse-crate"))]
{
let bt = std::backtrace::Backtrace::force_capture();
return Ok(Box::new(btparse::deserialize(&bt).map_err(Box::new)?));
}

#[cfg(all(
not(feature = "use-backtrace-crate"),
not(feature = "use-btparse-crate")
))]
{
return Err(Box::new(
"need to enable at least one backtrace crate selector feature",
));
}
}

// ============================================================================================== //
// [Backtrace frame] //
// ============================================================================================== //
Expand All @@ -158,7 +229,7 @@ pub struct Frame {
pub name: Option<String>,
pub lineno: Option<u32>,
pub filename: Option<PathBuf>,
pub ip: usize,
pub ip: Option<usize>,
_private_ctor: (),
}

Expand Down Expand Up @@ -364,11 +435,13 @@ impl Frame {
// Print frame index.
write!(out, "{:>2}: ", i)?;

if s.should_print_addresses() {
if let Some((module_name, module_base)) = self.module_info() {
write!(out, "{}:0x{:08x} - ", module_name, self.ip - module_base)?;
} else {
write!(out, "0x{:016x} - ", self.ip)?;
if let Some(ip) = self.ip {
if s.should_print_addresses() {
if let Some((module_name, module_base)) = self.module_info() {
write!(out, "{}:0x{:08x} - ", module_name, ip - module_base)?;
} else {
write!(out, "0x{:016x} - ", ip)?;
}
}
}

Expand Down Expand Up @@ -659,24 +732,11 @@ impl BacktracePrinter {
}

/// Pretty-prints a [`backtrace::Backtrace`] to an output stream.
pub fn print_trace(&self, trace: &backtrace::Backtrace, out: &mut impl WriteColor) -> IOResult {
pub fn print_trace(&self, trace: &dyn Backtrace, out: &mut impl WriteColor) -> IOResult {
writeln!(out, "{:━^80}", " BACKTRACE ")?;

// Collect frame info.
let frames: Vec<_> = trace
.frames()
.iter()
.flat_map(|frame| frame.symbols().iter().map(move |sym| (frame.ip(), sym)))
.zip(1usize..)
.map(|((ip, sym), n)| Frame {
name: sym.name().map(|x| x.to_string()),
lineno: sym.lineno(),
filename: sym.filename().map(|x| x.into()),
n,
ip: ip as usize,
_private_ctor: (),
})
.collect();
let frames = trace.frames();

let mut filtered_frames = frames.iter().collect();
match env::var("COLORBT_SHOW_HIDDEN").ok().as_deref() {
Expand Down Expand Up @@ -731,7 +791,7 @@ impl BacktracePrinter {
}

/// Pretty-print a backtrace to a `String`, using VT100 color codes.
pub fn format_trace_to_string(&self, trace: &backtrace::Backtrace) -> IOResult<String> {
pub fn format_trace_to_string(&self, trace: &dyn Backtrace) -> IOResult<String> {
// TODO: should we implicitly enable VT100 support on Windows here?
let mut ansi = Ansi::new(vec![]);
self.print_trace(trace, &mut ansi)?;
Expand Down Expand Up @@ -795,7 +855,10 @@ impl BacktracePrinter {
}

if self.current_verbosity() >= Verbosity::Medium {
self.print_trace(&backtrace::Backtrace::new(), out)?;
match capture_backtrace() {
Ok(trace) => self.print_trace(&*trace, out)?,
Err(e) => writeln!(out, "Failed to capture backtrace: {e}")?,
}
}

Ok(())
Expand All @@ -820,6 +883,7 @@ impl BacktracePrinter {

#[doc(hidden)]
#[deprecated(since = "0.4.0", note = "Use `BacktracePrinter::print_trace` instead`")]
#[cfg(feature = "use-backtrace-crate")]
pub fn print_backtrace(trace: &backtrace::Backtrace, s: &mut BacktracePrinter) -> IOResult {
s.print_trace(trace, &mut default_output_stream())
}
Expand Down

0 comments on commit b37bb85

Please sign in to comment.