diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 33df0a4..e2162ee 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -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
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 861d740..7d3a20e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/Cargo.toml b/Cargo.toml
index 90b3f40..952f69e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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 }
diff --git a/src/lib.rs b/src/lib.rs
index 8d360a7..6f441e2 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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;
+}
+
+#[cfg(feature = "use-backtrace-crate")]
+impl Backtrace for backtrace::Backtrace {
+ fn frames(&self) -> Vec {
+ 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 {
+ 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> {
+ #[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] //
// ============================================================================================== //
@@ -158,7 +229,7 @@ pub struct Frame {
pub name: Option,
pub lineno: Option,
pub filename: Option,
- pub ip: usize,
+ pub ip: Option,
_private_ctor: (),
}
@@ -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)?;
+ }
}
}
@@ -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() {
@@ -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 {
+ pub fn format_trace_to_string(&self, trace: &dyn Backtrace) -> IOResult {
// TODO: should we implicitly enable VT100 support on Windows here?
let mut ansi = Ansi::new(vec![]);
self.print_trace(trace, &mut ansi)?;
@@ -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(())
@@ -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())
}