diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index d8ffee2..b1513a1 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -67,7 +67,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Install rust - run: rustup update 1.42.0 && rustup default 1.42.0 + run: rustup update 1.55.0 && rustup default 1.55.0 - name: Build run: cargo build diff --git a/Cargo.toml b/Cargo.toml index 76d97d5..b76e3f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,8 +11,9 @@ readme = "./README.md" repository = "https://github.com/gimli-rs/addr2line" [dependencies] -gimli = { version = "0.27.0", default-features = false, features = ["read"] } +gimli = { version = "0.27.1", default-features = false, features = ["read"] } fallible-iterator = { version = "0.2", default-features = false, optional = true } +memmap2 = { version = "0.5.5", optional = true } object = { version = "0.30.0", default-features = false, features = ["read"], optional = true } smallvec = { version = "1", default-features = false, optional = true } rustc-demangle = { version = "0.1", optional = true } @@ -25,7 +26,6 @@ alloc = { version = '1.0.0', optional = true, package = 'rustc-std-workspace-all compiler_builtins = { version = '0.1.2', optional = true } [dev-dependencies] -memmap2 = "0.5.5" clap = "3.1.6" backtrace = "0.3.13" findshlibs = "0.10" @@ -41,7 +41,7 @@ debug = true codegen-units = 1 [features] -default = ["rustc-demangle", "cpp_demangle", "std-object", "fallible-iterator", "smallvec"] +default = ["rustc-demangle", "cpp_demangle", "std-object", "fallible-iterator", "smallvec", "memmap2"] std = ["gimli/std"] std-object = ["std", "object", "object/std", "object/compression", "gimli/endian-reader"] @@ -52,7 +52,7 @@ rustc-dep-of-std = ['core', 'alloc', 'compiler_builtins', 'gimli/rustc-dep-of-st [[test]] name = "output_equivalence" harness = false -required-features = ["std-object"] +required-features = ["default"] [[test]] name = "correctness" @@ -64,4 +64,4 @@ required-features = ["std-object"] [[example]] name = "addr2line" -required-features = ["std-object"] +required-features = ["default"] diff --git a/benches/bench.rs b/benches/bench.rs index 0ac7ac5..64ce5f4 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -10,6 +10,7 @@ use std::env; use std::fs::File; use std::path::{self, PathBuf}; +use addr2line::LookupResultExt; use object::{Object, ObjectSection, ObjectSymbol}; fn release_fixture_path() -> PathBuf { @@ -224,7 +225,7 @@ fn context_query_with_functions_rc(b: &mut test::Bencher) { let ctx = addr2line::Context::new(file).unwrap(); // Ensure nothing is lazily loaded. for addr in &addresses { - let mut frames = ctx.find_frames(*addr).unwrap(); + let mut frames = ctx.find_frames(*addr).skip_all_loads().unwrap(); while let Ok(Some(ref frame)) = frames.next() { test::black_box(frame); } @@ -232,7 +233,7 @@ fn context_query_with_functions_rc(b: &mut test::Bencher) { b.iter(|| { for addr in &addresses { - let mut frames = ctx.find_frames(*addr).unwrap(); + let mut frames = ctx.find_frames(*addr).skip_all_loads().unwrap(); while let Ok(Some(ref frame)) = frames.next() { test::black_box(frame); } @@ -253,7 +254,7 @@ fn context_query_with_functions_slice(b: &mut test::Bencher) { let ctx = addr2line::Context::from_dwarf(dwarf).unwrap(); // Ensure nothing is lazily loaded. for addr in &addresses { - let mut frames = ctx.find_frames(*addr).unwrap(); + let mut frames = ctx.find_frames(*addr).skip_all_loads().unwrap(); while let Ok(Some(ref frame)) = frames.next() { test::black_box(frame); } @@ -261,7 +262,7 @@ fn context_query_with_functions_slice(b: &mut test::Bencher) { b.iter(|| { for addr in &addresses { - let mut frames = ctx.find_frames(*addr).unwrap(); + let mut frames = ctx.find_frames(*addr).skip_all_loads().unwrap(); while let Ok(Some(ref frame)) = frames.next() { test::black_box(frame); } @@ -314,7 +315,7 @@ fn context_new_and_query_with_functions_rc(b: &mut test::Bencher) { b.iter(|| { let ctx = addr2line::Context::new(file).unwrap(); for addr in addresses.iter().take(100) { - let mut frames = ctx.find_frames(*addr).unwrap(); + let mut frames = ctx.find_frames(*addr).skip_all_loads().unwrap(); while let Ok(Some(ref frame)) = frames.next() { test::black_box(frame); } @@ -334,7 +335,7 @@ fn context_new_and_query_with_functions_slice(b: &mut test::Bencher) { let dwarf = dwarf_borrow(&dwarf); let ctx = addr2line::Context::from_dwarf(dwarf).unwrap(); for addr in addresses.iter().take(100) { - let mut frames = ctx.find_frames(*addr).unwrap(); + let mut frames = ctx.find_frames(*addr).skip_all_loads().unwrap(); while let Ok(Some(ref frame)) = frames.next() { test::black_box(frame); } diff --git a/examples/addr2line.rs b/examples/addr2line.rs index fa4d8e4..2b1d83a 100644 --- a/examples/addr2line.rs +++ b/examples/addr2line.rs @@ -16,6 +16,8 @@ use fallible_iterator::FallibleIterator; use object::{Object, ObjectSection, SymbolMap, SymbolMapName}; use typed_arena::Arena; +#[cfg(not(unix))] +use addr2line::LookupResultExt; use addr2line::{Context, Location}; fn parse_uint_from_hex_string(string: &str) -> Option { @@ -201,6 +203,13 @@ fn main() { dwarf.load_sup(&mut load_sup_section).unwrap(); } + #[cfg(unix)] + let mut split_dwarf_loader = addr2line::builtin_split_dwarf_loader::SplitDwarfLoader::new( + |data, endian| { + gimli::EndianSlice::new(arena_data.alloc(Cow::Owned(data.into_owned())), endian) + }, + Some(std::path::PathBuf::from(path)), + ); let ctx = Context::from_dwarf(dwarf).unwrap(); let stdin = std::io::stdin(); @@ -227,7 +236,12 @@ fn main() { if do_functions || do_inlines { let mut printed_anything = false; if let Some(probe) = probe { - let mut frames = ctx.find_frames(probe).unwrap().enumerate(); + let frames = ctx.find_frames(probe); + #[cfg(unix)] + let frames = split_dwarf_loader.run(frames).unwrap(); + #[cfg(not(unix))] + let frames = frames.skip_all_loads().unwrap(); + let mut frames = frames.enumerate(); while let Some((i, frame)) = frames.next().unwrap() { if pretty && i != 0 { print!(" (inlined by) "); diff --git a/src/builtin_split_dwarf_loader.rs b/src/builtin_split_dwarf_loader.rs new file mode 100644 index 0000000..d912afc --- /dev/null +++ b/src/builtin_split_dwarf_loader.rs @@ -0,0 +1,155 @@ +use alloc::borrow::Cow; +use alloc::sync::Arc; +use std::fs::File; +use std::path::PathBuf; + +use object::Object; + +use crate::{LookupContinuation, LookupResult}; + +#[cfg(unix)] +fn convert_path>( + r: &R, +) -> Result { + use std::ffi::OsStr; + use std::os::unix::ffi::OsStrExt; + let bytes = r.to_slice()?; + let s = OsStr::from_bytes(&*bytes); + Ok(PathBuf::from(s)) +} + +fn load_section<'data: 'file, 'file, O, R, F>( + id: gimli::SectionId, + file: &'file O, + endian: R::Endian, + loader: &mut F, +) -> Result +where + O: object::Object<'data, 'file>, + R: gimli::Reader, + F: FnMut(Cow<'data, [u8]>, R::Endian) -> R, +{ + use object::ObjectSection; + + let data = id + .dwo_name() + .and_then(|dwo_name| { + file.section_by_name(dwo_name) + .and_then(|section| section.uncompressed_data().ok()) + }) + .unwrap_or(Cow::Borrowed(&[])); + Ok(loader(data, endian)) +} + +/// A simple builtin split DWARF loader. +pub struct SplitDwarfLoader +where + R: gimli::Reader, + F: FnMut(Cow<[u8]>, R::Endian) -> R, +{ + loader: F, + dwarf_package: Option>, +} + +impl SplitDwarfLoader +where + R: gimli::Reader, + F: FnMut(Cow<[u8]>, R::Endian) -> R, +{ + fn load_dwarf_package(loader: &mut F, path: Option) -> Option> { + let mut path = path.map(Ok).unwrap_or_else(std::env::current_exe).ok()?; + let dwp_extension = path + .extension() + .map(|previous_extension| { + let mut previous_extension = previous_extension.to_os_string(); + previous_extension.push(".dwp"); + previous_extension + }) + .unwrap_or_else(|| "dwp".into()); + path.set_extension(dwp_extension); + let file = File::open(&path).ok()?; + let map = unsafe { memmap2::Mmap::map(&file).ok()? }; + let dwp = object::File::parse(&*map).ok()?; + + let endian = if dwp.is_little_endian() { + gimli::RunTimeEndian::Little + } else { + gimli::RunTimeEndian::Big + }; + + let empty = loader(Cow::Borrowed(&[]), endian); + gimli::DwarfPackage::load( + |section_id| load_section(section_id, &dwp, endian, loader), + empty, + ) + .ok() + } + + /// Create a new split DWARF loader. + pub fn new(mut loader: F, path: Option) -> SplitDwarfLoader { + let dwarf_package = SplitDwarfLoader::load_dwarf_package(&mut loader, path); + SplitDwarfLoader { + loader, + dwarf_package, + } + } + + /// Run the provided `LookupResult` to completion, loading any necessary + /// split DWARF along the way. + pub fn run(&mut self, mut l: LookupResult) -> L::Output + where + L: LookupContinuation, + { + loop { + let (load, continuation) = match l { + LookupResult::Break(output) => break output, + LookupResult::Continue(load) => load, + }; + + let mut r: Option>> = None; + if let Some(dwp) = self.dwarf_package.as_ref() { + if let Ok(Some(cu)) = dwp.find_cu(load.dwo_id, &load.parent) { + r = Some(Arc::new(cu)); + } + } + + if r.is_none() { + let mut path = PathBuf::new(); + if let Some(p) = load.comp_dir.as_ref() { + if let Ok(p) = convert_path(p) { + path.push(p); + } + } + + if let Some(p) = load.path.as_ref() { + if let Ok(p) = convert_path(p) { + path.push(p); + } + } + + if let Ok(file) = File::open(&path) { + if let Ok(map) = unsafe { memmap2::Mmap::map(&file) } { + if let Ok(file) = object::File::parse(&*map) { + let endian = if file.is_little_endian() { + gimli::RunTimeEndian::Little + } else { + gimli::RunTimeEndian::Big + }; + + r = gimli::Dwarf::load(|id| { + load_section(id, &file, endian, &mut self.loader) + }) + .ok() + .map(|mut dwo_dwarf| { + dwo_dwarf.make_dwo(&load.parent); + Arc::new(dwo_dwarf) + }); + } + } + } + } + + l = continuation.resume(r); + } + } +} diff --git a/src/function.rs b/src/function.rs index 44dc73f..8264e8a 100644 --- a/src/function.rs +++ b/src/function.rs @@ -5,7 +5,7 @@ use core::iter; use crate::lazy::LazyCell; use crate::maybe_small; -use crate::{Error, RangeAttributes, ResDwarf}; +use crate::{DebugFile, Error, ParsedDwarf, RangeAttributes, RawDwarf}; pub(crate) struct Functions { /// List of all `DW_TAG_subprogram` details in the unit. @@ -55,7 +55,7 @@ pub(crate) struct InlinedFunction { } impl Functions { - pub(crate) fn parse(unit: &gimli::Unit, dwarf: &ResDwarf) -> Result, Error> { + pub(crate) fn parse(unit: &gimli::Unit, raw: &RawDwarf) -> Result, Error> { let mut functions = Vec::new(); let mut addresses = Vec::new(); let mut entries = unit.entries_raw(None)?; @@ -74,7 +74,7 @@ impl Functions { } gimli::AttributeValue::DebugAddrIndex(index) => { ranges.low_pc = - Some(dwarf.sections.address(unit, index)?); + Some(raw.sections.address(unit, index)?); } _ => {} }, @@ -84,7 +84,7 @@ impl Functions { } gimli::AttributeValue::DebugAddrIndex(index) => { ranges.high_pc = - Some(dwarf.sections.address(unit, index)?); + Some(raw.sections.address(unit, index)?); } gimli::AttributeValue::Udata(val) => { ranges.size = Some(val) @@ -92,9 +92,8 @@ impl Functions { _ => {} }, gimli::DW_AT_ranges => { - ranges.ranges_offset = dwarf - .sections - .attr_ranges_offset(unit, attr.value())?; + ranges.ranges_offset = + raw.sections.attr_ranges_offset(unit, attr.value())?; } _ => {} }; @@ -104,7 +103,7 @@ impl Functions { } let function_index = functions.len(); - if ranges.for_each_range(&dwarf.sections, unit, |range| { + if ranges.for_each_range(&raw.sections, unit, |range| { addresses.push(FunctionAddress { range, function: function_index, @@ -151,12 +150,13 @@ impl Functions { pub(crate) fn parse_inlined_functions( &self, unit: &gimli::Unit, - dwarf: &ResDwarf, + parsed: &ParsedDwarf, + raw: &RawDwarf, ) -> Result<(), Error> { for function in &*self.functions { function .1 - .borrow_with(|| Function::parse(function.0, unit, dwarf)) + .borrow_with(|| Function::parse(function.0, unit, parsed, raw)) .as_ref() .map_err(Error::clone)?; } @@ -168,7 +168,8 @@ impl Function { pub(crate) fn parse( dw_die_offset: gimli::UnitOffset, unit: &gimli::Unit, - dwarf: &ResDwarf, + parsed: &ParsedDwarf, + raw: &RawDwarf, ) -> Result { let mut entries = unit.entries_raw(Some(dw_die_offset))?; let depth = entries.next_depth(); @@ -181,18 +182,25 @@ impl Function { Ok(ref attr) => { match attr.name() { gimli::DW_AT_linkage_name | gimli::DW_AT_MIPS_linkage_name => { - if let Ok(val) = dwarf.sections.attr_string(unit, attr.value()) { + if let Ok(val) = raw.sections.attr_string(unit, attr.value()) { name = Some(val); } } gimli::DW_AT_name => { if name.is_none() { - name = dwarf.sections.attr_string(unit, attr.value()).ok(); + name = raw.sections.attr_string(unit, attr.value()).ok(); } } gimli::DW_AT_abstract_origin | gimli::DW_AT_specification => { if name.is_none() { - name = name_attr(attr.value(), unit, dwarf, 16)?; + name = name_attr( + attr.value(), + DebugFile::Primary, + unit, + parsed, + raw, + 16, + )?; } } _ => {} @@ -208,7 +216,8 @@ impl Function { &mut entries, depth, unit, - dwarf, + parsed, + raw, &mut inlined_functions, &mut inlined_addresses, 0, @@ -250,7 +259,8 @@ impl Function { entries: &mut gimli::EntriesRaw, depth: isize, unit: &gimli::Unit, - dwarf: &ResDwarf, + parsed: &ParsedDwarf, + raw: &RawDwarf, inlined_functions: &mut Vec>, inlined_addresses: &mut Vec, inlined_depth: usize, @@ -273,7 +283,8 @@ impl Function { abbrev, next_depth, unit, - dwarf, + parsed, + raw, inlined_functions, inlined_addresses, inlined_depth, @@ -348,7 +359,8 @@ impl InlinedFunction { abbrev: &gimli::Abbreviation, depth: isize, unit: &gimli::Unit, - dwarf: &ResDwarf, + parsed: &ParsedDwarf, + raw: &RawDwarf, inlined_functions: &mut Vec>, inlined_addresses: &mut Vec, inlined_depth: usize, @@ -364,35 +376,36 @@ impl InlinedFunction { 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(dwarf.sections.address(unit, index)?); + ranges.low_pc = Some(raw.sections.address(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(dwarf.sections.address(unit, index)?); + ranges.high_pc = Some(raw.sections.address(unit, index)?); } gimli::AttributeValue::Udata(val) => ranges.size = Some(val), _ => {} }, gimli::DW_AT_ranges => { ranges.ranges_offset = - dwarf.sections.attr_ranges_offset(unit, attr.value())?; + raw.sections.attr_ranges_offset(unit, attr.value())?; } gimli::DW_AT_linkage_name | gimli::DW_AT_MIPS_linkage_name => { - if let Ok(val) = dwarf.sections.attr_string(unit, attr.value()) { + if let Ok(val) = raw.sections.attr_string(unit, attr.value()) { name = Some(val); } } gimli::DW_AT_name => { if name.is_none() { - name = dwarf.sections.attr_string(unit, attr.value()).ok(); + name = raw.sections.attr_string(unit, attr.value()).ok(); } } gimli::DW_AT_abstract_origin | gimli::DW_AT_specification => { if name.is_none() { - name = name_attr(attr.value(), unit, dwarf, 16)?; + name = + name_attr(attr.value(), DebugFile::Primary, unit, parsed, raw, 16)?; } } gimli::DW_AT_call_file => { @@ -421,7 +434,7 @@ impl InlinedFunction { call_column, }); - ranges.for_each_range(&dwarf.sections, unit, |range| { + ranges.for_each_range(&raw.sections, unit, |range| { inlined_addresses.push(InlinedFunctionAddress { range, call_depth: inlined_depth, @@ -433,7 +446,8 @@ impl InlinedFunction { entries, depth, unit, - dwarf, + parsed, + raw, inlined_functions, inlined_addresses, inlined_depth + 1, @@ -443,8 +457,10 @@ impl InlinedFunction { fn name_attr( attr: gimli::AttributeValue, + mut file: DebugFile, unit: &gimli::Unit, - dwarf: &ResDwarf, + parsed: &ParsedDwarf, + raw: &RawDwarf, recursion_limit: usize, ) -> Result, Error> where @@ -455,27 +471,34 @@ where } match attr { - gimli::AttributeValue::UnitRef(offset) => name_entry(unit, offset, dwarf, recursion_limit), + gimli::AttributeValue::UnitRef(offset) => { + name_entry(file, unit, offset, parsed, raw, recursion_limit) + } gimli::AttributeValue::DebugInfoRef(dr) => { - let res_unit = dwarf.find_unit(dr)?; + let res_unit = parsed.find_unit(dr, file)?; name_entry( + file, &res_unit.dw_unit, gimli::UnitOffset(dr.0 - res_unit.offset.0), - dwarf, + parsed, + raw, recursion_limit, ) } gimli::AttributeValue::DebugInfoRefSup(dr) => { - if let Some(sup_dwarf) = dwarf.sup.as_ref() { - let res_unit = sup_dwarf.find_unit(dr)?; + if file == DebugFile::Supplementary || raw.sup_sections.is_none() { + Ok(None) + } else { + file = DebugFile::Supplementary; + let res_unit = parsed.find_unit(dr, file)?; name_entry( + file, &res_unit.dw_unit, gimli::UnitOffset(dr.0 - res_unit.offset.0), - sup_dwarf, + parsed, + raw, recursion_limit, ) - } else { - Ok(None) } } _ => Ok(None), @@ -483,9 +506,11 @@ where } fn name_entry( + file: DebugFile, unit: &gimli::Unit, offset: gimli::UnitOffset, - dwarf: &ResDwarf, + parsed: &ParsedDwarf, + raw: &RawDwarf, recursion_limit: usize, ) -> Result, Error> where @@ -497,6 +522,11 @@ where } else { return Err(gimli::Error::NoEntryAtGivenOffset); }; + let sections = if let Some(sections) = raw.sections_for_file(file) { + sections + } else { + return Ok(None); + }; let mut name = None; let mut next = None; @@ -504,12 +534,12 @@ where match entries.read_attribute(*spec) { Ok(ref attr) => match attr.name() { gimli::DW_AT_linkage_name | gimli::DW_AT_MIPS_linkage_name => { - if let Ok(val) = dwarf.sections.attr_string(unit, attr.value()) { + if let Ok(val) = sections.attr_string(unit, attr.value()) { return Ok(Some(val)); } } gimli::DW_AT_name => { - if let Ok(val) = dwarf.sections.attr_string(unit, attr.value()) { + if let Ok(val) = sections.attr_string(unit, attr.value()) { name = Some(val); } } @@ -527,7 +557,7 @@ where } if let Some(next) = next { - return name_attr(next, unit, dwarf, recursion_limit - 1); + return name_attr(next, file, unit, parsed, raw, recursion_limit - 1); } Ok(None) diff --git a/src/lazy.rs b/src/lazy.rs index a34ed17..2df2ed6 100644 --- a/src/lazy.rs +++ b/src/lazy.rs @@ -10,6 +10,10 @@ impl LazyCell { } } + pub fn borrow(&self) -> Option<&T> { + unsafe { &*self.contents.get() }.as_ref() + } + pub fn borrow_with(&self, closure: impl FnOnce() -> T) -> &T { // First check if we're already initialized... let ptr = self.contents.get(); diff --git a/src/lib.rs b/src/lib.rs index 3afa37f..12c20ba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,6 +25,9 @@ #![deny(missing_docs)] #![no_std] +#[cfg(feature = "std")] +extern crate std; + #[allow(unused_imports)] #[macro_use] extern crate alloc; @@ -49,8 +52,10 @@ 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}; @@ -67,17 +72,126 @@ mod maybe_small { pub type IntoIter = alloc::vec::IntoIter; } +#[cfg(all(unix, 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, +} + +/// 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::Break(result) => break result, +/// LookupResult::Continue((load, continuation)) => { +/// let dwo = do_split_dwarf_load(load); +/// r = continuation.resume(dwo); +/// } +/// } +/// }; +/// ``` +pub type LookupResult = ControlFlow< + ::Output, + (SplitDwarfLoad<::Buf>, L), +>; + +/// 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; +} + +/// This trait is used to extend `LookupResult`. +pub trait LookupResultExt { + /// 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. + fn skip_all_loads(self) -> L::Output; +} + +impl LookupResultExt for LookupResult { + fn skip_all_loads(mut self) -> L::Output { + loop { + self = match self { + ControlFlow::Break(t) => return t, + ControlFlow::Continue((_, continuation)) => continuation.resume(None), + }; + } + } +} + +trait LookupResultExtInternal { + fn map T>(self, f: F) -> LookupResult>; + fn unwrap(self) -> L::Output; +} + +impl LookupResultExtInternal for LookupResult { + fn map T>(self, f: F) -> LookupResult> { + match self { + ControlFlow::Break(t) => ControlFlow::Break(f(t)), + ControlFlow::Continue((load, continuation)) => ControlFlow::Continue(( + load, + MappedLookup { + original: continuation, + mutator: f, + }, + )), + } + } + + fn unwrap(self) -> L::Output { + match self { + ControlFlow::Break(t) => t, + ControlFlow::Continue(_) => 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 { - dwarf: ResDwarf, + parsed_dwarf: ParsedDwarf, + raw_dwarf: RawDwarf, } /// The type of `Context` that supports the `new` method. @@ -109,7 +223,7 @@ impl Context> { /// 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_sup` + /// 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, @@ -187,18 +301,21 @@ impl Context { /// Construct a new `Context` from an existing [`gimli::Dwarf`] object. #[inline] - pub fn from_dwarf(sections: gimli::Dwarf) -> Result { - let mut dwarf = ResDwarf::parse(Arc::new(sections))?; - dwarf.sup = match dwarf.sections.sup.clone() { - Some(sup_sections) => Some(Box::new(ResDwarf::parse(sup_sections)?)), - None => None, + pub fn from_dwarf(sections: gimli::Dwarf) -> Result, Error> { + let sup_sections = sections.sup.clone(); + let raw_dwarf = RawDwarf { + sections: Arc::new(sections), + sup_sections, }; - Ok(Context { dwarf }) - } - - /// The dwarf sections associated with this `Context`. - pub fn dwarf(&self) -> &gimli::Dwarf { - &self.dwarf.sections + let mut parsed_dwarf = ParsedDwarf::parse(&raw_dwarf.sections)?; + if let Some(sup) = raw_dwarf.sup_sections.as_ref() { + let sup_dwarf = ParsedDwarf::parse(sup)?; + parsed_dwarf.sup_units = Some(sup_dwarf.units); + } + Ok(Context { + parsed_dwarf, + raw_dwarf, + }) } /// Finds the CUs for the function address given. @@ -229,7 +346,7 @@ impl Context { // First up find the position in the array which could have our function // address. let pos = match self - .dwarf + .parsed_dwarf .unit_ranges .binary_search_by_key(&probe_high, |i| i.range.begin) { @@ -244,7 +361,7 @@ impl Context { // Once we have our index we iterate backwards from that position // looking for a matching CU. - self.dwarf.unit_ranges[..pos] + self.parsed_dwarf.unit_ranges[..pos] .iter() .rev() .take_while(move |i| { @@ -265,25 +382,51 @@ impl Context { if probe_low >= i.range.end || probe_high <= i.range.begin { return None; } - Some((&self.dwarf.units[i.unit_id], &i.range)) + Some((&self.parsed_dwarf.units[i.unit_id], &i.range)) }) } /// Find the DWARF unit corresponding to the given virtual memory address. - pub fn find_dwarf_unit(&self, probe: u64) -> Option<&gimli::Unit> { - for unit in self.find_units(probe) { - match unit.find_function_or_location(probe, &self.dwarf) { - Ok((Some(_), _)) | Ok((_, Some(_))) => return Some(&unit.dw_unit), - _ => {} - } + 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 (dwarf, unit) = unit + .dwarf_and_unit_dwo(self) + // We've already been through both error cases here to get to this point. + .unwrap() + .unwrap(); + Some((&*dwarf.sections, unit)) + } + _ => match units_iter.next() { + Some(next_unit) => { + return ControlFlow::Continue( + next_unit.find_function_or_location(probe, self), + ); + } + None => 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.dwarf.sections)? { + if let Some(location) = unit.find_location(probe, &self.raw_dwarf.sections)? { return Ok(Some(location)); } } @@ -309,33 +452,94 @@ impl Context { /// 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) -> Result, Error> { - for unit in self.find_units(probe) { - match unit.find_function_or_location(probe, &self.dwarf)? { - (Some(function), location) => { - let inlined_functions = function.find_inlined_functions(probe); - return Ok(FrameIter(FrameIterState::Frames(FrameIterFrames { - unit, - sections: &self.dwarf.sections, - function, - inlined_functions, - next: location, - }))); - } - (None, Some(location)) => { - return Ok(FrameIter(FrameIterState::Location(Some(location)))); - } - _ => {} - } + 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.raw_dwarf.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))) } - 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::Break(result) => result, + /// LookupResult::Continue(_) => 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::Break(_) => None, + LookupResult::Continue((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.dwarf.units { - unit.parse_lines(&self.dwarf.sections)?; + for unit in &self.parsed_dwarf.units { + unit.parse_lines(&self.raw_dwarf.sections)?; } Ok(()) } @@ -343,8 +547,8 @@ impl Context { /// Initialize all function data structures. This is used for benchmarks. #[doc(hidden)] pub fn parse_functions(&self) -> Result<(), Error> { - for unit in &self.dwarf.units { - unit.parse_functions(&self.dwarf)?; + for unit in &self.parsed_dwarf.units { + unit.parse_functions(self).skip_all_loads()?; } Ok(()) } @@ -352,8 +556,8 @@ impl Context { /// 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.dwarf.units { - unit.parse_inlined_functions(&self.dwarf)?; + for unit in &self.parsed_dwarf.units { + unit.parse_inlined_functions(self).skip_all_loads()?; } Ok(()) } @@ -365,15 +569,19 @@ struct UnitRange { range: gimli::Range, } -struct ResDwarf { +struct ParsedDwarf { unit_ranges: Vec, units: Vec>, + sup_units: Option>>, +} + +struct RawDwarf { sections: Arc>, - sup: Option>>, + sup_sections: Option>>, } -impl ResDwarf { - fn parse(sections: Arc>) -> Result { +impl ParsedDwarf { + fn parse(sections: &gimli::Dwarf) -> Result { // 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. @@ -484,7 +692,7 @@ impl ResDwarf { } } } else { - have_unit_range |= ranges.for_each_range(§ions, &dw_unit, |range| { + have_unit_range |= ranges.for_each_range(sections, &dw_unit, |range| { unit_ranges.push(UnitRange { range, unit_id, @@ -500,7 +708,7 @@ impl ResDwarf { // 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)) + .borrow_with(|| Lines::parse(&dw_unit, ilnp.clone(), sections)) .as_ref() { for sequence in lines.sequences.iter() { @@ -523,6 +731,7 @@ impl ResDwarf { lang, lines, funcs: LazyCell::new(), + dwo: LazyCell::new(), }); } @@ -537,22 +746,46 @@ impl ResDwarf { i.max_end = max; } - Ok(ResDwarf { + Ok(ParsedDwarf { units: res_units, unit_ranges, - sections, - sup: None, + sup_units: None, }) } - fn find_unit(&self, offset: gimli::DebugInfoOffset) -> Result<&ResUnit, Error> { - match self - .units - .binary_search_by_key(&offset.0, |unit| unit.offset.0) - { + fn find_unit( + &self, + offset: gimli::DebugInfoOffset, + file: DebugFile, + ) -> Result<&ResUnit, Error> { + let units = match file { + DebugFile::Primary => &self.units, + DebugFile::Supplementary => self + .sup_units + .as_ref() + .ok_or(gimli::Error::NoEntryAtGivenOffset)?, + }; + + match 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) => Err(gimli::Error::NoEntryAtGivenOffset), - Err(i) => Ok(&self.units[i - 1]), + Err(i) => Ok(&units[i - 1]), + } + } +} + +impl RawDwarf { + fn dwo(&self, dwo_sections: Arc>) -> RawDwarf { + RawDwarf { + sections: dwo_sections, + sup_sections: self.sup_sections.clone(), + } + } + + fn sections_for_file(&self, file: DebugFile) -> Option<&gimli::Dwarf> { + match file { + DebugFile::Primary => Some(&self.sections), + DebugFile::Supplementary => self.sup_sections.as_deref(), } } } @@ -686,10 +919,252 @@ struct ResUnit { lang: Option, lines: LazyCell>, funcs: LazyCell, Error>>, + dwo: LazyCell, gimli::Unit)>>, Error>>, +} + +/// 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> { + ControlFlow::Break(t) + } + + fn new_needs_load(lookup: SplitDwarfLoad, f: F) -> LookupResult> { + ControlFlow::Continue(( + lookup, + 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 { + ControlFlow::Break((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) { + ControlFlow::Break(t) => ControlFlow::Break((self.mutator)(t)), + ControlFlow::Continue((load, continuation)) => ControlFlow::Continue(( + load, + 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 { + ControlFlow::Break(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. + let (load, continuation) = loop { + match r { + ControlFlow::Break(l) => match mutator(l) { + ControlFlow::Break(t) => return ControlFlow::Break(t), + ControlFlow::Continue(r2) => { + r = r2; + } + }, + ControlFlow::Continue((load, continuation)) => break (load, continuation), + } + }; + + ControlFlow::Continue(( + load, + 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<(&'unit RawDwarf, &'unit gimli::Unit), Error>, + R, + impl FnOnce( + Option>>, + ) -> Result<(&'unit RawDwarf, &'unit gimli::Unit), Error>, + >, + > { + loop { + break SimpleLookup::new_complete(match self.dwo.borrow() { + Some(Ok(Some(v))) => Ok((&v.0, &v.1)), + Some(Ok(None)) => Ok((&ctx.raw_dwarf, &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 dwo_path_f = || { + let dwo_comp_dir = self.dw_unit.comp_dir.clone(); + + let dwo_path = self.dw_unit.dwo_name().and_then(|s| { + if let Some(s) = s { + Ok(Some(ctx.raw_dwarf.sections.attr_string(&self.dw_unit, s)?)) + } else { + Ok(None) + } + })?; + Ok((dwo_comp_dir, dwo_path)) + }; + + let (comp_dir, path) = match dwo_path_f() { + 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((ctx.raw_dwarf.dwo(dwo_dwarf), dwo_unit)))) + }; + + return SimpleLookup::new_needs_load( + SplitDwarfLoad { + dwo_id, + comp_dir, + path, + parent: ctx.raw_dwarf.sections.clone(), + }, + move |dwo_dwarf| match self.dwo.borrow_with(|| process_dwo(dwo_dwarf)) { + Ok(Some(v)) => Ok((&v.0, &v.1)), + Ok(None) => Ok((&ctx.raw_dwarf, &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), @@ -701,19 +1176,39 @@ impl ResUnit { .map_err(Error::clone) } - fn parse_functions(&self, dwarf: &ResDwarf) -> Result<&Functions, Error> { + fn parse_functions_dwarf_and_unit( + &self, + unit: &gimli::Unit, + raw_dwarf: &RawDwarf, + ) -> Result<&Functions, Error> { self.funcs - .borrow_with(|| Functions::parse(&self.dw_unit, dwarf)) + .borrow_with(|| Functions::parse(unit, raw_dwarf)) .as_ref() .map_err(Error::clone) } - fn parse_inlined_functions(&self, dwarf: &ResDwarf) -> Result<(), Error> { - self.funcs - .borrow_with(|| Functions::parse(&self.dw_unit, dwarf)) - .as_ref() - .map_err(Error::clone)? - .parse_inlined_functions(&self.dw_unit, dwarf) + 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 (raw_dwarf, unit) = r?; + self.parse_functions_dwarf_and_unit(unit, raw_dwarf) + }) + } + 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 (raw_dwarf, unit) = r?; + self.funcs + .borrow_with(|| Functions::parse(unit, raw_dwarf)) + .as_ref() + .map_err(Error::clone)? + .parse_inlined_functions(unit, &ctx.parsed_dwarf, raw_dwarf) + }) } fn find_location( @@ -741,27 +1236,37 @@ impl ResUnit { LocationRangeUnitIter::new(self, sections, probe_low, probe_high) } - fn find_function_or_location( - &self, + fn find_function_or_location<'unit, 'ctx: 'unit>( + &'unit self, probe: u64, - dwarf: &ResDwarf, - ) -> Result<(Option<&Function>, Option>), Error> { - let functions = self.parse_functions(dwarf)?; - 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, &self.dw_unit, dwarf)) - .as_ref() - .map_err(Error::clone)?, - ) - } - None => None, - }; - let location = self.find_location(probe, &dwarf.sections)?; - Ok((function, location)) + 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 (raw_dwarf, unit) = r?; + let functions = self.parse_functions_dwarf_and_unit(unit, raw_dwarf)?; + 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, unit, &ctx.parsed_dwarf, raw_dwarf) + }) + .as_ref() + .map_err(Error::clone)?, + ) + } + None => None, + }; + let location = self.find_location(probe, &raw_dwarf.sections)?; + Ok((function, location)) + }) } } @@ -778,7 +1283,7 @@ pub struct LocationRangeIter<'ctx, R: gimli::Reader> { impl<'ctx, R: gimli::Reader> LocationRangeIter<'ctx, R> { #[inline] fn new(ctx: &'ctx Context, probe_low: u64, probe_high: u64) -> Result { - let sections = &ctx.dwarf.sections; + let sections = &ctx.raw_dwarf.sections; let unit_iter = ctx.find_units_range(probe_low, probe_high); Ok(Self { unit_iter: Box::new(unit_iter), @@ -879,12 +1384,11 @@ impl<'ctx> LocationRangeUnitIter<'ctx> { 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)); - let idx = match idx { + match idx { Ok(x) => x, Err(0) => 0, // probe below sequence, but range could overlap Err(x) => x - 1, - }; - idx + } } else { 0 }; @@ -906,12 +1410,7 @@ impl<'ctx> Iterator for LocationRangeUnitIter<'ctx> { type Item = (u64, u64, Location<'ctx>); fn next(&mut self) -> Option<(u64, u64, Location<'ctx>)> { - loop { - let seq = match self.seqs.get(self.seq_idx) { - Some(seq) => seq, - None => break, - }; - + while let Some(seq) = self.seqs.get(self.seq_idx) { if seq.start >= self.probe_high { break; } diff --git a/tests/correctness.rs b/tests/correctness.rs index 955e2b8..66caf39 100644 --- a/tests/correctness.rs +++ b/tests/correctness.rs @@ -5,11 +5,13 @@ extern crate gimli; extern crate memmap2; extern crate object; -use addr2line::Context; +use addr2line::{Context, LookupResultExt}; use fallible_iterator::FallibleIterator; use findshlibs::{IterationControl, SharedLibrary, TargetSharedLibrary}; use object::Object; +use std::borrow::Cow; use std::fs::File; +use std::sync::Arc; fn find_debuginfo() -> memmap2::Mmap { let path = std::env::current_exe().unwrap(); @@ -42,7 +44,38 @@ fn correctness() { let map = find_debuginfo(); let file = &object::File::parse(&*map).unwrap(); let module_base = file.relative_address_base(); - let ctx = Context::new(file).unwrap(); + + 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::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::EndianArcSlice::new(Arc::from(&*data), endian)) + } + + let dwarf = gimli::Dwarf::load(|id| load_section(id, file, endian)).unwrap(); + let ctx = Context::from_dwarf(dwarf).unwrap(); + #[cfg(unix)] + let mut split_dwarf_loader = addr2line::builtin_split_dwarf_loader::SplitDwarfLoader::new( + |data, endian| gimli::EndianArcSlice::new(Arc::from(&*data), endian), + None, + ); let mut bias = None; TargetSharedLibrary::each(|lib| { @@ -50,10 +83,15 @@ fn correctness() { IterationControl::Break }); - let test = |sym: u64, expected_prefix: &str| { + #[allow(unused_mut)] + let mut test = |sym: u64, expected_prefix: &str| { let ip = sym.wrapping_sub(bias.unwrap()); - let frames = ctx.find_frames(ip).unwrap(); + let frames = ctx.find_frames(ip); + #[cfg(unix)] + let frames = split_dwarf_loader.run(frames).unwrap(); + #[cfg(not(unix))] + let frames = frames.skip_all_loads().unwrap(); let frame = frames.last().unwrap().unwrap(); let name = frame.function.as_ref().unwrap().demangle().unwrap(); // Old rust versions generate DWARF with wrong linkage name, @@ -87,6 +125,13 @@ fn zero_function() { let file = &object::File::parse(&*map).unwrap(); let ctx = Context::new(file).unwrap(); for probe in 0..10 { - assert!(ctx.find_frames(probe).unwrap().count().unwrap() < 10); + assert!( + ctx.find_frames(probe) + .skip_all_loads() + .unwrap() + .count() + .unwrap() + < 10 + ); } }