Skip to content

Commit

Permalink
Add UnitRef::shared_attrs
Browse files Browse the repository at this point in the history
Often, consumers of a DWARF file will want to know if a given function has an attribute, either directly or indirectly. Currently, finding that information is tedious and prone to error. Add a helper function in gimli to make this easier.

This does not attempt to have the maximum possible performance; consumers who care about that can still implement the logic themselves. In particular, the caches are recalculated each time `shared_attrs` is called, introducing an allocation.

The logic works roughly as follows (inspired by [addr2line::Function::parse_children](https://github.com/gimli-rs/addr2line/blob/28ba2d45f2d22134915f55b46ddd6a039559366b/src/function.rs#L278)):

- Create a cache of all DWARF units known to the `UnitRef`
- Given an entry offset, iterate over all its attributes and pass them to a callback chosen by the caller.
- For each indirect attribute, such as `DW_AT_abstract_origin`, follow the pointer to the abstract source and do the same thing again there.

I have tested this downstream (see [rust-lang/rust#134831](rust-lang/rust#134831)), but I have not yet added unit tests. Let me know how you would like this to be tested.
  • Loading branch information
jyn514 committed Jan 21, 2025
1 parent 7e9d923 commit afbceac
Showing 1 changed file with 159 additions and 0 deletions.
159 changes: 159 additions & 0 deletions src/read/dwarf.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use core::ops::ControlFlow;

use alloc::string::String;
use alloc::sync::Arc;
use alloc::vec::Vec;

use crate::common::{
DebugAddrBase, DebugAddrIndex, DebugInfoOffset, DebugLineStrOffset, DebugLocListsBase,
Expand All @@ -19,6 +22,8 @@ use crate::read::{
UnitOffset, UnitType,
};

use super::Attribute;

/// All of the commonly used DWARF sections.
///
/// This is useful for storing sections when `T` does not implement `Reader`.
Expand Down Expand Up @@ -1527,6 +1532,160 @@ impl<'a, R: Reader> UnitRef<'a, R> {
pub fn attr_locations(&self, attr: AttributeValue<R>) -> Result<Option<LocListIter<R>>> {
self.dwarf.attr_locations(self.unit, attr)
}

/// Iterate over an entry's attributes, including those inherited from an abstract instance.
///
/// DWARF allows attributes to be "shared" by concrete instances of an "abstract instance root".
/// For example, inlined functions rarely store attributes inline, instead
/// containing a `DW_AT_abstract_origin` that points to declaration with more
/// information. This function, unlike [`DebuggingInformationEntry::attrs()`],
/// includes those shared attributes in addition to the attrs stored inline.
///
/// `recursion_limit` limits the maximum number of times a reference to an abtract instance is followed.
/// 16 is a conservative default.
///
/// `cb` is called once for each relevant attribute. Note that if an error occurs, `cb` may
/// still be called before the error is encountered. Be cautious about mutating state.
/// The `usize` argument is the number of times we have followed a concrete entry to an abstract entry.
/// It will always be less than `recursion_depth`.
// The `isize` argument is the delta traversal depth within the current entry, as documented by [`EntriesCursor::next_dfs`].
pub fn shared_attrs(
&self,
offset: UnitOffset<R::Offset>,
recursion_limit: usize,
cb: impl FnMut(&Attribute<R>, usize) -> Result<ControlFlow<()>>,
) -> Result<()> {
let collect = |mut units: DebugInfoUnitHeadersIter<R>| {
let mut vec = vec![];
while let Some(unit) = units.next()? {
if let Some(offset) = unit.offset().as_debug_info_offset() {
vec.push(offset);
}
}
Ok(vec)
};
let unit_offsets_cache: Result<_> = collect(self.dwarf.units());
let sup_offsets_cache = if let Some(sup) = self.dwarf.sup() {
collect(sup.units())?
} else {
Vec::new()
};
let mut state = LookupContext {
cb,
unit_offsets_cache: unit_offsets_cache?,
sup_offsets_cache,
};
let entry = self.unit.entry(offset)?;
self.parse_children(entry, &mut state, recursion_limit, 0)?;
Ok(())
}

fn parse_children(
&self,
die: DebuggingInformationEntry<'_, '_, R>,
context: &mut LookupContext<impl FnMut(&Attribute<R>, usize) -> Result<ControlFlow<()>>, R>,
recursion_limit: usize,
recursion_iterations: usize,
) -> Result<ControlFlow<()>> {
let mut attrs = die.attrs();
while let Some(attr) = attrs.next()? {
if let ControlFlow::Break(()) = (context.cb)(&attr, recursion_iterations)? {
return Ok(ControlFlow::Break(()));
}
match attr.name() {
constants::DW_AT_specification
| constants::DW_AT_abstract_origin
| constants::DW_AT_extension => {
self.lookup_attr(attr.value(), context, recursion_limit, recursion_iterations)?;
}
_ => {}
}
}
Ok(ControlFlow::Continue(()))
}

fn lookup_attr(
self,
attr: AttributeValue<R>,
context: &mut LookupContext<impl FnMut(&Attribute<R>, usize) -> Result<ControlFlow<()>>, R>,
recursion_limit: usize,
recursion_iterations: usize,
) -> Result<ControlFlow<()>> {
if recursion_limit == 0 {
return Ok(ControlFlow::Continue(()));
}

let unit_slot;
let mut unit = self;
let relative_offset = match attr {
AttributeValue::UnitRef(offset) => offset,
AttributeValue::DebugInfoRef(dr) => {
let (new_unit, relative_offset) =
self.find_unit(&context, DebugFile::Primary, dr)?;
unit_slot = new_unit;
unit.unit = &unit_slot;
relative_offset
}
AttributeValue::DebugInfoRefSup(dr) => {
let (new_unit, relative_offset) =
self.find_unit(&context, DebugFile::Supplementary, dr)?;
unit_slot = new_unit;
unit.unit = &unit_slot;
relative_offset
}
_ => return Ok(ControlFlow::Continue(())),
};

let entry = unit.entry(relative_offset)?;
unit.parse_children(
entry,
context,
recursion_iterations + 1,
recursion_limit - 1,
)
}

fn find_unit<F>(
&self,
state: &LookupContext<F, R>,
file_type: DebugFile,
direct_ref: DebugInfoOffset<R::Offset>,
) -> Result<(Unit<R, R::Offset>, UnitOffset<R::Offset>)> {
let unit_cache = match file_type {
DebugFile::Primary => &state.unit_offsets_cache,
DebugFile::Supplementary => &state.sup_offsets_cache,
};
let header_offset = match unit_cache.binary_search(&direct_ref) {
Ok(i) => unit_cache[i],
Err(i) => {
if i > 0 {
unit_cache[i - 1]
} else {
return Err(Error::NoEntryAtGivenOffset);
}
}
};
let header = self.dwarf.debug_info.header_from_offset(header_offset)?;
let relative_offset = direct_ref
.to_unit_offset(&header)
.ok_or(Error::NoEntryAtGivenOffset)?;
let unit = Unit::new(self.dwarf, header)?;
Ok((unit, relative_offset))
}
}

struct LookupContext<F, R: Reader> {
/// A cache of the start offsets for each of the units in this file.
unit_offsets_cache: Vec<DebugInfoOffset<R::Offset>>,
sup_offsets_cache: Vec<DebugInfoOffset<R::Offset>>,
cb: F,
}

enum DebugFile {
/// This debuginfo lives in the main dwarf unit.
Primary,
/// This debuginfo lives in the `sup` section of the dwarf unit.
Supplementary,
}

impl<T: ReaderOffset> UnitSectionOffset<T> {
Expand Down

0 comments on commit afbceac

Please sign in to comment.