Skip to content

Commit

Permalink
Parse short backtraces out of debuginfo
Browse files Browse the repository at this point in the history
This implements the runtime side of rust-lang/compiler-team#818.

- Extract a helper out of `find_frames` for iterating over a `LookupContinuation`
- Make the type signature for `search_object_map` consistent across all platforms. Backtraces are inherently platform specific, but let's not make it any harder on ourselves than we have to.
- Add a new `pub fn short_backtraces() -> enum { ThisFrameOnly, Start, End }` API
- Use the new [`gimli::UnitRef::shared_attrs`](gimli-rs/gimli#756) API to determine whether a given frame has a short backtrace. This, for now, requires a git dependency on gimli.

Note that this currently does not work on MacOS. I do not have a Mac to test this; someone lent me theirs and I tracked it down to `cx.find_dwarf_and_unit` returning `None`, but I have not had the time or resources to narrow it down further than that.
  • Loading branch information
jyn514 committed Jan 21, 2025
1 parent 230570f commit 46e31ce
Show file tree
Hide file tree
Showing 10 changed files with 110 additions and 11 deletions.
5 changes: 2 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ version = "0.36.0"
default-features = false
features = ['read_core', 'elf', 'macho', 'pe', 'xcoff', 'unaligned', 'archive']

[patch.crates-io]
gimli = { git = "https://github.com/jyn514/gimli", branch = "shared-attrs", version = "0.31.1" }

[dev-dependencies]
dylib-dep = { path = "crates/dylib-dep" }
libloading = "0.7"
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ pub use self::backtrace::{trace_unsynchronized, Frame};
mod backtrace;

pub use self::symbolize::resolve_frame_unsynchronized;
pub use self::symbolize::{resolve_unsynchronized, Symbol, SymbolName};
pub use self::symbolize::{resolve_unsynchronized, ShortBacktrace, Symbol, SymbolName};
mod symbolize;

pub use self::types::BytesOrWideString;
Expand Down
5 changes: 5 additions & 0 deletions src/symbolize/dbghelp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ impl Symbol<'_> {

self._filename_cache.as_ref().map(Path::new)
}

pub fn short_backtrace(&self) -> Option<super::ShortBacktrace> {
// Not supported with dllhelp API.
None
}
}

#[repr(C, align(8))]
Expand Down
76 changes: 73 additions & 3 deletions src/symbolize/gimli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ use self::mmap::Mmap;
use self::stash::Stash;
use super::BytesOrWideString;
use super::ResolveWhat;
use super::ShortBacktrace;
use super::SymbolName;
use addr2line::gimli;
use addr2line::{LookupContinuation, LookupResult};
use core::convert::TryInto;
use core::mem;
use core::u32;
Expand Down Expand Up @@ -170,9 +172,24 @@ impl<'data> Context<'data> {
stash: &'data Stash,
probe: u64,
) -> gimli::Result<addr2line::FrameIter<'_, EndianSlice<'data, Endian>>> {
use addr2line::{LookupContinuation, LookupResult};
let continuation = self.dwarf.find_frames(probe);
self.continuation_helper(stash, continuation)
}

fn find_dwarf_and_unit(
&'_ self,
stash: &'data Stash,
probe: u64,
) -> Option<gimli::UnitRef<'_, EndianSlice<'data, Endian>>> {
let continuation = self.dwarf.find_dwarf_and_unit(probe);
self.continuation_helper(stash, continuation)
}

