From 2d7c060ace7fa7e2d11a4061d34d785f328829af Mon Sep 17 00:00:00 2001 From: Philip Craig Date: Mon, 15 Apr 2024 16:41:37 +1000 Subject: [PATCH] Move code from lib.rs to lookup.rs --- src/lib.rs | 263 +------------------------------------------------- src/lookup.rs | 261 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 265 insertions(+), 259 deletions(-) create mode 100644 src/lookup.rs diff --git a/src/lib.rs b/src/lib.rs index 152f0a9..7826121 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,12 +43,12 @@ use alloc::borrow::Cow; use alloc::rc::Rc; use alloc::sync::Arc; -use core::marker::PhantomData; use core::ops::ControlFlow; use core::u64; use crate::function::{Function, Functions, InlinedFunction}; use crate::line::{LineLocationRangeIter, Lines}; +use crate::lookup::{LoopingLookup, SimpleLookup}; use crate::unit::{ResUnit, ResUnits, SupUnits}; #[cfg(feature = "smallvec")] @@ -73,6 +73,9 @@ mod function; mod lazy; mod line; +mod lookup; +pub use lookup::{LookupContinuation, LookupResult, SplitDwarfLoad}; + mod unit; pub use unit::LocationRangeIter; @@ -85,103 +88,6 @@ enum DebugFile { Dwo, } -/// Operations that consult debug information may require additional files -/// to be loaded if split DWARF is being used. This enum returns the result -/// of the operation in the `Break` variant, or information about the split -/// DWARF that is required and a continuation to invoke once it is available -/// in the `Continue` variant. -/// -/// This enum is intended to be used in a loop like so: -/// ```no_run -/// # use addr2line::*; -/// # use std::sync::Arc; -/// # let ctx: Context> = todo!(); -/// # let do_split_dwarf_load = |load: SplitDwarfLoad>| -> Option>>> { None }; -/// const ADDRESS: u64 = 0xdeadbeef; -/// let mut r = ctx.find_frames(ADDRESS); -/// let result = loop { -/// match r { -/// LookupResult::Output(result) => break result, -/// LookupResult::Load { load, continuation } => { -/// let dwo = do_split_dwarf_load(load); -/// r = continuation.resume(dwo); -/// } -/// } -/// }; -/// ``` -pub enum LookupResult { - /// The lookup requires split DWARF data to be loaded. - Load { - /// The information needed to find the split DWARF data. - load: SplitDwarfLoad<::Buf>, - /// The continuation to resume with the loaded split DWARF data. - continuation: L, - }, - /// The lookup has completed and produced an output. - Output(::Output), -} - -/// This trait represents a partially complete operation that can be resumed -/// once a load of needed split DWARF data is completed or abandoned by the -/// API consumer. -pub trait LookupContinuation: Sized { - /// The final output of this operation. - type Output; - /// The type of reader used. - type Buf: gimli::Reader; - - /// Resumes the operation with the provided data. - /// - /// After the caller loads the split DWARF data required, call this - /// method to resume the operation. The return value of this method - /// indicates if the computation has completed or if further data is - /// required. - /// - /// If the additional data cannot be located, or the caller does not - /// support split DWARF, `resume(None)` can be used to continue the - /// operation with the data that is available. - fn resume(self, input: Option>>) -> LookupResult; -} - -impl LookupResult { - /// Callers that do not handle split DWARF can call `skip_all_loads` - /// to fast-forward to the end result. This result is produced with - /// the data that is available and may be less accurate than the - /// the results that would be produced if the caller did properly - /// support split DWARF. - pub fn skip_all_loads(mut self) -> L::Output { - loop { - self = match self { - LookupResult::Output(t) => return t, - LookupResult::Load { continuation, .. } => continuation.resume(None), - }; - } - } - - pub(crate) fn map T>( - self, - f: F, - ) -> LookupResult> { - match self { - LookupResult::Output(t) => LookupResult::Output(f(t)), - LookupResult::Load { load, continuation } => LookupResult::Load { - load, - continuation: MappedLookup { - original: continuation, - mutator: f, - }, - }, - } - } - - pub(crate) 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 @@ -512,167 +418,6 @@ impl Context { } } -/// 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>, -} - -pub(crate) 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, -{ - pub(crate) fn new_complete(t: F::Output) -> LookupResult> { - LookupResult::Output(t) - } - - pub(crate) fn new_needs_load( - load: SplitDwarfLoad, - f: F, - ) -> LookupResult> { - LookupResult::Load { - load, - continuation: SimpleLookup { - f, - phantom: PhantomData, - }, - } - } -} - -impl LookupContinuation for SimpleLookup -where - F: FnOnce(Option>>) -> T, - R: gimli::Reader, -{ - type Output = T; - type Buf = R; - - fn resume(self, v: Option>>) -> LookupResult { - LookupResult::Output((self.f)(v)) - } -} - -pub(crate) struct MappedLookup -where - L: LookupContinuation, - F: FnOnce(L::Output) -> T, -{ - original: L, - mutator: F, -} - -impl LookupContinuation for MappedLookup -where - L: LookupContinuation, - F: FnOnce(L::Output) -> T, -{ - type Output = T; - type Buf = L::Buf; - - fn resume(self, v: Option>>) -> LookupResult { - match self.original.resume(v) { - LookupResult::Output(t) => LookupResult::Output((self.mutator)(t)), - LookupResult::Load { load, continuation } => LookupResult::Load { - load, - continuation: MappedLookup { - original: continuation, - mutator: self.mutator, - }, - }, - } - } -} - -/// Some functions (e.g. `find_frames`) require considering multiple -/// compilation units, each of which might require their own split DWARF -/// lookup (and thus produce a continuation). -/// -/// We store the underlying continuation here as well as a mutator function -/// that will either a) decide that the result of this continuation is -/// what is needed and mutate it to the final result or b) produce another -/// `LookupResult`. `new_lookup` will in turn eagerly drive any non-continuation -/// `LookupResult` with successive invocations of the mutator, until a new -/// continuation or a final result is produced. And finally, the impl of -/// `LookupContinuation::resume` will call `new_lookup` each time the -/// computation is resumed. -pub(crate) struct LoopingLookup -where - L: LookupContinuation, - F: FnMut(L::Output) -> ControlFlow>, -{ - continuation: L, - mutator: F, -} - -impl LoopingLookup -where - L: LookupContinuation, - F: FnMut(L::Output) -> ControlFlow>, -{ - pub(crate) fn new_complete(t: T) -> LookupResult { - LookupResult::Output(t) - } - - pub(crate) fn new_lookup(mut r: LookupResult, mut mutator: F) -> LookupResult { - // Drive the loop eagerly so that we only ever have to represent one state - // (the r == ControlFlow::Continue state) in LoopingLookup. - loop { - match r { - LookupResult::Output(l) => match mutator(l) { - ControlFlow::Break(t) => return LookupResult::Output(t), - ControlFlow::Continue(r2) => { - r = r2; - } - }, - LookupResult::Load { load, continuation } => { - return LookupResult::Load { - load, - continuation: LoopingLookup { - continuation, - mutator, - }, - }; - } - } - } - } -} - -impl LookupContinuation for LoopingLookup -where - L: LookupContinuation, - F: FnMut(L::Output) -> ControlFlow>, -{ - type Output = T; - type Buf = L::Buf; - - fn resume(self, v: Option>>) -> LookupResult { - let r = self.continuation.resume(v); - LoopingLookup::new_lookup(r, self.mutator) - } -} - struct RangeAttributes { low_pc: Option, high_pc: Option, diff --git a/src/lookup.rs b/src/lookup.rs new file mode 100644 index 0000000..8424384 --- /dev/null +++ b/src/lookup.rs @@ -0,0 +1,261 @@ +use alloc::sync::Arc; +use core::marker::PhantomData; +use core::ops::ControlFlow; + +/// 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>, +} + +/// Operations that consult debug information may require additional files +/// to be loaded if split DWARF is being used. This enum returns the result +/// of the operation in the `Break` variant, or information about the split +/// DWARF that is required and a continuation to invoke once it is available +/// in the `Continue` variant. +/// +/// This enum is intended to be used in a loop like so: +/// ```no_run +/// # use addr2line::*; +/// # use std::sync::Arc; +/// # let ctx: Context> = todo!(); +/// # let do_split_dwarf_load = |load: SplitDwarfLoad>| -> Option>>> { None }; +/// const ADDRESS: u64 = 0xdeadbeef; +/// let mut r = ctx.find_frames(ADDRESS); +/// let result = loop { +/// match r { +/// LookupResult::Output(result) => break result, +/// LookupResult::Load { load, continuation } => { +/// let dwo = do_split_dwarf_load(load); +/// r = continuation.resume(dwo); +/// } +/// } +/// }; +/// ``` +pub enum LookupResult { + /// The lookup requires split DWARF data to be loaded. + Load { + /// The information needed to find the split DWARF data. + load: SplitDwarfLoad<::Buf>, + /// The continuation to resume with the loaded split DWARF data. + continuation: L, + }, + /// The lookup has completed and produced an output. + Output(::Output), +} + +/// This trait represents a partially complete operation that can be resumed +/// once a load of needed split DWARF data is completed or abandoned by the +/// API consumer. +pub trait LookupContinuation: Sized { + /// The final output of this operation. + type Output; + /// The type of reader used. + type Buf: gimli::Reader; + + /// Resumes the operation with the provided data. + /// + /// After the caller loads the split DWARF data required, call this + /// method to resume the operation. The return value of this method + /// indicates if the computation has completed or if further data is + /// required. + /// + /// If the additional data cannot be located, or the caller does not + /// support split DWARF, `resume(None)` can be used to continue the + /// operation with the data that is available. + fn resume(self, input: Option>>) -> LookupResult; +} + +impl LookupResult { + /// Callers that do not handle split DWARF can call `skip_all_loads` + /// to fast-forward to the end result. This result is produced with + /// the data that is available and may be less accurate than the + /// the results that would be produced if the caller did properly + /// support split DWARF. + pub fn skip_all_loads(mut self) -> L::Output { + loop { + self = match self { + LookupResult::Output(t) => return t, + LookupResult::Load { continuation, .. } => continuation.resume(None), + }; + } + } + + pub(crate) fn map T>( + self, + f: F, + ) -> LookupResult> { + match self { + LookupResult::Output(t) => LookupResult::Output(f(t)), + LookupResult::Load { load, continuation } => LookupResult::Load { + load, + continuation: MappedLookup { + original: continuation, + mutator: f, + }, + }, + } + } + + pub(crate) fn unwrap(self) -> L::Output { + match self { + LookupResult::Output(t) => t, + LookupResult::Load { .. } => unreachable!("Internal API misuse"), + } + } +} + +pub(crate) 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, +{ + pub(crate) fn new_complete(t: F::Output) -> LookupResult> { + LookupResult::Output(t) + } + + pub(crate) fn new_needs_load( + load: SplitDwarfLoad, + f: F, + ) -> LookupResult> { + LookupResult::Load { + load, + continuation: SimpleLookup { + f, + phantom: PhantomData, + }, + } + } +} + +impl LookupContinuation for SimpleLookup +where + F: FnOnce(Option>>) -> T, + R: gimli::Reader, +{ + type Output = T; + type Buf = R; + + fn resume(self, v: Option>>) -> LookupResult { + LookupResult::Output((self.f)(v)) + } +} + +pub(crate) struct MappedLookup +where + L: LookupContinuation, + F: FnOnce(L::Output) -> T, +{ + original: L, + mutator: F, +} + +impl LookupContinuation for MappedLookup +where + L: LookupContinuation, + F: FnOnce(L::Output) -> T, +{ + type Output = T; + type Buf = L::Buf; + + fn resume(self, v: Option>>) -> LookupResult { + match self.original.resume(v) { + LookupResult::Output(t) => LookupResult::Output((self.mutator)(t)), + LookupResult::Load { load, continuation } => LookupResult::Load { + load, + continuation: MappedLookup { + original: continuation, + mutator: self.mutator, + }, + }, + } + } +} + +/// Some functions (e.g. `find_frames`) require considering multiple +/// compilation units, each of which might require their own split DWARF +/// lookup (and thus produce a continuation). +/// +/// We store the underlying continuation here as well as a mutator function +/// that will either a) decide that the result of this continuation is +/// what is needed and mutate it to the final result or b) produce another +/// `LookupResult`. `new_lookup` will in turn eagerly drive any non-continuation +/// `LookupResult` with successive invocations of the mutator, until a new +/// continuation or a final result is produced. And finally, the impl of +/// `LookupContinuation::resume` will call `new_lookup` each time the +/// computation is resumed. +pub(crate) struct LoopingLookup +where + L: LookupContinuation, + F: FnMut(L::Output) -> ControlFlow>, +{ + continuation: L, + mutator: F, +} + +impl LoopingLookup +where + L: LookupContinuation, + F: FnMut(L::Output) -> ControlFlow>, +{ + pub(crate) fn new_complete(t: T) -> LookupResult { + LookupResult::Output(t) + } + + pub(crate) fn new_lookup(mut r: LookupResult, mut mutator: F) -> LookupResult { + // Drive the loop eagerly so that we only ever have to represent one state + // (the r == ControlFlow::Continue state) in LoopingLookup. + loop { + match r { + LookupResult::Output(l) => match mutator(l) { + ControlFlow::Break(t) => return LookupResult::Output(t), + ControlFlow::Continue(r2) => { + r = r2; + } + }, + LookupResult::Load { load, continuation } => { + return LookupResult::Load { + load, + continuation: LoopingLookup { + continuation, + mutator, + }, + }; + } + } + } + } +} + +impl LookupContinuation for LoopingLookup +where + L: LookupContinuation, + F: FnMut(L::Output) -> ControlFlow>, +{ + type Output = T; + type Buf = L::Buf; + + fn resume(self, v: Option>>) -> LookupResult { + let r = self.continuation.resume(v); + LoopingLookup::new_lookup(r, self.mutator) + } +}