| //! 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()) |
| } |
| } |