blob: b04c59d50e10e0dff94308f68bdc4f87cc75e1e3 [file] [log] [blame]
//! Minor conveniences on top of the backtrace crate
//!
//! See [`short_frames_strict`][] for details.
use backtrace::*;
use std::ops::Range;
#[cfg(test)]
mod test;
/// Gets an iterator over the frames that are part of Rust's "short backtrace" range.
/// If no such range is found, the full stack is yielded.
///
/// Rust generally tries to include special frames on the stack called `rust_end_short_backtrace`
/// and `rust_begin_short_backtrace` which delimit the "real" stackframes from "gunk" stackframes
/// like setting up main and invoking the panic runtime. This yields all the "real" frames between
/// those two (which theoretically can be nothing with enough optimization, although that's unlikely
/// for any non-trivial program).
///
/// If only one of the special frames is present we will only clamp one side of the stack
/// (similar to `a..` or `..a`). If the special frames are in the wrong order we will discard
/// them and produce the full stack. If multiple versions of a special frame are found
/// (I've seen it in the wild), we will pick the "innermost" ones, producing the smallest
/// possible backtrace (and excluding all special frames from the output).
///
/// Each element of the iterator includes a Range which you should use to slice
/// the frame's `symbols()` array. This handles the theoretical situation where "real" frames
/// got inlined together with the special marker frames. I want to believe this can't happen
/// but you can never trust backtraces to be reasonable! We will never yield a Frame to you
/// with an empty Range.
///
/// Note that some "gunk" frames may still be found within the short backtrace, as there is still some
/// platform-specific and optimization-specific glue around the edges because compilers are
/// complicated and nothing's perfect. This can include:
///
/// * `core::ops::function::FnOnce::call_once`
/// * `std::panicking::begin_panic_handler`
/// * `core::panicking::panic_fmt`
/// * `rust_begin_unwind`
///
/// In the future we may introduce a non-strict short_frames which heuristically filters
/// those frames out too. Until then, the strict approach is safe.
///
/// # Example
///
/// Here's an example simple "short backtrace" implementation.
/// Note the use of `sub_frames` for the inner loop to restrict `symbols`!
///
/// This example is based off of code found in `miette` (Apache-2.0), which itself
/// copied the logic from `human-panic` (MIT/Apache-2.0).
///
/// FIXME: it would be nice if this example consulted `RUST_BACKTRACE=full`,
/// and maybe other vars used by rust's builtin panic handler..?
///
/// ```
/// fn backtrace() -> String {
/// use std::fmt::Write;
/// if let Ok(var) = std::env::var("RUST_BACKTRACE") {
/// if !var.is_empty() && var != "0" {
/// const HEX_WIDTH: usize = std::mem::size_of::<usize>() + 2;
/// // Padding for next lines after frame's address
/// const NEXT_SYMBOL_PADDING: usize = HEX_WIDTH + 6;
/// let mut backtrace = String::new();
/// let trace = backtrace::Backtrace::new();
/// let frames = backtrace_ext::short_frames_strict(&trace).enumerate();
/// for (idx, (frame, subframes)) in frames {
/// let ip = frame.ip();
/// let _ = write!(backtrace, "\n{:4}: {:2$?}", idx, ip, HEX_WIDTH);
///
/// let symbols = frame.symbols();
/// if symbols.is_empty() {
/// let _ = write!(backtrace, " - <unresolved>");
/// continue;
/// }
///
/// for (idx, symbol) in symbols[subframes].iter().enumerate() {
/// // Print symbols from this address,
/// // if there are several addresses
/// // we need to put it on next line
/// if idx != 0 {
/// let _ = write!(backtrace, "\n{:1$}", "", NEXT_SYMBOL_PADDING);
/// }
///
/// if let Some(name) = symbol.name() {
/// let _ = write!(backtrace, " - {}", name);
/// } else {
/// let _ = write!(backtrace, " - <unknown>");
/// }
///
/// // See if there is debug information with file name and line
/// if let (Some(file), Some(line)) = (symbol.filename(), symbol.lineno()) {
/// let _ = write!(
/// backtrace,
/// "\n{:3$}at {}:{}",
/// "",
/// file.display(),
/// line,
/// NEXT_SYMBOL_PADDING
/// );
/// }
/// }
/// }
/// return backtrace;
/// }
/// }
/// "".into()
/// }
/// ```
pub fn short_frames_strict(
backtrace: &Backtrace,
) -> impl Iterator<Item = (&BacktraceFrame, Range<usize>)> {
short_frames_strict_impl(backtrace)
}
pub(crate) fn short_frames_strict_impl<B: Backtraceish>(
backtrace: &B,
) -> impl Iterator<Item = (&B::Frame, Range<usize>)> {
// Search for the special frames
let mut short_start = None;
let mut short_end = None;
let frames = backtrace.frames();
for (frame_idx, frame) in frames.iter().enumerate() {
let symbols = frame.symbols();
for (subframe_idx, frame) in symbols.iter().enumerate() {
if let Some(name) = frame.name_str() {
// Yes these ARE backwards, and that's intentional! We want to print the frames from
// "newest to oldest" (show what panicked first), and that's the order that Backtrace
// gives us, but these magic labels view the stack in the opposite order. So we just
// swap it once here and forget about that weirdness.
//
// Note that due to platform/optimization wobblyness you can end up with multiple frames
// that contain these names in sequence. If that happens we just want to pick the two
// that are closest together. For the start that means just using the last one we found,
// and for the end that means taking the first one we find.
if name.contains("rust_end_short_backtrace") {
short_start = Some((frame_idx, subframe_idx));
}
if name.contains("rust_begin_short_backtrace") && short_end.is_none() {
short_end = Some((frame_idx, subframe_idx));
}
}
}
}
// Check if these are in the right order, if they aren't, discard them
// This also handles the mega-cursed case of "someone made a symbol with both names
// so actually they're the exact same subframe".
if let (Some(start), Some(end)) = (short_start, short_end) {
if start >= end {
short_start = None;
short_end = None;
}
}
// By default we want to produce a full stack trace and now we'll try to clamp it.
let mut first_frame = 0usize;
let mut first_subframe = 0usize;
// NOTE: this is INCLUSIVE
let mut last_frame = frames.len().saturating_sub(1);
// NOTE: this is EXCLUSIVE
let mut last_subframe_excl = backtrace
.frames()
.last()
.map(|frame| frame.symbols().len())
.unwrap_or(0);
// This code tries to be really paranoid about boundary conditions although in practice
// most of them are impossible because there's always going to be gunk on either side
// of the short backtrace to smooth out the boundaries, and panic_fmt is basically
// impossible to optimize out. Still, don't trust backtracers!!!
//
// This library has a fuckton of tests to try to catch all the little corner cases here.
// If we found the start bound...
if let Some((idx, sub_idx)) = short_start {
if frames[idx].symbols().len() == sub_idx + 1 {
// If it was the last subframe of this frame, we want to just
// use the whole next frame! It's ok if this takes us to `first_frame = len`,
// that will be properly handled as an empty output
first_frame = idx + 1;
first_subframe = 0;
} else {
// Otherwise use this frame, and all the subframes after it
first_frame = idx;
first_subframe = sub_idx + 1;
}
}
// If we found the end bound...
if let Some((idx, sub_idx)) = short_end {
if sub_idx == 0 {
// If it was the first subframe of this frame, we want to just
// use the whole previous frame!
if idx == 0 {
// If we were *also* on the first frame, set subframe_excl
// to 0, indicating an empty output
last_frame = 0;
last_subframe_excl = 0;
} else {
last_frame = idx - 1;
last_subframe_excl = frames[last_frame].symbols().len();
}
} else {
// Otherwise use this frame (no need subframe math, exclusive bound!)
last_frame = idx;
last_subframe_excl = sub_idx;
}
}
// If the two subframes managed to perfectly line up with eachother, just
// throw everything out and yield an empty range. We don't need to fix any
// other values at this point as they won't be used for anything with an
// empty iterator
let final_frames = {
let start = (first_frame, first_subframe);
let end = (last_frame, last_subframe_excl);
if start == end {
&frames[0..0]
} else {
&frames[first_frame..=last_frame]
}
};
// Get the index of the last frame when starting from the first frame
let adjusted_last_frame = last_frame.saturating_sub(first_frame);
// finally do the iteration
final_frames.iter().enumerate().map(move |(idx, frame)| {
// Default to all subframes being yielded
let mut sub_start = 0;
let mut sub_end_excl = frame.symbols().len();
// If we're on first frame, apply its subframe clamp
if idx == 0 {
sub_start = first_subframe;
}
// If we're on the last frame, apply its subframe clamp
if idx == adjusted_last_frame {
sub_end_excl = last_subframe_excl;
}
(frame, sub_start..sub_end_excl)
})
}
pub(crate) trait Backtraceish {
type Frame: Frameish;
fn frames(&self) -> &[Self::Frame];
}
pub(crate) trait Frameish {
type Symbol: Symbolish;
fn symbols(&self) -> &[Self::Symbol];
}
pub(crate) trait Symbolish {
fn name_str(&self) -> Option<&str>;
}
impl Backtraceish for Backtrace {
type Frame = BacktraceFrame;
fn frames(&self) -> &[Self::Frame] {
self.frames()
}
}
impl Frameish for BacktraceFrame {
type Symbol = BacktraceSymbol;
fn symbols(&self) -> &[Self::Symbol] {
self.symbols()
}
}
impl Symbolish for BacktraceSymbol {
// We need to shortcut SymbolName here because
// HRTB isn't in our msrv
fn name_str(&self) -> Option<&str> {
self.name().and_then(|n| n.as_str())
}
}