//! 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 = smallvec::SmallVec<[T; 16]>; pub type IntoIter = smallvec::IntoIter<[T; 16]>; } #[cfg(not(feature = "smallvec"))] mod maybe_small { pub type Vec = alloc::vec::Vec; pub type IntoIter = alloc::vec::IntoIter; } #[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> = todo!(); /// # let do_split_dwarf_load = |load: SplitDwarfLoad>| -> Option>>> { 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 { /// The lookup requires split DWARF data to be loaded. Load { /// The information needed to find the split DWARF data. load: SplitDwarfLoad<::Buf>, /// The continuation to resume with the loaded split DWARF data. continuation: L, }, /// The lookup has completed and produced an output. Output(::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>>) -> LookupResult; } impl LookupResult { /// 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>(self, f: F) -> LookupResult> { 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 { sections: Arc>, unit_ranges: Box<[UnitRange]>, units: Box<[ResUnit]>, sup_units: Box<[SupUnit]>, } /// The type of `Context` that supports the `new` method. #[cfg(feature = "std-object")] pub type ObjectContext = Context>; #[cfg(feature = "std-object")] impl Context> { /// Construct a new `Context`. /// /// The resulting `Context` uses `gimli::EndianRcSlice`. /// 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::new_with_sup(file, None) } /// Construct a new `Context`. /// /// Optionally also use a supplementary object file. /// /// The resulting `Context` uses `gimli::EndianRcSlice`. /// 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 { 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, 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 Context { /// 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, debug_addr: gimli::DebugAddr, debug_aranges: gimli::DebugAranges, debug_info: gimli::DebugInfo, debug_line: gimli::DebugLine, debug_line_str: gimli::DebugLineStr, debug_ranges: gimli::DebugRanges, debug_rnglists: gimli::DebugRngLists, debug_str: gimli::DebugStr, debug_str_offsets: gimli::DebugStrOffsets, default_section: R, ) -> Result { 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) -> Result, 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> { 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, &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, &gimli::Unit)>, 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>, 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, 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, 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> = todo!(); /// # let do_split_dwarf_load = |load: SplitDwarfLoad>| -> Option>>> { 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, impl FnOnce(Option>>) -> 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 { offset: gimli::DebugInfoOffset, dw_unit: gimli::Unit, lang: Option, lines: LazyCell>, funcs: LazyCell, Error>>, dwo: LazyCell>, gimli::Unit)>>, Error>>, } struct SupUnit { offset: gimli::DebugInfoOffset, dw_unit: gimli::Unit, } impl Context { fn parse_units(sections: &gimli::Dwarf) -> Result<(Vec, Vec>), 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) -> Result>, 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, file: DebugFile, ) -> Result<(&gimli::Unit, gimli::UnitOffset), 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( dw_unit: &gimli::Unit, ilnp: gimli::IncompleteLineProgram, sections: &gimli::Dwarf, ) -> Result { let mut sequences = Vec::new(); let mut sequence_rows = Vec::::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( dw_unit: &gimli::Unit, file: &gimli::FileEntry, header: &gimli::LineProgramHeader, sections: &gimli::Dwarf, ) -> Result { 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` for it. pub struct SplitDwarfLoad { /// 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, /// A path on the filesystem, relative to `comp_dir` to find this dwo. pub path: Option, /// 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>, } struct SimpleLookup where F: FnOnce(Option>>) -> T, R: gimli::Reader, { f: F, phantom: PhantomData<(T, R)>, } impl SimpleLookup where F: FnOnce(Option>>) -> T, R: gimli::Reader, { fn new_complete(t: F::Output) -> LookupResult> { LookupResult::Output(t) } fn new_needs_load(load: SplitDwarfLoad, f: F) -> LookupResult> { LookupResult::Load { load, continuation: SimpleLookup { f, phantom: PhantomData, }, } } } impl LookupContinuation for SimpleLookup where F: FnOnce(Option>>) -> T, R: gimli::Reader, { type Output = T; type Buf = R; fn resume(self, v: Option>>) -> LookupResult { LookupResult::Output((self.f)(v)) } } struct MappedLookup where L: LookupContinuation, F: FnOnce(L::Output) -> T, { original: L, mutator: F, } impl LookupContinuation for MappedLookup where L: LookupContinuation, F: FnOnce(L::Output) -> T, { type Output = T; type Buf = L::Buf; fn resume(self, v: Option>>) -> LookupResult { 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 where L: LookupContinuation, F: FnMut(L::Output) -> ControlFlow>, { continuation: L, mutator: F, } impl LoopingLookup where L: LookupContinuation, F: FnMut(L::Output) -> ControlFlow>, { fn new_complete(t: T) -> LookupResult { LookupResult::Output(t) } fn new_lookup(mut r: LookupResult, mut mutator: F) -> LookupResult { // 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 LookupContinuation for LoopingLookup where L: LookupContinuation, F: FnMut(L::Output) -> ControlFlow>, { type Output = T; type Buf = L::Buf; fn resume(self, v: Option>>) -> LookupResult { let r = self.continuation.resume(v); LoopingLookup::new_lookup(r, self.mutator) } } impl ResUnit { fn dwarf_and_unit_dwo<'unit, 'ctx: 'unit>( &'unit self, ctx: &'ctx Context, ) -> LookupResult< SimpleLookup< Result<(DebugFile, &'unit gimli::Dwarf, &'unit gimli::Unit), Error>, R, impl FnOnce( Option>>, ) -> Result<(DebugFile, &'unit gimli::Dwarf, &'unit gimli::Unit), 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>>| { 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) -> Result, 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, sections: &gimli::Dwarf, ) -> Result<&Functions, 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, ) -> LookupResult, 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, ) -> LookupResult, 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, ) -> Result>, 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, ) -> Result>, Error> { LocationRangeUnitIter::new(self, sections, probe_low, probe_high) } fn find_function_or_location<'unit, 'ctx: 'unit>( &'unit self, probe: u64, ctx: &'ctx Context, ) -> LookupResult< impl LookupContinuation< Output = Result<(Option<&'unit Function>, Option>), 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, &'ctx gimli::Range)> + 'ctx>, iter: Option>, probe_low: u64, probe_high: u64, sections: &'ctx gimli::Dwarf, } impl<'ctx, R: gimli::Reader> LocationRangeIter<'ctx, R> { #[inline] fn new(ctx: &'ctx Context, probe_low: u64, probe_high: u64) -> Result { 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)>, 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 { 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, 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( resunit: &'ctx ResUnit, sections: &gimli::Dwarf, probe_low: u64, probe_high: u64, ) -> Result, 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 { low_pc: Option, high_pc: Option, size: Option, ranges_offset: Option::Offset>>, } impl Default for RangeAttributes { fn default() -> Self { RangeAttributes { low_pc: None, high_pc: None, size: None, ranges_offset: None, } } } impl RangeAttributes { fn for_each_range( &self, sections: &gimli::Dwarf, unit: &gimli::Unit, mut f: F, ) -> Result { 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>), Frames(FrameIterFrames<'ctx, R>), } struct FrameIterFrames<'ctx, R> where R: gimli::Reader, { unit: &'ctx ResUnit, sections: &'ctx gimli::Dwarf, function: &'ctx Function, inlined_functions: iter::Rev>>, next: Option>, } 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>, 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>, 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>, /// The name of the function. pub function: Option>, /// The source location corresponding to this frame. pub location: Option>, } /// A function name. pub struct FunctionName { /// The name of the function. pub name: R, /// The language of the compilation unit containing this function. pub language: Option, } impl FunctionName { /// The raw name of this function before demangling. pub fn raw_name(&self) -> Result, Error> { self.name.to_string_lossy() } /// The name of this function after demangling (if applicable). pub fn demangle(&self) -> Result, 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 { 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) -> 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, /// The column number. pub column: Option, } #[cfg(test)] mod tests { #[test] fn context_is_send() { fn assert_is_send() {} assert_is_send::>>(); } }