use std::cell::OnceCell;
use std::cmp::Ordering;
use std::fmt::Debug;
use std::fmt::Formatter;
use std::fmt::Result as FmtResult;
use std::vec;
use gimli::Error;
use super::range::RangeAttributes;
use super::reader::R;
use super::units::Units;
fn name_entry<'dwarf>(
unit: gimli::UnitRef<'_, R<'dwarf>>,
offset: gimli::UnitOffset<<R<'_> as gimli::Reader>::Offset>,
units: &Units<'dwarf>,
recursion_limit: usize,
) -> Result<Option<R<'dwarf>>, Error> {
let mut entries = unit.entries_raw(Some(offset))?;
let abbrev = if let Some(abbrev) = entries.read_abbreviation()? {
abbrev
} else {
return Err(gimli::Error::NoEntryAtGivenOffset)
};
let mut name = None;
let mut next = None;
for spec in abbrev.attributes() {
match entries.read_attribute(*spec) {
Ok(ref attr) => match attr.name() {
gimli::DW_AT_linkage_name | gimli::DW_AT_MIPS_linkage_name => {
if let Ok(val) = unit.attr_string(attr.value()) {
return Ok(Some(val))
}
}
gimli::DW_AT_name => {
if let Ok(val) = unit.attr_string(attr.value()) {
name = Some(val);
}
}
gimli::DW_AT_abstract_origin | gimli::DW_AT_specification => {
next = Some(attr.value());
}
_ => {}
},
Err(e) => return Err(e),
}
}
if name.is_some() {
return Ok(name)
}
if let Some(next) = next {
return name_attr(next, unit, units, recursion_limit - 1)
}
Ok(None)
}
fn name_attr<'dwarf>(
attr: gimli::AttributeValue<R>,
unit: gimli::UnitRef<'_, R<'dwarf>>,
units: &Units<'dwarf>,
recursion_limit: usize,
) -> Result<Option<R<'dwarf>>, Error> {
if recursion_limit == 0 {
return Ok(None)
}
match attr {
gimli::AttributeValue::UnitRef(offset) => name_entry(unit, offset, units, recursion_limit),
gimli::AttributeValue::DebugInfoRef(offset) => {
let (unit, offset) = units.find_unit(offset)?;
name_entry(unit, offset, units, recursion_limit)
}
_ => Ok(None),
}
}
struct InlinedState<'call, 'dwarf> {
entries: gimli::EntriesRaw<'call, 'call, R<'dwarf>>,
functions: Vec<InlinedFunction<'dwarf>>,
addresses: Vec<InlinedFunctionAddress>,
unit: gimli::UnitRef<'call, R<'dwarf>>,
units: &'call Units<'dwarf>,
}
#[derive(Debug)]
pub(super) struct InlinedFunction<'dwarf> {
pub(crate) name: Option<R<'dwarf>>,
pub(crate) call_file: Option<u64>,
pub(crate) call_line: u32,
pub(crate) call_column: u32,
}
impl<'dwarf> InlinedFunction<'dwarf> {
fn parse(
state: &mut InlinedState<'_, 'dwarf>,
abbrev: &gimli::Abbreviation,
depth: isize,
inlined_depth: usize,
) -> Result<(), Error> {
let mut ranges = RangeAttributes::default();
let mut name = None;
let mut call_file = None;
let mut call_line = 0;
let mut call_column = 0;
for spec in abbrev.attributes() {
match state.entries.read_attribute(*spec) {
Ok(ref attr) => match attr.name() {
gimli::DW_AT_low_pc => match attr.value() {
gimli::AttributeValue::Addr(val) => ranges.low_pc = Some(val),
gimli::AttributeValue::DebugAddrIndex(index) => {
ranges.low_pc = Some(state.unit.address(index)?);
}
_ => {}
},
gimli::DW_AT_high_pc => match attr.value() {
gimli::AttributeValue::Addr(val) => ranges.high_pc = Some(val),
gimli::AttributeValue::DebugAddrIndex(index) => {
ranges.high_pc = Some(state.unit.address(index)?);
}
gimli::AttributeValue::Udata(val) => ranges.size = Some(val),
_ => {}
},
gimli::DW_AT_ranges => {
ranges.ranges_offset = state.unit.attr_ranges_offset(attr.value())?;
}
gimli::DW_AT_linkage_name | gimli::DW_AT_MIPS_linkage_name => {
if let Ok(val) = state.unit.attr_string(attr.value()) {
name = Some(val);
}
}
gimli::DW_AT_name => {
if name.is_none() {
name = state.unit.attr_string(attr.value()).ok();
}
}
gimli::DW_AT_abstract_origin | gimli::DW_AT_specification => {
if name.is_none() {
name = name_attr(attr.value(), state.unit, state.units, 16)?;
}
}
gimli::DW_AT_call_file => {
if let gimli::AttributeValue::FileIndex(fi) = attr.value() {
if fi > 0 || state.unit.header.version() >= 5 {
call_file = Some(fi);
}
}
}
gimli::DW_AT_call_line => {
call_line = attr.udata_value().unwrap_or(0) as u32;
}
gimli::DW_AT_call_column => {
call_column = attr.udata_value().unwrap_or(0) as u32;
}
_ => {}
},
Err(e) => return Err(e),
}
}
let function_index = state.functions.len();
state.functions.push(InlinedFunction {
name,
call_file,
call_line,
call_column,
});
ranges.for_each_range(state.unit, |range| {
state.addresses.push(InlinedFunctionAddress {
range,
call_depth: inlined_depth,
function: function_index,
});
})?;
Function::parse_children(state, depth, inlined_depth + 1)
}
}
struct InlinedFunctionAddress {
range: gimli::Range,
call_depth: usize,
function: usize,
}
pub(super) struct InlinedFunctions<'dwarf> {
inlined_functions: Box<[InlinedFunction<'dwarf>]>,
inlined_addresses: Box<[InlinedFunctionAddress]>,
}
impl<'dwarf> InlinedFunctions<'dwarf> {
pub(crate) fn parse(
dw_die_offset: gimli::UnitOffset<<R<'dwarf> as gimli::Reader>::Offset>,
unit: gimli::UnitRef<'_, R<'dwarf>>,
units: &Units<'dwarf>,
) -> Result<Self, Error> {
let mut entries = unit.entries_raw(Some(dw_die_offset))?;
let depth = entries.next_depth();
let abbrev = entries.read_abbreviation()?.unwrap();
debug_assert_eq!(abbrev.tag(), gimli::DW_TAG_subprogram);
let () = entries.skip_attributes(abbrev.attributes())?;
let mut state = InlinedState {
entries,
functions: Vec::new(),
addresses: Vec::new(),
unit,
units,
};
Function::parse_children(&mut state, depth, 0)?;
state.addresses.sort_by(|r1, r2| {
(r1.call_depth, r1.range.begin).cmp(&(r2.call_depth, r2.range.begin))
});
Ok(Self {
inlined_functions: state.functions.into_boxed_slice(),
inlined_addresses: state.addresses.into_boxed_slice(),
})
}
pub(super) fn find_inlined_functions(
&self,
probe: u64,
) -> vec::IntoIter<&InlinedFunction<'dwarf>> {
let mut inlined_functions = Vec::new();
let mut inlined_addresses = &self.inlined_addresses[..];
loop {
let current_depth = inlined_functions.len();
let search = inlined_addresses.binary_search_by(|range| {
if range.call_depth > current_depth {
Ordering::Greater
} else if range.call_depth < current_depth {
Ordering::Less
} else if range.range.begin > probe {
Ordering::Greater
} else if range.range.end <= probe {
Ordering::Less
} else {
Ordering::Equal
}
});
if let Ok(index) = search {
let function_index = inlined_addresses[index].function;
inlined_functions.push(&self.inlined_functions[function_index]);
inlined_addresses = &inlined_addresses[index + 1..];
} else {
break
}
}
inlined_functions.into_iter()
}
}
#[derive(Debug)]
pub(crate) struct FunctionAddress {
range: gimli::Range,
pub(crate) function: usize,
}
pub(crate) struct Function<'dwarf> {
pub(crate) dw_die_offset: gimli::UnitOffset<<R<'dwarf> as gimli::Reader>::Offset>,
pub(crate) name: Option<R<'dwarf>>,
pub(crate) range: Option<gimli::Range>,
pub(super) inlined_functions: OnceCell<gimli::Result<InlinedFunctions<'dwarf>>>,
}
impl<'dwarf> Function<'dwarf> {
fn parse_children(
state: &mut InlinedState<'_, 'dwarf>,
depth: isize,
inlined_depth: usize,
) -> Result<(), Error> {
loop {
let next_depth = state.entries.next_depth();
if next_depth <= depth {
return Ok(())
}
if let Some(abbrev) = state.entries.read_abbreviation()? {
match abbrev.tag() {
gimli::DW_TAG_subprogram => {
Function::skip(&mut state.entries, abbrev, next_depth)?;
}
gimli::DW_TAG_inlined_subroutine => {
InlinedFunction::parse(state, abbrev, next_depth, inlined_depth)?;
}
_ => {
state.entries.skip_attributes(abbrev.attributes())?;
}
}
}
}
}
pub(super) fn parse_inlined_functions(
&self,
unit: gimli::UnitRef<'_, R<'dwarf>>,
units: &Units<'dwarf>,
) -> Result<&InlinedFunctions<'dwarf>, Error> {
self.inlined_functions
.get_or_init(|| InlinedFunctions::parse(self.dw_die_offset, unit, units))
.as_ref()
.map_err(|err| *err)
}
fn skip(
entries: &mut gimli::EntriesRaw<'_, '_, R<'dwarf>>,
abbrev: &gimli::Abbreviation,
depth: isize,
) -> Result<(), Error> {
entries.skip_attributes(abbrev.attributes())?;
while entries.next_depth() > depth {
if let Some(abbrev) = entries.read_abbreviation()? {
entries.skip_attributes(abbrev.attributes())?;
}
}
Ok(())
}
}
impl Debug for Function<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
let Self {
dw_die_offset,
name,
range,
inlined_functions: _,
} = self;
f.debug_struct(stringify!(Function))
.field("dw_die_offset", dw_die_offset)
.field(
"name",
match name.as_ref().and_then(|r| r.to_string().ok()) {
Some(ref s) => s,
None => &name,
},
)
.field("range", range)
.finish()
}
}
#[derive(Debug)]
pub(crate) struct Functions<'dwarf> {
pub(crate) functions: Box<[Function<'dwarf>]>,
pub(crate) addresses: Box<[FunctionAddress]>,
}
impl<'dwarf> Functions<'dwarf> {
pub(crate) fn parse(
unit: gimli::UnitRef<'_, R<'dwarf>>,
units: &Units<'dwarf>,
) -> Result<Self, Error> {
let mut functions = Vec::new();
let mut addresses = Vec::new();
let mut entries = unit.entries_raw(None)?;
while !entries.is_empty() {
let dw_die_offset = entries.next_offset();
if let Some(abbrev) = entries.read_abbreviation()? {
if abbrev.tag() == gimli::DW_TAG_subprogram {
let mut name = None;
let mut ranges = RangeAttributes::default();
for spec in abbrev.attributes() {
match entries.read_attribute(*spec) {
Ok(ref attr) => {
match attr.name() {
gimli::DW_AT_linkage_name | gimli::DW_AT_MIPS_linkage_name => {
if let Ok(val) = unit.attr_string(attr.value()) {
name = Some(val);
}
}
gimli::DW_AT_name => {
if name.is_none() {
name = unit.attr_string(attr.value()).ok();
}
}
gimli::DW_AT_abstract_origin | gimli::DW_AT_specification => {
if name.is_none() {
name = name_attr(attr.value(), unit, units, 16)?;
}
}
gimli::DW_AT_low_pc => match attr.value() {
gimli::AttributeValue::Addr(val) => {
ranges.low_pc = Some(val)
}
gimli::AttributeValue::DebugAddrIndex(index) => {
ranges.low_pc = Some(unit.address(index)?);
}
_ => {}
},
gimli::DW_AT_high_pc => match attr.value() {
gimli::AttributeValue::Addr(val) => {
ranges.high_pc = Some(val)
}
gimli::AttributeValue::DebugAddrIndex(index) => {
ranges.high_pc = Some(unit.address(index)?);
}
gimli::AttributeValue::Udata(val) => {
ranges.size = Some(val)
}
_ => {}
},
gimli::DW_AT_ranges => {
ranges.ranges_offset =
unit.attr_ranges_offset(attr.value())?;
}
_ => {}
};
}
Err(e) => return Err(e),
}
}
let function_index = functions.len();
let added = ranges.for_each_range(unit, |range| {
addresses.push(FunctionAddress {
range,
function: function_index,
});
})?;
if added {
let function = Function {
dw_die_offset,
name,
range: ranges.bounds(),
inlined_functions: OnceCell::new(),
};
functions.push(function);
}
} else {
entries.skip_attributes(abbrev.attributes())?;
}
}
}
addresses.sort_by_key(|x| x.range.begin);
Ok(Functions {
functions: functions.into_boxed_slice(),
addresses: addresses.into_boxed_slice(),
})
}
#[cfg(test)]
#[cfg(feature = "nightly")]
pub(crate) fn parse_inlined_functions(
&self,
unit: gimli::UnitRef<'_, R<'dwarf>>,
units: &Units<'dwarf>,
) -> Result<(), Error> {
for function in &*self.functions {
let _inlined_fns = function.parse_inlined_functions(unit, units)?;
}
Ok(())
}
pub(crate) fn find_address(&self, probe: u64) -> Option<usize> {
self.addresses
.binary_search_by(|address| {
if probe < address.range.begin {
Ordering::Greater
} else if probe >= address.range.end {
Ordering::Less
} else {
Ordering::Equal
}
})
.ok()
}
}
#[cfg(test)]
mod tests {
use super::*;
use test_log::test;
#[test]
fn debug_repr() {
let addr = FunctionAddress {
range: gimli::Range {
begin: 0x42,
end: 0x43,
},
function: 1337,
};
assert_ne!(format!("{addr:?}"), "");
let func = Function {
dw_die_offset: gimli::UnitOffset(24),
name: None,
range: None,
inlined_functions: OnceCell::new(),
};
assert_ne!(format!("{func:?}"), "");
let funcs = Functions {
functions: Box::default(),
addresses: Box::default(),
};
assert_ne!(format!("{funcs:?}"), "");
}
}