let mut l = self.dwarf.find_frames(probe);
fn continuation_helper<O>(
&'_ self,
stash: &'data Stash,
mut l: LookupResult<impl LookupContinuation<Output = O, Buf = EndianSlice<'data, Endian>>>,
) -> O {
loop {
let (load, continuation) = match l {
LookupResult::Output(output) => break output,
Expand Down Expand Up @@ -409,6 +426,43 @@ impl Cache {
}
}

impl ShortBacktrace {
fn from_raw(raw: u8) -> Option<Self> {
let this = match raw {
0 => ShortBacktrace::ThisFrameOnly,
1 => ShortBacktrace::Start,
2 => ShortBacktrace::End,
_ => return None,
};
Some(this)
}
}

#[allow(non_upper_case_globals)]
const DW_AT_short_backtrace: gimli::DwAt = gimli::DwAt(0x3c00);

fn parse_short_backtrace<'data, R: gimli::Reader<Offset = usize>>(
unit_ref: gimli::UnitRef<'_, R>,
frame: &addr2line::Frame<'_, R>,
) -> Option<ShortBacktrace> {
use core::ops::ControlFlow;

let mut short_backtrace = None;
let _ = unit_ref.shared_attrs(frame.dw_die_offset?, 16, |attr, _| {
if attr.name() == DW_AT_short_backtrace {
let parsed = ShortBacktrace::from_raw(
attr.u8_value()
.ok_or(gimli::Error::UnsupportedAttributeForm)?,
);
short_backtrace = Some(parsed.expect("rustc generated invalid debuginfo?"));
return Ok(ControlFlow::Break(()));
}
Ok(ControlFlow::Continue(()))
});

short_backtrace
}

pub unsafe fn resolve(what: ResolveWhat<'_>, cb: &mut dyn FnMut(&super::Symbol)) {
let addr = what.address_or_ip();
let mut call = |sym: Symbol<'_>| {
Expand All @@ -435,24 +489,30 @@ pub unsafe fn resolve(what: ResolveWhat<'_>, cb: &mut dyn FnMut(&super::Symbol))
if let Ok(mut frames) = cx.find_frames(stash, addr as u64) {
while let Ok(Some(frame)) = frames.next() {
any_frames = true;
let name = match frame.function {
let name = match &frame.function {
Some(f) => Some(f.name.slice()),
None => cx.object.search_symtab(addr as u64),
};
let unit_ref = cx.find_dwarf_and_unit(stash, addr as u64);
let short_backtrace = unit_ref.and_then(|unit| parse_short_backtrace(unit, &frame));
call(Symbol::Frame {
addr: addr as *mut c_void,
location: frame.location,
name,
short_backtrace,
});
}
}
if !any_frames {
if let Some((object_cx, object_addr)) = cx.object.search_object_map(addr as u64) {
let unit_ref = None;
if let Ok(mut frames) = object_cx.find_frames(stash, object_addr) {
while let Ok(Some(frame)) = frames.next() {
any_frames = true;
call(Symbol::Frame {
addr: addr as *mut c_void,
short_backtrace: unit_ref
.and_then(|unit| parse_short_backtrace(unit, &frame)),
location: frame.location,
name: frame.function.map(|f| f.name.slice()),
});
Expand All @@ -475,6 +535,7 @@ pub enum Symbol<'a> {
addr: *mut c_void,
location: Option<addr2line::Location<'a>>,
name: Option<&'a [u8]>,
short_backtrace: Option<ShortBacktrace>,
},
/// Couldn't find debug information, but we found it in the symbol table of
/// the elf executable.
Expand Down Expand Up @@ -532,4 +593,13 @@ impl Symbol<'_> {
Symbol::Symtab { .. } => None,
}
}

pub fn short_backtrace(&self) -> Option<ShortBacktrace> {
match self {
Symbol::Frame {
short_backtrace, ..
} => *short_backtrace,
Symbol::Symtab { .. } => None,
}
}
}
2 changes: 1 addition & 1 deletion src/symbolize/gimli/coff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ impl<'a> Object<'a> {
self.symbols[i].1.name(self.strings).ok()
}

pub(super) fn search_object_map(&self, _addr: u64) -> Option<(&Context<'_>, u64)> {
pub(super) fn search_object_map(&mut self, _addr: u64) -> Option<(&Context<'_>, u64)> {
None
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/symbolize/gimli/elf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ impl<'a> Object<'a> {
}
}

pub(super) fn search_object_map(&self, _addr: u64) -> Option<(&Context<'_>, u64)> {
pub(super) fn search_object_map(&mut self, _addr: u64) -> Option<(&Context<'_>, u64)> {
None
}

Expand Down
2 changes: 1 addition & 1 deletion src/symbolize/gimli/xcoff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ impl<'a> Object<'a> {
}
}

pub(super) fn search_object_map(&self, _addr: u64) -> Option<(&Context<'_>, u64)> {
pub(super) fn search_object_map(&mut self, _addr: u64) -> Option<(&Context<'_>, u64)> {
None
}
}
Expand Down
18 changes: 18 additions & 0 deletions src/symbolize/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,16 @@ impl Symbol {
pub fn filename(&self) -> Option<&Path> {
self.inner.filename()
}

/// Returns the short backtrace printing info for this function.
///
/// This is currently only available when libbacktrace or gimli is being
/// used (e.g. unix platforms other) and when a binary is compiled with
/// debuginfo. If neither of these conditions is met then this will likely
/// return `None`.
pub fn short_backtrace(&self) -> Option<ShortBacktrace> {
self.inner.short_backtrace()
}
}

impl fmt::Debug for Symbol {
Expand Down Expand Up @@ -407,6 +417,14 @@ impl<'a> fmt::Debug for SymbolName<'a> {
}
}

#[derive(Copy, Clone, Debug)]
#[allow(missing_docs)]
pub enum ShortBacktrace {
ThisFrameOnly,
Start,
End,
}

/// Attempt to reclaim that cached memory used to symbolicate addresses.
///
/// This method will attempt to release any global data structures that have
Expand Down
6 changes: 5 additions & 1 deletion src/symbolize/noop.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Empty symbolication strategy used to compile for platforms that have no
//! support.
use super::{BytesOrWideString, ResolveWhat, SymbolName};
use super::{BytesOrWideString, ResolveWhat, ShortBacktrace, SymbolName};
use core::ffi::c_void;
use core::marker;

Expand Down Expand Up @@ -36,6 +36,10 @@ impl Symbol<'_> {
pub fn colno(&self) -> Option<u32> {
None
}

pub fn short_backtrace(&self) -> Option<ShortBacktrace> {
None
}
}

pub unsafe fn clear_symbol_cache() {}

0 comments on commit 46e31ce

Please sign in to comment.