use alloc::boxed::Box; use alloc::vec::Vec; use core::cmp::Ordering; use crate::lazy::LazyResult; use crate::maybe_small; use crate::{Context, DebugFile, Error, RangeAttributes}; pub(crate) struct LazyFunctions(LazyResult>); impl LazyFunctions { pub(crate) fn new() -> Self { LazyFunctions(LazyResult::new()) } pub(crate) fn borrow(&self, unit: gimli::UnitRef) -> Result<&Functions, Error> { self.0 .borrow_with(|| Functions::parse(unit)) .as_ref() .map_err(Error::clone) } } pub(crate) struct Functions { /// List of all `DW_TAG_subprogram` details in the unit. pub(crate) functions: Box<[LazyFunction]>, /// List of `DW_TAG_subprogram` address ranges in the unit. pub(crate) addresses: Box<[FunctionAddress]>, } /// A single address range for a function. /// /// It is possible for a function to have multiple address ranges; this /// is handled by having multiple `FunctionAddress` entries with the same /// `function` field. pub(crate) struct FunctionAddress { range: gimli::Range, /// An index into `Functions::functions`. pub(crate) function: usize, } pub(crate) struct LazyFunction { dw_die_offset: gimli::UnitOffset, lazy: LazyResult>, } impl LazyFunction { fn new(dw_die_offset: gimli::UnitOffset) -> Self { LazyFunction { dw_die_offset, lazy: LazyResult::new(), } } pub(crate) fn borrow( &self, file: DebugFile, unit: gimli::UnitRef, ctx: &Context, ) -> Result<&Function, Error> { self.lazy .borrow_with(|| Function::parse(self.dw_die_offset, file, unit, ctx)) .as_ref() .map_err(Error::clone) } } pub(crate) struct Function { pub(crate) dw_die_offset: gimli::UnitOffset, pub(crate) name: Option, /// List of all `DW_TAG_inlined_subroutine` details in this function. inlined_functions: Box<[InlinedFunction]>, /// List of `DW_TAG_inlined_subroutine` address ranges in this function. inlined_addresses: Box<[InlinedFunctionAddress]>, } pub(crate) struct InlinedFunctionAddress { range: gimli::Range, call_depth: usize, /// An index into `Function::inlined_functions`. function: usize, } pub(crate) struct InlinedFunction { pub(crate) dw_die_offset: gimli::UnitOffset, pub(crate) name: Option, pub(crate) call_file: Option, pub(crate) call_line: u32, pub(crate) call_column: u32, } impl Functions { fn parse(unit: gimli::UnitRef) -> Result, 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 ranges = RangeAttributes::default(); for spec in abbrev.attributes() { match 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(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 has_address = ranges.for_each_range(unit, |range| { addresses.push(FunctionAddress { range, function: function_index, }); })?; if has_address { functions.push(LazyFunction::new(dw_die_offset)); } } else { entries.skip_attributes(abbrev.attributes())?; } } } // The binary search requires the addresses to be sorted. // // It also requires them to be non-overlapping. In practice, overlapping // function ranges are unlikely, so we don't try to handle that yet. // // It's possible for multiple functions to have the same address range if the // compiler can detect and remove functions with identical code. In that case // we'll nondeterministically return one of them. addresses.sort_by_key(|x| x.range.begin); Ok(Functions { functions: functions.into_boxed_slice(), addresses: addresses.into_boxed_slice(), }) } pub(crate) fn find_address(&self, probe: u64) -> Option { 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() } pub(crate) fn parse_inlined_functions( &self, file: DebugFile, unit: gimli::UnitRef, ctx: &Context, ) -> Result<(), Error> { for function in &*self.functions { function.borrow(file, unit, ctx)?; } Ok(()) } } impl Function { fn parse( dw_die_offset: gimli::UnitOffset, file: DebugFile, unit: gimli::UnitRef, ctx: &Context, ) -> Result { 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 mut name = 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()) { 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(), file, unit, ctx, 16)?; } } _ => {} }; } Err(e) => return Err(e), } } let mut state = InlinedState { entries, functions: Vec::new(), addresses: Vec::new(), file, unit, ctx, }; Function::parse_children(&mut state, depth, 0)?; // Sort ranges in "breadth-first traversal order", i.e. first by call_depth // and then by range.begin. This allows finding the range containing an // address at a certain depth using binary search. // Note: Using DFS order, i.e. ordering by range.begin first and then by // call_depth, would not work! Consider the two examples // "[0..10 at depth 0], [0..2 at depth 1], [6..8 at depth 1]" and // "[0..5 at depth 0], [0..2 at depth 1], [5..10 at depth 0], [6..8 at depth 1]". // In this example, if you want to look up address 7 at depth 0, and you // encounter [0..2 at depth 1], are you before or after the target range? // You don't know. state.addresses.sort_by(|r1, r2| { if r1.call_depth < r2.call_depth { Ordering::Less } else if r1.call_depth > r2.call_depth { Ordering::Greater } else if r1.range.begin < r2.range.begin { Ordering::Less } else if r1.range.begin > r2.range.begin { Ordering::Greater } else { Ordering::Equal } }); Ok(Function { dw_die_offset, name, inlined_functions: state.functions.into_boxed_slice(), inlined_addresses: state.addresses.into_boxed_slice(), }) } fn parse_children( state: &mut InlinedState, depth: isize, inlined_depth: usize, ) -> Result<(), Error> { loop { let dw_die_offset = state.entries.next_offset(); 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, dw_die_offset, abbrev, next_depth, inlined_depth, )?; } _ => { state.entries.skip_attributes(abbrev.attributes())?; } } } } } fn skip( entries: &mut gimli::EntriesRaw<'_, '_, R>, abbrev: &gimli::Abbreviation, depth: isize, ) -> Result<(), Error> { // TODO: use DW_AT_sibling entries.skip_attributes(abbrev.attributes())?; while entries.next_depth() > depth { if let Some(abbrev) = entries.read_abbreviation()? { entries.skip_attributes(abbrev.attributes())?; } } Ok(()) } /// Build the list of inlined functions that contain `probe`. pub(crate) fn find_inlined_functions( &self, probe: u64, ) -> maybe_small::Vec<&InlinedFunction> { // `inlined_functions` is ordered from outside to inside. let mut inlined_functions = maybe_small::Vec::new(); let mut inlined_addresses = &self.inlined_addresses[..]; loop { let current_depth = inlined_functions.len(); // Look up (probe, current_depth) in inline_ranges. // `inlined_addresses` is sorted in "breadth-first traversal order", i.e. // by `call_depth` first, and then by `range.begin`. See the comment at // the sort call for more information about why. 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 } } impl InlinedFunction { fn parse( state: &mut InlinedState, dw_die_offset: gimli::UnitOffset, abbrev: &gimli::Abbreviation, depth: isize, inlined_depth: usize, ) -> Result<(), Error> { let unit = state.unit; 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(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())?; } 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(), state.file, unit, state.ctx, 16)?; } } gimli::DW_AT_call_file => { // There is a spec issue [1] with how DW_AT_call_file is specified in DWARF 5. // Before, a file index of 0 would indicate no source file, however in // DWARF 5 this could be a valid index into the file table. // // Implementations such as LLVM generates a file index of 0 when DWARF 5 is // used. // // Thus, if we see a version of 5 or later, treat a file index of 0 as such. // [1]: http://wiki.dwarfstd.org/index.php?title=DWARF5_Line_Table_File_Numbers if let gimli::AttributeValue::FileIndex(fi) = attr.value() { if fi > 0 || 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 { dw_die_offset, name, call_file, call_line, call_column, }); ranges.for_each_range(unit, |range| { state.addresses.push(InlinedFunctionAddress { range, call_depth: inlined_depth, function: function_index, }); })?; Function::parse_children(state, depth, inlined_depth + 1) } } struct InlinedState<'a, R: gimli::Reader> { // Mutable fields. entries: gimli::EntriesRaw<'a, 'a, R>, functions: Vec>, addresses: Vec, // Constant fields. file: DebugFile, unit: gimli::UnitRef<'a, R>, ctx: &'a Context, } fn name_attr( attr: gimli::AttributeValue, mut file: DebugFile, unit: gimli::UnitRef, ctx: &Context, recursion_limit: usize, ) -> Result, Error> where R: gimli::Reader, { if recursion_limit == 0 { return Ok(None); } match attr { gimli::AttributeValue::UnitRef(offset) => { name_entry(file, unit, offset, ctx, recursion_limit) } gimli::AttributeValue::DebugInfoRef(dr) => { let sections = unit.dwarf; let (unit, offset) = ctx.find_unit(dr, file)?; let unit = gimli::UnitRef::new(sections, unit); name_entry(file, unit, offset, ctx, recursion_limit) } gimli::AttributeValue::DebugInfoRefSup(dr) => { if let Some(sup_sections) = unit.dwarf.sup.as_ref() { file = DebugFile::Supplementary; let (unit, offset) = ctx.find_unit(dr, file)?; let unit = gimli::UnitRef::new(sup_sections, unit); name_entry(file, unit, offset, ctx, recursion_limit) } else { Ok(None) } } _ => Ok(None), } } fn name_entry( file: DebugFile, unit: gimli::UnitRef, offset: gimli::UnitOffset, ctx: &Context, recursion_limit: usize, ) -> Result, Error> where R: gimli::Reader, { 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, file, unit, ctx, recursion_limit - 1); } Ok(None) }