blob: 3270aad7e1b301a7f4bc30188d5faa80d02cfc2f [file] [log] [blame] [edit]
//! 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(&sections)?;
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>>>();
}
}