diff options
Diffstat (limited to 'vendor/addr2line/src/lib.rs')
-rw-r--r-- | vendor/addr2line/src/lib.rs | 1729 |
1 files changed, 1729 insertions, 0 deletions
diff --git a/vendor/addr2line/src/lib.rs b/vendor/addr2line/src/lib.rs new file mode 100644 index 0000000..3270aad --- /dev/null +++ b/vendor/addr2line/src/lib.rs @@ -0,0 +1,1729 @@ +//! This crate provides a cross-platform library and binary for translating addresses into +//! function names, file names and line numbers. Given an address in an executable or an +//! offset in a section of a relocatable object, it uses the debugging information to +//! figure out which file name and line number are associated with it. +//! +//! When used as a library, files must first be loaded using the +//! [`object`](https://github.com/gimli-rs/object) crate. +//! A context can then be created with [`Context::new`](./struct.Context.html#method.new). +//! The context caches some of the parsed information so that multiple lookups are +//! efficient. +//! Location information is obtained with +//! [`Context::find_location`](./struct.Context.html#method.find_location) or +//! [`Context::find_location_range`](./struct.Context.html#method.find_location_range). +//! Function information is obtained with +//! [`Context::find_frames`](./struct.Context.html#method.find_frames), which returns +//! a frame for each inline function. Each frame contains both name and location. +//! +//! The crate has an example CLI wrapper around the library which provides some of +//! the functionality of the `addr2line` command line tool distributed with [GNU +//! binutils](https://www.gnu.org/software/binutils/). +//! +//! Currently this library only provides information from the DWARF debugging information, +//! which is parsed using [`gimli`](https://github.com/gimli-rs/gimli). The example CLI +//! wrapper also uses symbol table information provided by the `object` crate. +#![deny(missing_docs)] +#![no_std] + +#[cfg(feature = "std")] +extern crate std; + +#[allow(unused_imports)] +#[macro_use] +extern crate alloc; + +#[cfg(feature = "fallible-iterator")] +pub extern crate fallible_iterator; +pub extern crate gimli; +#[cfg(feature = "object")] +pub extern crate object; + +use alloc::borrow::Cow; +use alloc::boxed::Box; +#[cfg(feature = "object")] +use alloc::rc::Rc; +use alloc::string::{String, ToString}; +use alloc::sync::Arc; +use alloc::vec::Vec; + +use core::cmp::{self, Ordering}; +use core::iter; +use core::marker::PhantomData; +use core::mem; +use core::num::NonZeroU64; +use core::ops::ControlFlow; +use core::u64; + +use crate::function::{Function, Functions, InlinedFunction}; +use crate::lazy::LazyCell; + +#[cfg(feature = "smallvec")] +mod maybe_small { + pub type Vec<T> = smallvec::SmallVec<[T; 16]>; + pub type IntoIter<T> = smallvec::IntoIter<[T; 16]>; +} +#[cfg(not(feature = "smallvec"))] +mod maybe_small { + pub type Vec<T> = alloc::vec::Vec<T>; + pub type IntoIter<T> = alloc::vec::IntoIter<T>; +} + +#[cfg(all(feature = "std", feature = "object", feature = "memmap2"))] +/// A simple builtin split DWARF loader. +pub mod builtin_split_dwarf_loader; +mod function; +mod lazy; + +type Error = gimli::Error; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum DebugFile { + Primary, + Supplementary, + Dwo, +} + +/// Operations that consult debug information may require additional files +/// to be loaded if split DWARF is being used. This enum returns the result +/// of the operation in the `Break` variant, or information about the split +/// DWARF that is required and a continuation to invoke once it is available +/// in the `Continue` variant. +/// +/// This enum is intended to be used in a loop like so: +/// ```no_run +/// # use addr2line::*; +/// # use std::sync::Arc; +/// # let ctx: Context<gimli::EndianRcSlice<gimli::RunTimeEndian>> = todo!(); +/// # let do_split_dwarf_load = |load: SplitDwarfLoad<gimli::EndianRcSlice<gimli::RunTimeEndian>>| -> Option<Arc<gimli::Dwarf<gimli::EndianRcSlice<gimli::RunTimeEndian>>>> { None }; +/// const ADDRESS: u64 = 0xdeadbeef; +/// let mut r = ctx.find_frames(ADDRESS); +/// let result = loop { +/// match r { +/// LookupResult::Output(result) => break result, +/// LookupResult::Load { load, continuation } => { +/// let dwo = do_split_dwarf_load(load); +/// r = continuation.resume(dwo); +/// } +/// } +/// }; +/// ``` +pub enum LookupResult<L: LookupContinuation> { + /// The lookup requires split DWARF data to be loaded. + Load { + /// The information needed to find the split DWARF data. + load: SplitDwarfLoad<<L as LookupContinuation>::Buf>, + /// The continuation to resume with the loaded split DWARF data. + continuation: L, + }, + /// The lookup has completed and produced an output. + Output(<L as LookupContinuation>::Output), +} + +/// This trait represents a partially complete operation that can be resumed +/// once a load of needed split DWARF data is completed or abandoned by the +/// API consumer. +pub trait LookupContinuation: Sized { + /// The final output of this operation. + type Output; + /// The type of reader used. + type Buf: gimli::Reader; + + /// Resumes the operation with the provided data. + /// + /// After the caller loads the split DWARF data required, call this + /// method to resume the operation. The return value of this method + /// indicates if the computation has completed or if further data is + /// required. + /// + /// If the additional data cannot be located, or the caller does not + /// support split DWARF, `resume(None)` can be used to continue the + /// operation with the data that is available. + fn resume(self, input: Option<Arc<gimli::Dwarf<Self::Buf>>>) -> LookupResult<Self>; +} + +impl<L: LookupContinuation> LookupResult<L> { + /// Callers that do not handle split DWARF can call `skip_all_loads` + /// to fast-forward to the end result. This result is produced with + /// the data that is available and may be less accurate than the + /// the results that would be produced if the caller did properly + /// support split DWARF. + pub fn skip_all_loads(mut self) -> L::Output { + loop { + self = match self { + LookupResult::Output(t) => return t, + LookupResult::Load { continuation, .. } => continuation.resume(None), + }; + } + } + + fn map<T, F: FnOnce(L::Output) -> T>(self, f: F) -> LookupResult<MappedLookup<T, L, F>> { + match self { + LookupResult::Output(t) => LookupResult::Output(f(t)), + LookupResult::Load { load, continuation } => LookupResult::Load { + load, + continuation: MappedLookup { + original: continuation, + mutator: f, + }, + }, + } + } + + fn unwrap(self) -> L::Output { + match self { + LookupResult::Output(t) => t, + LookupResult::Load { .. } => unreachable!("Internal API misuse"), + } + } +} + +/// The state necessary to perform address to line translation. +/// +/// Constructing a `Context` is somewhat costly, so users should aim to reuse `Context`s +/// when performing lookups for many addresses in the same executable. +pub struct Context<R: gimli::Reader> { + sections: Arc<gimli::Dwarf<R>>, + unit_ranges: Box<[UnitRange]>, + units: Box<[ResUnit<R>]>, + sup_units: Box<[SupUnit<R>]>, +} + +/// The type of `Context` that supports the `new` method. +#[cfg(feature = "std-object")] +pub type ObjectContext = Context<gimli::EndianRcSlice<gimli::RunTimeEndian>>; + +#[cfg(feature = "std-object")] +impl Context<gimli::EndianRcSlice<gimli::RunTimeEndian>> { + /// Construct a new `Context`. + /// + /// The resulting `Context` uses `gimli::EndianRcSlice<gimli::RunTimeEndian>`. + /// This means it is not thread safe, has no lifetime constraints (since it copies + /// the input data), and works for any endianity. + /// + /// Performance sensitive applications may want to use `Context::from_dwarf` + /// with a more specialised `gimli::Reader` implementation. + #[inline] + pub fn new<'data: 'file, 'file, O: object::Object<'data, 'file>>( + file: &'file O, + ) -> Result<Self, Error> { + Self::new_with_sup(file, None) + } + + /// Construct a new `Context`. + /// + /// Optionally also use a supplementary object file. + /// + /// The resulting `Context` uses `gimli::EndianRcSlice<gimli::RunTimeEndian>`. + /// This means it is not thread safe, has no lifetime constraints (since it copies + /// the input data), and works for any endianity. + /// + /// Performance sensitive applications may want to use `Context::from_dwarf` + /// with a more specialised `gimli::Reader` implementation. + pub fn new_with_sup<'data: 'file, 'file, O: object::Object<'data, 'file>>( + file: &'file O, + sup_file: Option<&'file O>, + ) -> Result<Self, Error> { + let endian = if file.is_little_endian() { + gimli::RunTimeEndian::Little + } else { + gimli::RunTimeEndian::Big + }; + + fn load_section<'data: 'file, 'file, O, Endian>( + id: gimli::SectionId, + file: &'file O, + endian: Endian, + ) -> Result<gimli::EndianRcSlice<Endian>, Error> + where + O: object::Object<'data, 'file>, + Endian: gimli::Endianity, + { + use object::ObjectSection; + + let data = file + .section_by_name(id.name()) + .and_then(|section| section.uncompressed_data().ok()) + .unwrap_or(Cow::Borrowed(&[])); + Ok(gimli::EndianRcSlice::new(Rc::from(&*data), endian)) + } + + let mut dwarf = gimli::Dwarf::load(|id| load_section(id, file, endian))?; + if let Some(sup_file) = sup_file { + dwarf.load_sup(|id| load_section(id, sup_file, endian))?; + } + Context::from_dwarf(dwarf) + } +} + +impl<R: gimli::Reader> Context<R> { + /// Construct a new `Context` from DWARF sections. + /// + /// This method does not support using a supplementary object file. + pub fn from_sections( + debug_abbrev: gimli::DebugAbbrev<R>, + debug_addr: gimli::DebugAddr<R>, + debug_aranges: gimli::DebugAranges<R>, + debug_info: gimli::DebugInfo<R>, + debug_line: gimli::DebugLine<R>, + debug_line_str: gimli::DebugLineStr<R>, + debug_ranges: gimli::DebugRanges<R>, + debug_rnglists: gimli::DebugRngLists<R>, + debug_str: gimli::DebugStr<R>, + debug_str_offsets: gimli::DebugStrOffsets<R>, + default_section: R, + ) -> Result<Self, Error> { + Self::from_dwarf(gimli::Dwarf { + debug_abbrev, + debug_addr, + debug_aranges, + debug_info, + debug_line, + debug_line_str, + debug_str, + debug_str_offsets, + debug_types: default_section.clone().into(), + locations: gimli::LocationLists::new( + default_section.clone().into(), + default_section.into(), + ), + ranges: gimli::RangeLists::new(debug_ranges, debug_rnglists), + file_type: gimli::DwarfFileType::Main, + sup: None, + abbreviations_cache: gimli::AbbreviationsCache::new(), + }) + } + + /// Construct a new `Context` from an existing [`gimli::Dwarf`] object. + #[inline] + pub fn from_dwarf(sections: gimli::Dwarf<R>) -> Result<Context<R>, Error> { + let sections = Arc::new(sections); + let (unit_ranges, units) = Context::parse_units(§ions)?; + let sup_units = if let Some(sup) = sections.sup.as_ref() { + Context::parse_sup(sup)? + } else { + Vec::new() + }; + Ok(Context { + sections, + unit_ranges: unit_ranges.into_boxed_slice(), + units: units.into_boxed_slice(), + sup_units: sup_units.into_boxed_slice(), + }) + } + + /// Finds the CUs for the function address given. + /// + /// There might be multiple CUs whose range contains this address. + /// Weak symbols have shown up in the wild which cause this to happen + /// but otherwise this can happen if the CU has non-contiguous functions + /// but only reports a single range. + /// + /// Consequently we return an iterator for all CUs which may contain the + /// address, and the caller must check if there is actually a function or + /// location in the CU for that address. + fn find_units(&self, probe: u64) -> impl Iterator<Item = &ResUnit<R>> { + self.find_units_range(probe, probe + 1) + .map(|(unit, _range)| unit) + } + + /// Finds the CUs covering the range of addresses given. + /// + /// The range is [low, high) (ie, the upper bound is exclusive). This can return multiple + /// ranges for the same unit. + #[inline] + fn find_units_range( + &self, + probe_low: u64, + probe_high: u64, + ) -> impl Iterator<Item = (&ResUnit<R>, &gimli::Range)> { + // First up find the position in the array which could have our function + // address. + let pos = match self + .unit_ranges + .binary_search_by_key(&probe_high, |i| i.range.begin) + { + // Although unlikely, we could find an exact match. + Ok(i) => i + 1, + // No exact match was found, but this probe would fit at slot `i`. + // This means that slot `i` is bigger than `probe`, along with all + // indices greater than `i`, so we need to search all previous + // entries. + Err(i) => i, + }; + + // Once we have our index we iterate backwards from that position + // looking for a matching CU. + self.unit_ranges[..pos] + .iter() + .rev() + .take_while(move |i| { + // We know that this CU's start is beneath the probe already because + // of our sorted array. + debug_assert!(i.range.begin <= probe_high); + + // Each entry keeps track of the maximum end address seen so far, + // starting from the beginning of the array of unit ranges. We're + // iterating in reverse so if our probe is beyond the maximum range + // of this entry, then it's guaranteed to not fit in any prior + // entries, so we break out. + probe_low < i.max_end + }) + .filter_map(move |i| { + // If this CU doesn't actually contain this address, move to the + // next CU. + if probe_low >= i.range.end || probe_high <= i.range.begin { + return None; + } + Some((&self.units[i.unit_id], &i.range)) + }) + } + + /// Find the DWARF unit corresponding to the given virtual memory address. + pub fn find_dwarf_and_unit( + &self, + probe: u64, + ) -> LookupResult< + impl LookupContinuation<Output = Option<(&gimli::Dwarf<R>, &gimli::Unit<R>)>, Buf = R>, + > { + let mut units_iter = self.find_units(probe); + if let Some(unit) = units_iter.next() { + return LoopingLookup::new_lookup( + unit.find_function_or_location(probe, self), + move |r| { + ControlFlow::Break(match r { + Ok((Some(_), _)) | Ok((_, Some(_))) => { + let (_file, sections, unit) = unit + .dwarf_and_unit_dwo(self) + // We've already been through both error cases here to get to this point. + .unwrap() + .unwrap(); + Some((sections, unit)) + } + _ => match units_iter.next() { + Some(next_unit) => { + return ControlFlow::Continue( + next_unit.find_function_or_location(probe, self), + ); + } + None => None, + }, + }) + }, + ); + } + + LoopingLookup::new_complete(None) + } + + /// Find the source file and line corresponding to the given virtual memory address. + pub fn find_location(&self, probe: u64) -> Result<Option<Location<'_>>, Error> { + for unit in self.find_units(probe) { + if let Some(location) = unit.find_location(probe, &self.sections)? { + return Ok(Some(location)); + } + } + Ok(None) + } + + /// Return source file and lines for a range of addresses. For each location it also + /// returns the address and size of the range of the underlying instructions. + pub fn find_location_range( + &self, + probe_low: u64, + probe_high: u64, + ) -> Result<LocationRangeIter<'_, R>, Error> { + LocationRangeIter::new(self, probe_low, probe_high) + } + + /// Return an iterator for the function frames corresponding to the given virtual + /// memory address. + /// + /// If the probe address is not for an inline function then only one frame is + /// returned. + /// + /// If the probe address is for an inline function then the first frame corresponds + /// to the innermost inline function. Subsequent frames contain the caller and call + /// location, until an non-inline caller is reached. + pub fn find_frames( + &self, + probe: u64, + ) -> LookupResult<impl LookupContinuation<Output = Result<FrameIter<'_, R>, Error>, Buf = R>> + { + let mut units_iter = self.find_units(probe); + if let Some(unit) = units_iter.next() { + LoopingLookup::new_lookup(unit.find_function_or_location(probe, self), move |r| { + ControlFlow::Break(match r { + Err(e) => Err(e), + Ok((Some(function), location)) => { + let inlined_functions = function.find_inlined_functions(probe); + Ok(FrameIter(FrameIterState::Frames(FrameIterFrames { + unit, + sections: &self.sections, + function, + inlined_functions, + next: location, + }))) + } + Ok((None, Some(location))) => { + Ok(FrameIter(FrameIterState::Location(Some(location)))) + } + Ok((None, None)) => match units_iter.next() { + Some(next_unit) => { + return ControlFlow::Continue( + next_unit.find_function_or_location(probe, self), + ); + } + None => Ok(FrameIter(FrameIterState::Empty)), + }, + }) + }) + } else { + LoopingLookup::new_complete(Ok(FrameIter(FrameIterState::Empty))) + } + } + + /// Preload units for `probe`. + /// + /// The iterator returns pairs of `SplitDwarfLoad`s containing the + /// information needed to locate and load split DWARF for `probe` and + /// a matching callback to invoke once that data is available. + /// + /// If this method is called, and all of the returned closures are invoked, + /// addr2line guarantees that any future API call for the address `probe` + /// will not require the loading of any split DWARF. + /// + /// ```no_run + /// # use addr2line::*; + /// # use std::sync::Arc; + /// # let ctx: Context<gimli::EndianRcSlice<gimli::RunTimeEndian>> = todo!(); + /// # let do_split_dwarf_load = |load: SplitDwarfLoad<gimli::EndianRcSlice<gimli::RunTimeEndian>>| -> Option<Arc<gimli::Dwarf<gimli::EndianRcSlice<gimli::RunTimeEndian>>>> { None }; + /// const ADDRESS: u64 = 0xdeadbeef; + /// ctx.preload_units(ADDRESS).for_each(|(load, callback)| { + /// let dwo = do_split_dwarf_load(load); + /// callback(dwo); + /// }); + /// + /// let frames_iter = match ctx.find_frames(ADDRESS) { + /// LookupResult::Output(result) => result, + /// LookupResult::Load { .. } => unreachable!("addr2line promised we wouldn't get here"), + /// }; + /// + /// // ... + /// ``` + pub fn preload_units( + &'_ self, + probe: u64, + ) -> impl Iterator< + Item = ( + SplitDwarfLoad<R>, + impl FnOnce(Option<Arc<gimli::Dwarf<R>>>) -> Result<(), gimli::Error> + '_, + ), + > { + self.find_units(probe) + .filter_map(move |unit| match unit.dwarf_and_unit_dwo(self) { + LookupResult::Output(_) => None, + LookupResult::Load { load, continuation } => Some((load, |result| { + continuation.resume(result).unwrap().map(|_| ()) + })), + }) + } + + /// Initialize all line data structures. This is used for benchmarks. + #[doc(hidden)] + pub fn parse_lines(&self) -> Result<(), Error> { + for unit in self.units.iter() { + unit.parse_lines(&self.sections)?; + } + Ok(()) + } + + /// Initialize all function data structures. This is used for benchmarks. + #[doc(hidden)] + pub fn parse_functions(&self) -> Result<(), Error> { + for unit in self.units.iter() { + unit.parse_functions(self).skip_all_loads()?; + } + Ok(()) + } + + /// Initialize all inlined function data structures. This is used for benchmarks. + #[doc(hidden)] + pub fn parse_inlined_functions(&self) -> Result<(), Error> { + for unit in self.units.iter() { + unit.parse_inlined_functions(self).skip_all_loads()?; + } + Ok(()) + } +} + +struct UnitRange { + unit_id: usize, + max_end: u64, + range: gimli::Range, +} + +struct ResUnit<R: gimli::Reader> { + offset: gimli::DebugInfoOffset<R::Offset>, + dw_unit: gimli::Unit<R>, + lang: Option<gimli::DwLang>, + lines: LazyCell<Result<Lines, Error>>, + funcs: LazyCell<Result<Functions<R>, Error>>, + dwo: LazyCell<Result<Option<Box<(Arc<gimli::Dwarf<R>>, gimli::Unit<R>)>>, Error>>, +} + +struct SupUnit<R: gimli::Reader> { + offset: gimli::DebugInfoOffset<R::Offset>, + dw_unit: gimli::Unit<R>, +} + +impl<R: gimli::Reader> Context<R> { + fn parse_units(sections: &gimli::Dwarf<R>) -> Result<(Vec<UnitRange>, Vec<ResUnit<R>>), Error> { + // Find all the references to compilation units in .debug_aranges. + // Note that we always also iterate through all of .debug_info to + // find compilation units, because .debug_aranges may be missing some. + let mut aranges = Vec::new(); + let mut headers = sections.debug_aranges.headers(); + while let Some(header) = headers.next()? { + aranges.push((header.debug_info_offset(), header.offset())); + } + aranges.sort_by_key(|i| i.0); + + let mut unit_ranges = Vec::new(); + let mut res_units = Vec::new(); + let mut units = sections.units(); + while let Some(header) = units.next()? { + let unit_id = res_units.len(); + let offset = match header.offset().as_debug_info_offset() { + Some(offset) => offset, + None => continue, + }; + // We mainly want compile units, but we may need to follow references to entries + // within other units for function names. We don't need anything from type units. + match header.type_() { + gimli::UnitType::Type { .. } | gimli::UnitType::SplitType { .. } => continue, + _ => {} + } + let dw_unit = match sections.unit(header) { + Ok(dw_unit) => dw_unit, + Err(_) => continue, + }; + + let mut lang = None; + let mut have_unit_range = false; + { + let mut entries = dw_unit.entries_raw(None)?; + + let abbrev = match entries.read_abbreviation()? { + Some(abbrev) => abbrev, + None => continue, + }; + + let mut ranges = RangeAttributes::default(); + for spec in abbrev.attributes() { + let attr = entries.read_attribute(*spec)?; + 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(sections.address(&dw_unit, 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(sections.address(&dw_unit, index)?); + } + gimli::AttributeValue::Udata(val) => ranges.size = Some(val), + _ => {} + }, + gimli::DW_AT_ranges => { + ranges.ranges_offset = + sections.attr_ranges_offset(&dw_unit, attr.value())?; + } + gimli::DW_AT_language => { + if let gimli::AttributeValue::Language(val) = attr.value() { + lang = Some(val); + } + } + _ => {} + } + } + + // Find the address ranges for the CU, using in order of preference: + // - DW_AT_ranges + // - .debug_aranges + // - DW_AT_low_pc/DW_AT_high_pc + // + // Using DW_AT_ranges before .debug_aranges is possibly an arbitrary choice, + // but the feeling is that DW_AT_ranges is more likely to be reliable or complete + // if it is present. + // + // .debug_aranges must be used before DW_AT_low_pc/DW_AT_high_pc because + // it has been observed on macOS that DW_AT_ranges was not emitted even for + // discontiguous CUs. + let i = match ranges.ranges_offset { + Some(_) => None, + None => aranges.binary_search_by_key(&offset, |x| x.0).ok(), + }; + if let Some(mut i) = i { + // There should be only one set per CU, but in practice multiple + // sets have been observed. This is probably a compiler bug, but + // either way we need to handle it. + while i > 0 && aranges[i - 1].0 == offset { + i -= 1; + } + for (_, aranges_offset) in aranges[i..].iter().take_while(|x| x.0 == offset) { + let aranges_header = sections.debug_aranges.header(*aranges_offset)?; + let mut aranges = aranges_header.entries(); + while let Some(arange) = aranges.next()? { + if arange.length() != 0 { + unit_ranges.push(UnitRange { + range: arange.range(), + unit_id, + max_end: 0, + }); + have_unit_range = true; + } + } + } + } else { + have_unit_range |= ranges.for_each_range(sections, &dw_unit, |range| { + unit_ranges.push(UnitRange { + range, + unit_id, + max_end: 0, + }); + })?; + } + } + + let lines = LazyCell::new(); + if !have_unit_range { + // The unit did not declare any ranges. + // Try to get some ranges from the line program sequences. + if let Some(ref ilnp) = dw_unit.line_program { + if let Ok(lines) = lines + .borrow_with(|| Lines::parse(&dw_unit, ilnp.clone(), sections)) + .as_ref() + { + for sequence in lines.sequences.iter() { + unit_ranges.push(UnitRange { + range: gimli::Range { + begin: sequence.start, + end: sequence.end, + }, + unit_id, + max_end: 0, + }) + } + } + } + } + + res_units.push(ResUnit { + offset, + dw_unit, + lang, + lines, + funcs: LazyCell::new(), + dwo: LazyCell::new(), + }); + } + + // Sort this for faster lookup in `find_unit_and_address` below. + unit_ranges.sort_by_key(|i| i.range.begin); + + // Calculate the `max_end` field now that we've determined the order of + // CUs. + let mut max = 0; + for i in unit_ranges.iter_mut() { + max = max.max(i.range.end); + i.max_end = max; + } + + Ok((unit_ranges, res_units)) + } + + fn parse_sup(sections: &gimli::Dwarf<R>) -> Result<Vec<SupUnit<R>>, Error> { + let mut sup_units = Vec::new(); + let mut units = sections.units(); + while let Some(header) = units.next()? { + let offset = match header.offset().as_debug_info_offset() { + Some(offset) => offset, + None => continue, + }; + let dw_unit = match sections.unit(header) { + Ok(dw_unit) => dw_unit, + Err(_) => continue, + }; + sup_units.push(SupUnit { dw_unit, offset }); + } + Ok(sup_units) + } + + // Find the unit containing the given offset, and convert the offset into a unit offset. + fn find_unit( + &self, + offset: gimli::DebugInfoOffset<R::Offset>, + file: DebugFile, + ) -> Result<(&gimli::Unit<R>, gimli::UnitOffset<R::Offset>), Error> { + let unit = match file { + DebugFile::Primary => { + match self + .units + .binary_search_by_key(&offset.0, |unit| unit.offset.0) + { + // There is never a DIE at the unit offset or before the first unit. + Ok(_) | Err(0) => return Err(gimli::Error::NoEntryAtGivenOffset), + Err(i) => &self.units[i - 1].dw_unit, + } + } + DebugFile::Supplementary => { + match self + .sup_units + .binary_search_by_key(&offset.0, |unit| unit.offset.0) + { + // There is never a DIE at the unit offset or before the first unit. + Ok(_) | Err(0) => return Err(gimli::Error::NoEntryAtGivenOffset), + Err(i) => &self.sup_units[i - 1].dw_unit, + } + } + DebugFile::Dwo => return Err(gimli::Error::NoEntryAtGivenOffset), + }; + + let unit_offset = offset + .to_unit_offset(&unit.header) + .ok_or(gimli::Error::NoEntryAtGivenOffset)?; + Ok((unit, unit_offset)) + } +} + +struct Lines { + files: Box<[String]>, + sequences: Box<[LineSequence]>, +} + +impl Lines { + fn parse<R: gimli::Reader>( + dw_unit: &gimli::Unit<R>, + ilnp: gimli::IncompleteLineProgram<R, R::Offset>, + sections: &gimli::Dwarf<R>, + ) -> Result<Self, Error> { + let mut sequences = Vec::new(); + let mut sequence_rows = Vec::<LineRow>::new(); + let mut rows = ilnp.rows(); + while let Some((_, row)) = rows.next_row()? { + if row.end_sequence() { + if let Some(start) = sequence_rows.first().map(|x| x.address) { + let end = row.address(); + let mut rows = Vec::new(); + mem::swap(&mut rows, &mut sequence_rows); + sequences.push(LineSequence { + start, + end, + rows: rows.into_boxed_slice(), + }); + } + continue; + } + + let address = row.address(); + let file_index = row.file_index(); + let line = row.line().map(NonZeroU64::get).unwrap_or(0) as u32; + let column = match row.column() { + gimli::ColumnType::LeftEdge => 0, + gimli::ColumnType::Column(x) => x.get() as u32, + }; + + if let Some(last_row) = sequence_rows.last_mut() { + if last_row.address == address { + last_row.file_index = file_index; + last_row.line = line; + last_row.column = column; + continue; + } + } + + sequence_rows.push(LineRow { + address, + file_index, + line, + column, + }); + } + sequences.sort_by_key(|x| x.start); + + let mut files = Vec::new(); + let header = rows.header(); + match header.file(0) { + Some(file) => files.push(render_file(dw_unit, file, header, sections)?), + None => files.push(String::from("")), // DWARF version <= 4 may not have 0th index + } + let mut index = 1; + while let Some(file) = header.file(index) { + files.push(render_file(dw_unit, file, header, sections)?); + index += 1; + } + + Ok(Self { + files: files.into_boxed_slice(), + sequences: sequences.into_boxed_slice(), + }) + } +} + +fn render_file<R: gimli::Reader>( + dw_unit: &gimli::Unit<R>, + file: &gimli::FileEntry<R, R::Offset>, + header: &gimli::LineProgramHeader<R, R::Offset>, + sections: &gimli::Dwarf<R>, +) -> Result<String, gimli::Error> { + let mut path = if let Some(ref comp_dir) = dw_unit.comp_dir { + comp_dir.to_string_lossy()?.into_owned() + } else { + String::new() + }; + + // The directory index 0 is defined to correspond to the compilation unit directory. + if file.directory_index() != 0 { + if let Some(directory) = file.directory(header) { + path_push( + &mut path, + sections + .attr_string(dw_unit, directory)? + .to_string_lossy()? + .as_ref(), + ); + } + } + + path_push( + &mut path, + sections + .attr_string(dw_unit, file.path_name())? + .to_string_lossy()? + .as_ref(), + ); + + Ok(path) +} + +struct LineSequence { + start: u64, + end: u64, + rows: Box<[LineRow]>, +} + +struct LineRow { + address: u64, + file_index: u64, + line: u32, + column: u32, +} + +/// This struct contains the information needed to find split DWARF data +/// and to produce a `gimli::Dwarf<R>` for it. +pub struct SplitDwarfLoad<R> { + /// The dwo id, for looking up in a DWARF package, or for + /// verifying an unpacked dwo found on the file system + pub dwo_id: gimli::DwoId, + /// The compilation directory `path` is relative to. + pub comp_dir: Option<R>, + /// A path on the filesystem, relative to `comp_dir` to find this dwo. + pub path: Option<R>, + /// Once the split DWARF data is loaded, the loader is expected + /// to call [make_dwo(parent)](gimli::read::Dwarf::make_dwo) before + /// returning the data. + pub parent: Arc<gimli::Dwarf<R>>, +} + +struct SimpleLookup<T, R, F> +where + F: FnOnce(Option<Arc<gimli::Dwarf<R>>>) -> T, + R: gimli::Reader, +{ + f: F, + phantom: PhantomData<(T, R)>, +} + +impl<T, R, F> SimpleLookup<T, R, F> +where + F: FnOnce(Option<Arc<gimli::Dwarf<R>>>) -> T, + R: gimli::Reader, +{ + fn new_complete(t: F::Output) -> LookupResult<SimpleLookup<T, R, F>> { + LookupResult::Output(t) + } + + fn new_needs_load(load: SplitDwarfLoad<R>, f: F) -> LookupResult<SimpleLookup<T, R, F>> { + LookupResult::Load { + load, + continuation: SimpleLookup { + f, + phantom: PhantomData, + }, + } + } +} + +impl<T, R, F> LookupContinuation for SimpleLookup<T, R, F> +where + F: FnOnce(Option<Arc<gimli::Dwarf<R>>>) -> T, + R: gimli::Reader, +{ + type Output = T; + type Buf = R; + + fn resume(self, v: Option<Arc<gimli::Dwarf<Self::Buf>>>) -> LookupResult<Self> { + LookupResult::Output((self.f)(v)) + } +} + +struct MappedLookup<T, L, F> +where + L: LookupContinuation, + F: FnOnce(L::Output) -> T, +{ + original: L, + mutator: F, +} + +impl<T, L, F> LookupContinuation for MappedLookup<T, L, F> +where + L: LookupContinuation, + F: FnOnce(L::Output) -> T, +{ + type Output = T; + type Buf = L::Buf; + + fn resume(self, v: Option<Arc<gimli::Dwarf<Self::Buf>>>) -> LookupResult<Self> { + match self.original.resume(v) { + LookupResult::Output(t) => LookupResult::Output((self.mutator)(t)), + LookupResult::Load { load, continuation } => LookupResult::Load { + load, + continuation: MappedLookup { + original: continuation, + mutator: self.mutator, + }, + }, + } + } +} + +/// Some functions (e.g. `find_frames`) require considering multiple +/// compilation units, each of which might require their own split DWARF +/// lookup (and thus produce a continuation). +/// +/// We store the underlying continuation here as well as a mutator function +/// that will either a) decide that the result of this continuation is +/// what is needed and mutate it to the final result or b) produce another +/// `LookupResult`. `new_lookup` will in turn eagerly drive any non-continuation +/// `LookupResult` with successive invocations of the mutator, until a new +/// continuation or a final result is produced. And finally, the impl of +/// `LookupContinuation::resume` will call `new_lookup` each time the +/// computation is resumed. +struct LoopingLookup<T, L, F> +where + L: LookupContinuation, + F: FnMut(L::Output) -> ControlFlow<T, LookupResult<L>>, +{ + continuation: L, + mutator: F, +} + +impl<T, L, F> LoopingLookup<T, L, F> +where + L: LookupContinuation, + F: FnMut(L::Output) -> ControlFlow<T, LookupResult<L>>, +{ + fn new_complete(t: T) -> LookupResult<Self> { + LookupResult::Output(t) + } + + fn new_lookup(mut r: LookupResult<L>, mut mutator: F) -> LookupResult<Self> { + // Drive the loop eagerly so that we only ever have to represent one state + // (the r == ControlFlow::Continue state) in LoopingLookup. + loop { + match r { + LookupResult::Output(l) => match mutator(l) { + ControlFlow::Break(t) => return LookupResult::Output(t), + ControlFlow::Continue(r2) => { + r = r2; + } + }, + LookupResult::Load { load, continuation } => { + return LookupResult::Load { + load, + continuation: LoopingLookup { + continuation, + mutator, + }, + }; + } + } + } + } +} + +impl<T, L, F> LookupContinuation for LoopingLookup<T, L, F> +where + L: LookupContinuation, + F: FnMut(L::Output) -> ControlFlow<T, LookupResult<L>>, +{ + type Output = T; + type Buf = L::Buf; + + fn resume(self, v: Option<Arc<gimli::Dwarf<Self::Buf>>>) -> LookupResult<Self> { + let r = self.continuation.resume(v); + LoopingLookup::new_lookup(r, self.mutator) + } +} + +impl<R: gimli::Reader> ResUnit<R> { + fn dwarf_and_unit_dwo<'unit, 'ctx: 'unit>( + &'unit self, + ctx: &'ctx Context<R>, + ) -> LookupResult< + SimpleLookup< + Result<(DebugFile, &'unit gimli::Dwarf<R>, &'unit gimli::Unit<R>), Error>, + R, + impl FnOnce( + Option<Arc<gimli::Dwarf<R>>>, + ) + -> Result<(DebugFile, &'unit gimli::Dwarf<R>, &'unit gimli::Unit<R>), Error>, + >, + > { + loop { + break SimpleLookup::new_complete(match self.dwo.borrow() { + Some(Ok(Some(v))) => Ok((DebugFile::Dwo, &*v.0, &v.1)), + Some(Ok(None)) => Ok((DebugFile::Primary, &*ctx.sections, &self.dw_unit)), + Some(Err(e)) => Err(*e), + None => { + let dwo_id = match self.dw_unit.dwo_id { + None => { + self.dwo.borrow_with(|| Ok(None)); + continue; + } + Some(dwo_id) => dwo_id, + }; + + let comp_dir = self.dw_unit.comp_dir.clone(); + + let dwo_name = self.dw_unit.dwo_name().and_then(|s| { + if let Some(s) = s { + Ok(Some(ctx.sections.attr_string(&self.dw_unit, s)?)) + } else { + Ok(None) + } + }); + + let path = match dwo_name { + Ok(v) => v, + Err(e) => { + self.dwo.borrow_with(|| Err(e)); + continue; + } + }; + + let process_dwo = move |dwo_dwarf: Option<Arc<gimli::Dwarf<R>>>| { + let dwo_dwarf = match dwo_dwarf { + None => return Ok(None), + Some(dwo_dwarf) => dwo_dwarf, + }; + let mut dwo_units = dwo_dwarf.units(); + let dwo_header = match dwo_units.next()? { + Some(dwo_header) => dwo_header, + None => return Ok(None), + }; + + let mut dwo_unit = dwo_dwarf.unit(dwo_header)?; + dwo_unit.copy_relocated_attributes(&self.dw_unit); + Ok(Some(Box::new((dwo_dwarf, dwo_unit)))) + }; + + return SimpleLookup::new_needs_load( + SplitDwarfLoad { + dwo_id, + comp_dir, + path, + parent: ctx.sections.clone(), + }, + move |dwo_dwarf| match self.dwo.borrow_with(|| process_dwo(dwo_dwarf)) { + Ok(Some(v)) => Ok((DebugFile::Dwo, &*v.0, &v.1)), + Ok(None) => Ok((DebugFile::Primary, &*ctx.sections, &self.dw_unit)), + Err(e) => Err(*e), + }, + ); + } + }); + } + } + + fn parse_lines(&self, sections: &gimli::Dwarf<R>) -> Result<Option<&Lines>, Error> { + // NB: line information is always stored in the main debug file so this does not need + // to handle DWOs. + let ilnp = match self.dw_unit.line_program { + Some(ref ilnp) => ilnp, + None => return Ok(None), + }; + self.lines + .borrow_with(|| Lines::parse(&self.dw_unit, ilnp.clone(), sections)) + .as_ref() + .map(Some) + .map_err(Error::clone) + } + + fn parse_functions_dwarf_and_unit( + &self, + unit: &gimli::Unit<R>, + sections: &gimli::Dwarf<R>, + ) -> Result<&Functions<R>, Error> { + self.funcs + .borrow_with(|| Functions::parse(unit, sections)) + .as_ref() + .map_err(Error::clone) + } + + fn parse_functions<'unit, 'ctx: 'unit>( + &'unit self, + ctx: &'ctx Context<R>, + ) -> LookupResult<impl LookupContinuation<Output = Result<&'unit Functions<R>, Error>, Buf = R>> + { + self.dwarf_and_unit_dwo(ctx).map(move |r| { + let (_file, sections, unit) = r?; + self.parse_functions_dwarf_and_unit(unit, sections) + }) + } + fn parse_inlined_functions<'unit, 'ctx: 'unit>( + &'unit self, + ctx: &'ctx Context<R>, + ) -> LookupResult<impl LookupContinuation<Output = Result<(), Error>, Buf = R> + 'unit> { + self.dwarf_and_unit_dwo(ctx).map(move |r| { + let (file, sections, unit) = r?; + self.funcs + .borrow_with(|| Functions::parse(unit, sections)) + .as_ref() + .map_err(Error::clone)? + .parse_inlined_functions(file, unit, ctx, sections) + }) + } + + fn find_location( + &self, + probe: u64, + sections: &gimli::Dwarf<R>, + ) -> Result<Option<Location<'_>>, Error> { + if let Some(mut iter) = LocationRangeUnitIter::new(self, sections, probe, probe + 1)? { + match iter.next() { + None => Ok(None), + Some((_addr, _len, loc)) => Ok(Some(loc)), + } + } else { + Ok(None) + } + } + + #[inline] + fn find_location_range( + &self, + probe_low: u64, + probe_high: u64, + sections: &gimli::Dwarf<R>, + ) -> Result<Option<LocationRangeUnitIter<'_>>, Error> { + LocationRangeUnitIter::new(self, sections, probe_low, probe_high) + } + + fn find_function_or_location<'unit, 'ctx: 'unit>( + &'unit self, + probe: u64, + ctx: &'ctx Context<R>, + ) -> LookupResult< + impl LookupContinuation< + Output = Result<(Option<&'unit Function<R>>, Option<Location<'unit>>), Error>, + Buf = R, + >, + > { + self.dwarf_and_unit_dwo(ctx).map(move |r| { + let (file, sections, unit) = r?; + let functions = self.parse_functions_dwarf_and_unit(unit, sections)?; + let function = match functions.find_address(probe) { + Some(address) => { + let function_index = functions.addresses[address].function; + let (offset, ref function) = functions.functions[function_index]; + Some( + function + .borrow_with(|| Function::parse(offset, file, unit, ctx, sections)) + .as_ref() + .map_err(Error::clone)?, + ) + } + None => None, + }; + let location = self.find_location(probe, sections)?; + Ok((function, location)) + }) + } +} + +/// Iterator over `Location`s in a range of addresses, returned by `Context::find_location_range`. +pub struct LocationRangeIter<'ctx, R: gimli::Reader> { + unit_iter: Box<dyn Iterator<Item = (&'ctx ResUnit<R>, &'ctx gimli::Range)> + 'ctx>, + iter: Option<LocationRangeUnitIter<'ctx>>, + + probe_low: u64, + probe_high: u64, + sections: &'ctx gimli::Dwarf<R>, +} + +impl<'ctx, R: gimli::Reader> LocationRangeIter<'ctx, R> { + #[inline] + fn new(ctx: &'ctx Context<R>, probe_low: u64, probe_high: u64) -> Result<Self, Error> { + let sections = &ctx.sections; + let unit_iter = ctx.find_units_range(probe_low, probe_high); + Ok(Self { + unit_iter: Box::new(unit_iter), + iter: None, + probe_low, + probe_high, + sections, + }) + } + + fn next_loc(&mut self) -> Result<Option<(u64, u64, Location<'ctx>)>, Error> { + loop { + let iter = self.iter.take(); + match iter { + None => match self.unit_iter.next() { + Some((unit, range)) => { + self.iter = unit.find_location_range( + cmp::max(self.probe_low, range.begin), + cmp::min(self.probe_high, range.end), + self.sections, + )?; + } + None => return Ok(None), + }, + Some(mut iter) => { + if let item @ Some(_) = iter.next() { + self.iter = Some(iter); + return Ok(item); + } + } + } + } + } +} + +impl<'ctx, R> Iterator for LocationRangeIter<'ctx, R> +where + R: gimli::Reader + 'ctx, +{ + type Item = (u64, u64, Location<'ctx>); + + #[inline] + fn next(&mut self) -> Option<Self::Item> { + match self.next_loc() { + Err(_) => None, + Ok(loc) => loc, + } + } +} + +#[cfg(feature = "fallible-iterator")] +impl<'ctx, R> fallible_iterator::FallibleIterator for LocationRangeIter<'ctx, R> +where + R: gimli::Reader + 'ctx, +{ + type Item = (u64, u64, Location<'ctx>); + type Error = Error; + + #[inline] + fn next(&mut self) -> Result<Option<Self::Item>, Self::Error> { + self.next_loc() + } +} + +struct LocationRangeUnitIter<'ctx> { + lines: &'ctx Lines, + seqs: &'ctx [LineSequence], + seq_idx: usize, + row_idx: usize, + probe_high: u64, +} + +impl<'ctx> LocationRangeUnitIter<'ctx> { + fn new<R: gimli::Reader>( + resunit: &'ctx ResUnit<R>, + sections: &gimli::Dwarf<R>, + probe_low: u64, + probe_high: u64, + ) -> Result<Option<Self>, Error> { + let lines = resunit.parse_lines(sections)?; + + if let Some(lines) = lines { + // Find index for probe_low. + let seq_idx = lines.sequences.binary_search_by(|sequence| { + if probe_low < sequence.start { + Ordering::Greater + } else if probe_low >= sequence.end { + Ordering::Less + } else { + Ordering::Equal + } + }); + let seq_idx = match seq_idx { + Ok(x) => x, + Err(0) => 0, // probe below sequence, but range could overlap + Err(_) => lines.sequences.len(), + }; + + let row_idx = if let Some(seq) = lines.sequences.get(seq_idx) { + let idx = seq.rows.binary_search_by(|row| row.address.cmp(&probe_low)); + match idx { + Ok(x) => x, + Err(0) => 0, // probe below sequence, but range could overlap + Err(x) => x - 1, + } + } else { + 0 + }; + + Ok(Some(Self { + lines, + seqs: &*lines.sequences, + seq_idx, + row_idx, + probe_high, + })) + } else { + Ok(None) + } + } +} + +impl<'ctx> Iterator for LocationRangeUnitIter<'ctx> { + type Item = (u64, u64, Location<'ctx>); + + fn next(&mut self) -> Option<(u64, u64, Location<'ctx>)> { + while let Some(seq) = self.seqs.get(self.seq_idx) { + if seq.start >= self.probe_high { + break; + } + + match seq.rows.get(self.row_idx) { + Some(row) => { + if row.address >= self.probe_high { + break; + } + + let file = self + .lines + .files + .get(row.file_index as usize) + .map(String::as_str); + let nextaddr = seq + .rows + .get(self.row_idx + 1) + .map(|row| row.address) + .unwrap_or(seq.end); + + let item = ( + row.address, + nextaddr - row.address, + Location { + file, + line: if row.line != 0 { Some(row.line) } else { None }, + column: if row.column != 0 { + Some(row.column) + } else { + None + }, + }, + ); + self.row_idx += 1; + + return Some(item); + } + None => { + self.seq_idx += 1; + self.row_idx = 0; + } + } + } + None + } +} + +fn path_push(path: &mut String, p: &str) { + if has_unix_root(p) || has_windows_root(p) { + *path = p.to_string(); + } else { + let dir_separator = if has_windows_root(path.as_str()) { + '\\' + } else { + '/' + }; + + if !path.is_empty() && !path.ends_with(dir_separator) { + path.push(dir_separator); + } + *path += p; + } +} + +/// Check if the path in the given string has a unix style root +fn has_unix_root(p: &str) -> bool { + p.starts_with('/') +} + +/// Check if the path in the given string has a windows style root +fn has_windows_root(p: &str) -> bool { + p.starts_with('\\') || p.get(1..3) == Some(":\\") +} +struct RangeAttributes<R: gimli::Reader> { + low_pc: Option<u64>, + high_pc: Option<u64>, + size: Option<u64>, + ranges_offset: Option<gimli::RangeListsOffset<<R as gimli::Reader>::Offset>>, +} + +impl<R: gimli::Reader> Default for RangeAttributes<R> { + fn default() -> Self { + RangeAttributes { + low_pc: None, + high_pc: None, + size: None, + ranges_offset: None, + } + } +} + +impl<R: gimli::Reader> RangeAttributes<R> { + fn for_each_range<F: FnMut(gimli::Range)>( + &self, + sections: &gimli::Dwarf<R>, + unit: &gimli::Unit<R>, + mut f: F, + ) -> Result<bool, Error> { + let mut added_any = false; + let mut add_range = |range: gimli::Range| { + if range.begin < range.end { + f(range); + added_any = true + } + }; + if let Some(ranges_offset) = self.ranges_offset { + let mut range_list = sections.ranges(unit, ranges_offset)?; + while let Some(range) = range_list.next()? { + add_range(range); + } + } else if let (Some(begin), Some(end)) = (self.low_pc, self.high_pc) { + add_range(gimli::Range { begin, end }); + } else if let (Some(begin), Some(size)) = (self.low_pc, self.size) { + add_range(gimli::Range { + begin, + end: begin + size, + }); + } + Ok(added_any) + } +} + +/// An iterator over function frames. +pub struct FrameIter<'ctx, R>(FrameIterState<'ctx, R>) +where + R: gimli::Reader; + +enum FrameIterState<'ctx, R> +where + R: gimli::Reader, +{ + Empty, + Location(Option<Location<'ctx>>), + Frames(FrameIterFrames<'ctx, R>), +} + +struct FrameIterFrames<'ctx, R> +where + R: gimli::Reader, +{ + unit: &'ctx ResUnit<R>, + sections: &'ctx gimli::Dwarf<R>, + function: &'ctx Function<R>, + inlined_functions: iter::Rev<maybe_small::IntoIter<&'ctx InlinedFunction<R>>>, + next: Option<Location<'ctx>>, +} + +impl<'ctx, R> FrameIter<'ctx, R> +where + R: gimli::Reader + 'ctx, +{ + /// Advances the iterator and returns the next frame. + pub fn next(&mut self) -> Result<Option<Frame<'ctx, R>>, Error> { + let frames = match &mut self.0 { + FrameIterState::Empty => return Ok(None), + FrameIterState::Location(location) => { + // We can't move out of a mutable reference, so use `take` instead. + let location = location.take(); + self.0 = FrameIterState::Empty; + return Ok(Some(Frame { + dw_die_offset: None, + function: None, + location, + })); + } + FrameIterState::Frames(frames) => frames, + }; + + let loc = frames.next.take(); + let func = match frames.inlined_functions.next() { + Some(func) => func, + None => { + let frame = Frame { + dw_die_offset: Some(frames.function.dw_die_offset), + function: frames.function.name.clone().map(|name| FunctionName { + name, + language: frames.unit.lang, + }), + location: loc, + }; + self.0 = FrameIterState::Empty; + return Ok(Some(frame)); + } + }; + + let mut next = Location { + file: None, + line: if func.call_line != 0 { + Some(func.call_line) + } else { + None + }, + column: if func.call_column != 0 { + Some(func.call_column) + } else { + None + }, + }; + if let Some(call_file) = func.call_file { + if let Some(lines) = frames.unit.parse_lines(frames.sections)? { + next.file = lines.files.get(call_file as usize).map(String::as_str); + } + } + frames.next = Some(next); + + Ok(Some(Frame { + dw_die_offset: Some(func.dw_die_offset), + function: func.name.clone().map(|name| FunctionName { + name, + language: frames.unit.lang, + }), + location: loc, + })) + } +} + +#[cfg(feature = "fallible-iterator")] +impl<'ctx, R> fallible_iterator::FallibleIterator for FrameIter<'ctx, R> +where + R: gimli::Reader + 'ctx, +{ + type Item = Frame<'ctx, R>; + type Error = Error; + + #[inline] + fn next(&mut self) -> Result<Option<Frame<'ctx, R>>, Error> { + self.next() + } +} + +/// A function frame. +pub struct Frame<'ctx, R: gimli::Reader> { + /// The DWARF unit offset corresponding to the DIE of the function. + pub dw_die_offset: Option<gimli::UnitOffset<R::Offset>>, + /// The name of the function. + pub function: Option<FunctionName<R>>, + /// The source location corresponding to this frame. + pub location: Option<Location<'ctx>>, +} + +/// A function name. +pub struct FunctionName<R: gimli::Reader> { + /// The name of the function. + pub name: R, + /// The language of the compilation unit containing this function. + pub language: Option<gimli::DwLang>, +} + +impl<R: gimli::Reader> FunctionName<R> { + /// The raw name of this function before demangling. + pub fn raw_name(&self) -> Result<Cow<'_, str>, Error> { + self.name.to_string_lossy() + } + + /// The name of this function after demangling (if applicable). + pub fn demangle(&self) -> Result<Cow<'_, str>, Error> { + self.raw_name().map(|x| demangle_auto(x, self.language)) + } +} + +/// Demangle a symbol name using the demangling scheme for the given language. +/// +/// Returns `None` if demangling failed or is not required. +#[allow(unused_variables)] +pub fn demangle(name: &str, language: gimli::DwLang) -> Option<String> { + match language { + #[cfg(feature = "rustc-demangle")] + gimli::DW_LANG_Rust => rustc_demangle::try_demangle(name) + .ok() + .as_ref() + .map(|x| format!("{:#}", x)), + #[cfg(feature = "cpp_demangle")] + gimli::DW_LANG_C_plus_plus + | gimli::DW_LANG_C_plus_plus_03 + | gimli::DW_LANG_C_plus_plus_11 + | gimli::DW_LANG_C_plus_plus_14 => cpp_demangle::Symbol::new(name) + .ok() + .and_then(|x| x.demangle(&Default::default()).ok()), + _ => None, + } +} + +/// Apply 'best effort' demangling of a symbol name. +/// +/// If `language` is given, then only the demangling scheme for that language +/// is used. +/// +/// If `language` is `None`, then heuristics are used to determine how to +/// demangle the name. Currently, these heuristics are very basic. +/// +/// If demangling fails or is not required, then `name` is returned unchanged. +pub fn demangle_auto(name: Cow<'_, str>, language: Option<gimli::DwLang>) -> Cow<'_, str> { + match language { + Some(language) => demangle(name.as_ref(), language), + None => demangle(name.as_ref(), gimli::DW_LANG_Rust) + .or_else(|| demangle(name.as_ref(), gimli::DW_LANG_C_plus_plus)), + } + .map(Cow::from) + .unwrap_or(name) +} + +/// A source location. +pub struct Location<'a> { + /// The file name. + pub file: Option<&'a str>, + /// The line number. + pub line: Option<u32>, + /// The column number. + pub column: Option<u32>, +} + +#[cfg(test)] +mod tests { + #[test] + fn context_is_send() { + fn assert_is_send<T: Send>() {} + assert_is_send::<crate::Context<gimli::read::EndianSlice<'_, gimli::LittleEndian>>>(); + } +} |