diff --git a/Cargo.toml b/Cargo.toml index 76d97d5..b62a6a3 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 = { path = "../gimli", 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..dbf390a 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::LookupContinuation; 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..3cb3f7b 100644 --- a/examples/addr2line.rs +++ b/examples/addr2line.rs @@ -9,14 +9,14 @@ extern crate typed_arena; use std::borrow::Cow; use std::fs::File; use std::io::{BufRead, Lines, StdinLock, Write}; -use std::path::Path; +use std::path::{Path, PathBuf}; use clap::{Arg, Command, Values}; use fallible_iterator::FallibleIterator; use object::{Object, ObjectSection, SymbolMap, SymbolMapName}; use typed_arena::Arena; -use addr2line::{Context, Location}; +use addr2line::{builtin_split_dwarf_loader, Context, Location}; fn parse_uint_from_hex_string(string: &str) -> Option { if string.len() > 2 && string.starts_with("0x") { @@ -201,6 +201,12 @@ fn main() { dwarf.load_sup(&mut load_sup_section).unwrap(); } + let mut split_dwarf_loader = builtin_split_dwarf_loader::SplitDwarfLoader::new( + |data, endian| { + gimli::EndianSlice::new(arena_data.alloc(Cow::Owned(data.into_owned())), endian) + }, + Some(PathBuf::from(path)), + ); let ctx = Context::from_dwarf(dwarf).unwrap(); let stdin = std::io::stdin(); @@ -227,7 +233,10 @@ 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 mut frames = split_dwarf_loader + .run(ctx.find_frames(probe)) + .unwrap() + .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..cdde8c5 --- /dev/null +++ b/src/builtin_split_dwarf_loader.rs @@ -0,0 +1,133 @@ +use alloc::borrow::Cow; +use alloc::sync::Arc; +use core::ops::ControlFlow; +use std::fs::File; +use std::path::PathBuf; + +use object::Object; + +use crate::LookupContinuation; + +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 LookupContinuation to completion, loading any necessary + /// split DWARF along the way. + pub fn run(&mut self, mut l: L) -> L::Output + where + L: LookupContinuation, + { + loop { + let load = match l.state() { + ControlFlow::Break(output) => break output, + ControlFlow::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 path = PathBuf::from(&load.dwo_name); + 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.resume(r); + } + } +} 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 bb96565..7ee5621 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; @@ -51,6 +54,7 @@ use core::cmp::{self, Ordering}; use core::iter; use core::mem; use core::num::NonZeroU64; +use core::ops::ControlFlow; use core::u64; use crate::function::{Function, Functions, InlinedFunction}; @@ -67,6 +71,9 @@ mod maybe_small { 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; @@ -105,18 +112,17 @@ pub trait SplitDwarfLoader { /// /// 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<'loader, R: gimli::Reader> { +pub struct Context { parsed_dwarf: ParsedDwarf, raw_dwarf: RawDwarf, - split_dwarf_loader: Option + Send + 'loader>>, } /// The type of `Context` that supports the `new` method. #[cfg(feature = "std-object")] -pub type ObjectContext<'loader> = Context<'loader, gimli::EndianRcSlice>; +pub type ObjectContext = Context>; #[cfg(feature = "std-object")] -impl Context<'static, gimli::EndianRcSlice> { +impl Context> { /// Construct a new `Context`. /// /// The resulting `Context` uses `gimli::EndianRcSlice`. @@ -178,7 +184,7 @@ impl Context<'static, gimli::EndianRcSlice> { } } -impl<'loader, R: gimli::Reader> Context<'loader, R> { +impl Context { /// Construct a new `Context` from DWARF sections. /// /// This method does not support using a supplementary object file. @@ -218,17 +224,7 @@ impl<'loader, R: gimli::Reader> Context<'loader, R> { /// Construct a new `Context` from an existing [`gimli::Dwarf`] object. #[inline] - pub fn from_dwarf(sections: gimli::Dwarf) -> Result { - Self::from_dwarf_with_split_loader(sections, None) - } - - /// Construct a new `Context` from an existing [`gimli::Dwarf`] object with - /// the specified [`SplitDwarfLoader`]. - #[inline] - pub fn from_dwarf_with_split_loader( - sections: gimli::Dwarf, - split_dwarf_loader: Option + Send + 'loader>>, - ) -> Result, Error> { + pub fn from_dwarf(sections: gimli::Dwarf) -> Result, Error> { let sup_sections = sections.sup.clone(); let raw_dwarf = RawDwarf { sections: Arc::new(sections), @@ -242,7 +238,6 @@ impl<'loader, R: gimli::Reader> Context<'loader, R> { Ok(Context { parsed_dwarf, raw_dwarf, - split_dwarf_loader, }) } @@ -322,7 +317,7 @@ impl<'loader, R: gimli::Reader> Context<'loader, R> { /// 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) { + match unit.find_function_or_location(probe, self).skip_all_loads() { Ok((Some(_), _)) | Ok((_, Some(_))) => return Some(&unit.dw_unit), _ => {} } @@ -359,26 +354,42 @@ impl<'loader, R: gimli::Reader> Context<'loader, R> { /// 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)? { - (Some(function), location) => { - let inlined_functions = function.find_inlined_functions(probe); - return Ok(FrameIter(FrameIterState::Frames(FrameIterFrames { - unit, - sections: &self.raw_dwarf.sections, - function, - inlined_functions, - next: location, - }))); - } - (None, Some(location)) => { - return Ok(FrameIter(FrameIterState::Location(Some(location)))); - } - _ => {} + pub fn find_frames( + &self, + probe: u64, + ) -> impl LookupContinuation, Error>, R = R> { + let mut units_iter = self.find_units(probe); + match units_iter.next() { + None => LoopingLookup::new_complete(Ok(FrameIter(FrameIterState::Empty))), + Some(unit) => { + 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)), + }, + }) + }) } } - Ok(FrameIter(FrameIterState::Empty)) } /// Initialize all line data structures. This is used for benchmarks. @@ -394,7 +405,7 @@ impl<'loader, R: gimli::Reader> Context<'loader, R> { #[doc(hidden)] pub fn parse_functions(&self) -> Result<(), Error> { for unit in &self.parsed_dwarf.units { - unit.parse_functions(self)?; + unit.parse_functions(self).skip_all_loads()?; } Ok(()) } @@ -403,7 +414,7 @@ impl<'loader, R: gimli::Reader> Context<'loader, R> { #[doc(hidden)] pub fn parse_inlined_functions(&self) -> Result<(), Error> { for unit in &self.parsed_dwarf.units { - unit.parse_inlined_functions(self)?; + unit.parse_inlined_functions(self).skip_all_loads()?; } Ok(()) } @@ -768,65 +779,360 @@ struct ResUnit { dwo: LazyCell, gimli::Unit)>>, Error>>, } -impl ResUnit { - fn dwarf_and_unit_dwo<'unit, 'ctx: 'unit>( - &'unit self, - ctx: &'ctx Context, - ) -> Result<(&'unit RawDwarf, &'unit gimli::Unit), Error> { - let process_dwo = || { - let mut dwo_name = self - .dw_unit - .comp_dir - .as_ref() - .map(|s| s.to_string_lossy()) - .transpose()? - .map(|s| s.into_owned()) - .unwrap_or_default(); - let dwo_id = match self.dw_unit.dwo_id { - Some(dwo_id) => dwo_id, - None => return Ok(None), - }; +/// This struct contains the information needed to find split DWARF data +/// and to produce a `gimli::Dwarf` for it. +pub struct SplitDwarfLookup { + /// DAFUQ + pub dwo_id: gimli::DwoId, + /// ... + pub dwo_name: String, + /// ... + pub parent: Arc>, +} - let split_dwarf_loader = match ctx.split_dwarf_loader.as_ref() { - Some(loader) => loader, - None => return Ok(None), - }; +/// Operations that consult debug information may require additional files +/// to be loaded if split DWARF is being used. This trait is a continuation +/// that allows the operation to be resumed once the caller can provide +/// the additional data needed, if any. +/// +/// This trait is intended to be used in a loop like so: +/// ```ignore +/// let mut continuation = ctx.method(arguments); +/// let result = loop { +/// match continuation.state() { +/// ControlFlow::Break(result) => break result, +/// ControlFlow::Continue(lookup) => { +/// let dwo = do_split_dwarf_lookup(lookup); +/// continuation.resume(dwo); +/// } +/// } +/// }; +/// ``` +pub trait LookupContinuation { + /// The final output of this operation. + type Output; + + /// The type of reader used. + type R: gimli::Reader; + + /// Returns the current state of the operation. + /// + /// If `ControlFlow::Continue` is returned, it contains the information + /// needed to load the additional file. Once the file is loaded, + /// `resume` should be called with that data. + /// + /// If `ControlFlow::Break` is returned, it contains the final result of + /// this operation. + /// + /// `state` should never be called twice without calling `resume` in + /// between or it may panic. + fn state(&mut self) -> ControlFlow>; - let dwo_suffix = 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) + /// Resumes the operation with the provided data. + /// + /// After the caller loads the data required by `state` returning + /// `ControlFlow::Continue`, call this method to resume the operation. + /// Then call `state` again to see if the computation has completed + /// or if more 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. + /// + /// `resume` should never be called after `state` returns + /// `ControlFlow::Break`, and it should never be called twice without + /// calling `resume` and receiving a return value of + /// `ControlFlow::Continue` in between, or it may panic. + fn resume(&mut self, load_result: Option>>); + + /// 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) -> Self::Output; +} + +struct SimpleLookup +where + F: FnMut(Option>>) -> ControlFlow>, +{ + result: Option>>, + f: Option, +} + +impl SimpleLookup +where + F: FnMut(Option>>) -> ControlFlow>, +{ + fn new_complete(t: T) -> SimpleLookup { + SimpleLookup { + result: Some(ControlFlow::Break(t)), + f: None, + } + } + + fn new_needs_load(lookup: SplitDwarfLookup, f: F) -> SimpleLookup { + SimpleLookup { + result: Some(ControlFlow::Continue(lookup)), + f: Some(f), + } + } + + fn map U>( + self, + mut g: G, + ) -> SimpleLookup< + U, + R, + impl FnMut(Option>>) -> ControlFlow>, + > { + SimpleLookup { + result: self.result.map(|v| match v { + ControlFlow::Break(b) => ControlFlow::Break(g(b)), + ControlFlow::Continue(c) => ControlFlow::Continue(c), + }), + f: self.f.map(move |mut f| { + move |v| match f(v) { + ControlFlow::Break(b) => ControlFlow::Break(g(b)), + ControlFlow::Continue(c) => ControlFlow::Continue(c), } - })?; - if let Some(dwo_suffix) = dwo_suffix - .as_ref() - .map(|s| s.to_string_lossy()) - .transpose()? - { - path_push(&mut dwo_name, &dwo_suffix); + }), + } + } +} + +impl LookupContinuation for SimpleLookup +where + F: FnMut(Option>>) -> ControlFlow>, + R: gimli::Reader, +{ + type Output = T; + type R = R; + + fn state(&mut self) -> ControlFlow> { + self.result.take().unwrap() + } + + fn resume(&mut self, v: Option>>) { + let result = (self.f.as_mut().unwrap())(v); + if result.is_break() { + self.f = None; + } + self.result = Some(result); + } + + fn skip_all_loads(mut self) -> Self::Output { + loop { + match self.state() { + ControlFlow::Break(t) => return t, + ControlFlow::Continue(_) => self.resume(None), } - let dwo_dwarf = - match split_dwarf_loader.load(dwo_id, dwo_name, &ctx.raw_dwarf.sections)? { - Some(dwo_dwarf) => dwo_dwarf, - None => return Ok(None), - }; + } + } +} - let mut dwo_units = dwo_dwarf.units(); - let dwo_header = match dwo_units.next()? { - Some(dwo_header) => dwo_header, - None => return Ok(None), - }; +enum LoopingLookupState { + Complete(T), + AwaitingLoad(SplitDwarfLookup), + AwaitingProcessing(U), +} + +struct LoopingLookup +where + L: LookupContinuation, + F: FnMut(L::Output) -> ControlFlow, +{ + state: Option>, + // current_lookup, if present, is always waiting for a resume() call. + current_lookup: Option, + f: Option, +} + +impl LoopingLookup +where + L: LookupContinuation, + F: FnMut(L::Output) -> ControlFlow, +{ + fn new_complete(t: T) -> LoopingLookup { + LoopingLookup { + state: Some(LoopingLookupState::Complete(t)), + current_lookup: None, + f: None, + } + } + + fn new_lookup(mut lookup: L, f: F) -> LoopingLookup { + let (state, current_lookup) = match lookup.state() { + ControlFlow::Break(v) => (LoopingLookupState::AwaitingProcessing(v), None), + ControlFlow::Continue(l) => (LoopingLookupState::AwaitingLoad(l), Some(lookup)), + }; + LoopingLookup { + state: Some(state), + current_lookup, + f: Some(f), + } + } - 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)))) + fn advance(&mut self) -> ControlFlow> { + let mut v = match self.state.take().unwrap() { + LoopingLookupState::Complete(t) => return ControlFlow::Break(t), + LoopingLookupState::AwaitingLoad(l) => return ControlFlow::Continue(l), + LoopingLookupState::AwaitingProcessing(v) => v, }; - match self.dwo.borrow_with(process_dwo) { - Ok(None) => Ok((&ctx.raw_dwarf, &self.dw_unit)), - Ok(Some(v)) => Ok((&v.0, &v.1)), - Err(e) => Err(*e), + loop { + match (self.f.as_mut().unwrap())(v) { + ControlFlow::Break(t) => { + self.f = None; + return ControlFlow::Break(t); + } + ControlFlow::Continue(mut lookup) => match lookup.state() { + ControlFlow::Break(v2) => { + v = v2; + } + ControlFlow::Continue(l) => { + self.current_lookup = Some(lookup); + return ControlFlow::Continue(l); + } + }, + } + } + } +} + +impl LookupContinuation for LoopingLookup +where + L: LookupContinuation, + F: FnMut(L::Output) -> ControlFlow, + R: gimli::Reader, +{ + type Output = T; + type R = R; + + fn state(&mut self) -> ControlFlow> { + self.advance() + } + + fn resume(&mut self, v: Option>>) { + let mut lookup = self.current_lookup.take().unwrap(); + lookup.resume(v); + self.state = Some(match lookup.state() { + ControlFlow::Break(v) => LoopingLookupState::AwaitingProcessing(v), + ControlFlow::Continue(l) => { + self.current_lookup = Some(lookup); + LoopingLookupState::AwaitingLoad(l) + } + }); + } + + fn skip_all_loads(mut self) -> Self::Output { + loop { + match self.state() { + ControlFlow::Break(t) => return t, + ControlFlow::Continue(_) => self.resume(None), + } + } + } +} + +impl ResUnit { + fn dwarf_and_unit_dwo<'unit, 'ctx: 'unit>( + &'unit self, + ctx: &'ctx Context, + ) -> SimpleLookup< + Result<(&'unit RawDwarf, &'unit gimli::Unit), Error>, + R, + impl FnMut( + Option>>, + ) -> ControlFlow< + Result<(&'unit RawDwarf, &'unit gimli::Unit), Error>, + SplitDwarfLookup, + >, + > { + loop { + break SimpleLookup::new_complete(match self.dwo.borrow() { + 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_name_f = || { + let mut dwo_name = self + .dw_unit + .comp_dir + .as_ref() + .map(|s| s.to_string_lossy()) + .transpose()? + .map(|s| s.into_owned()) + .unwrap_or_default(); + + let dwo_suffix = 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) + } + })?; + if let Some(dwo_suffix) = dwo_suffix + .as_ref() + .map(|s| s.to_string_lossy()) + .transpose()? + { + path_push(&mut dwo_name, &dwo_suffix); + } + Ok(dwo_name) + }; + + let dwo_name = match dwo_name_f() { + Ok(dwo_name) => dwo_name, + 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( + SplitDwarfLookup { + dwo_id, + dwo_name, + parent: ctx.raw_dwarf.sections.clone(), + }, + move |dwo_dwarf| { + ControlFlow::Break( + 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), + }, + ) + }, + ); + } + Some(Ok(Some(v))) => Ok((&v.0, &v.1)), + Some(Ok(None)) => Ok((&ctx.raw_dwarf, &self.dw_unit)), + Some(Err(e)) => Err(*e), + }); } } @@ -855,18 +1161,28 @@ impl ResUnit { .map_err(Error::clone) } - fn parse_functions(&self, ctx: &Context) -> Result<&Functions, Error> { - let (raw_dwarf, unit) = self.dwarf_and_unit_dwo(ctx)?; - self.parse_functions_dwarf_and_unit(unit, raw_dwarf) + fn parse_functions<'unit, 'ctx: 'unit>( + &'unit self, + ctx: &'ctx Context, + ) -> impl LookupContinuation, Error>, R = 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(&self, ctx: &Context) -> Result<(), Error> { - let (raw_dwarf, unit) = self.dwarf_and_unit_dwo(ctx)?; - 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 parse_inlined_functions<'unit, 'ctx: 'unit>( + &'unit self, + ctx: &'ctx Context, + ) -> impl LookupContinuation, R = 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( @@ -894,28 +1210,35 @@ 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, - ctx: &Context, - ) -> Result<(Option<&Function>, Option>), Error> { - let (raw_dwarf, unit) = self.dwarf_and_unit_dwo(ctx)?; - 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)) + ctx: &'ctx Context, + ) -> impl LookupContinuation< + Output = Result<(Option<&'unit Function>, Option>), Error>, + R = 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)) + }) } } diff --git a/tests/correctness.rs b/tests/correctness.rs index 79d712f..e861a6d 100644 --- a/tests/correctness.rs +++ b/tests/correctness.rs @@ -5,13 +5,12 @@ extern crate gimli; extern crate memmap2; extern crate object; -use addr2line::{Context, SplitDwarfLoader}; +use addr2line::{builtin_split_dwarf_loader, Context, LookupContinuation}; use fallible_iterator::FallibleIterator; use findshlibs::{IterationControl, SharedLibrary, TargetSharedLibrary}; use object::Object; use std::borrow::Cow; use std::fs::File; -use std::path::PathBuf; use std::sync::Arc; fn find_debuginfo() -> memmap2::Mmap { @@ -40,138 +39,11 @@ fn find_debuginfo() -> memmap2::Mmap { return map; } -fn find_dwp() -> Option { - let mut path = std::env::current_exe().unwrap(); - let dwp_extension = match path.extension() { - Some(ext) => { - let mut ext = ext.to_os_string(); - ext.push(".dwp"); - ext - } - None => "dwp".into(), - }; - path.set_extension(dwp_extension); - let file = File::open(&path).ok()?; - let map = unsafe { memmap2::Mmap::map(&file).unwrap() }; - Some(map) -} - -struct Loader<'file> { - main_dwarf: &'file object::File<'file>, - package: Option>>, -} - -impl<'file> Loader<'file> { - fn new( - main_dwarf: &'file object::File<'file>, - packaged_dwarf: Option<&'file object::File<'file>>, - ) -> Result { - Ok(if let Some(dwp) = packaged_dwarf { - 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.dwo_name().unwrap()) - .and_then(|section| section.uncompressed_data().ok()) - .unwrap_or(Cow::Borrowed(&[])); - Ok(gimli::EndianArcSlice::new(Arc::from(&*data), endian)) - } - - let endian = if dwp.is_little_endian() { - gimli::RunTimeEndian::Little - } else { - gimli::RunTimeEndian::Big - }; - - let package = gimli::DwarfPackage::load( - |section_id| load_section(section_id, dwp, endian), - gimli::EndianArcSlice::new(Arc::from([]), endian), - )?; - Loader { - main_dwarf, - package: Some(package), - } - } else { - Loader { - main_dwarf, - package: None, - } - }) - } -} - -impl<'file> SplitDwarfLoader> for Loader<'file> { - fn load( - &self, - dwo_id: gimli::DwoId, - dwo_name: String, - parent: &gimli::Dwarf>, - ) -> Result>>>, gimli::Error> - { - if let Some(dwp) = self.package.as_ref() { - match dwp.find_cu(dwo_id, parent)? { - Some(cu) => Ok(Some(Arc::new(cu))), - None => return Ok(None), - } - } else { - let path = PathBuf::from(dwo_name); - let file = File::open(&path).unwrap_or_else(|e| panic!("{}: {}", path.display(), e)); - let map = unsafe { memmap2::Mmap::map(&file).unwrap() }; - let file = &object::File::parse(&*map).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, - main: &'file O, - file: &'file O, - endian: Endian, - ) -> Result, gimli::Error> - where - O: object::Object<'data, 'file>, - Endian: gimli::Endianity, - { - use object::ObjectSection; - - let data = match id.dwo_name() { - Some(dwo_name) => file - .section_by_name(dwo_name) - .and_then(|section| section.uncompressed_data().ok()) - .unwrap_or(Cow::Borrowed(&[])), - None => main - .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 dwo_dwarf = - gimli::Dwarf::load(|id| load_section(id, self.main_dwarf, file, endian))?; - Ok(Some(Arc::new(dwo_dwarf.make_dwo(parent)))) - } - } -} - #[test] fn correctness() { let map = find_debuginfo(); let file = &object::File::parse(&*map).unwrap(); let module_base = file.relative_address_base(); - let dwp_map = find_dwp(); - let dwp_file = dwp_map.as_deref().map(|m| object::File::parse(m).unwrap()); let endian = if file.is_little_endian() { gimli::RunTimeEndian::Little @@ -198,8 +70,11 @@ fn correctness() { } let dwarf = gimli::Dwarf::load(|id| load_section(id, file, endian)).unwrap(); - let loader = Box::new(Loader::new(file, dwp_file.as_ref()).unwrap()); - let ctx = Context::from_dwarf_with_split_loader(dwarf, Some(loader)).unwrap(); + let ctx = Context::from_dwarf(dwarf).unwrap(); + let mut split_dwarf_loader = builtin_split_dwarf_loader::SplitDwarfLoader::new( + |data, endian| gimli::EndianArcSlice::new(Arc::from(&*data), endian), + None, + ); let mut bias = None; TargetSharedLibrary::each(|lib| { @@ -207,10 +82,10 @@ fn correctness() { IterationControl::Break }); - let test = |sym: u64, expected_prefix: &str| { + let mut test = |sym: u64, expected_prefix: &str| { let ip = sym.wrapping_sub(bias.unwrap()); - let frames = ctx.find_frames(ip).unwrap(); + let frames = split_dwarf_loader.run(ctx.find_frames(ip)).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, @@ -244,6 +119,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 + ); } }