addr2line/
lookup.rs

1use alloc::sync::Arc;
2use core::marker::PhantomData;
3use core::ops::ControlFlow;
4
5/// This struct contains the information needed to find split DWARF data
6/// and to produce a `gimli::Dwarf<R>` for it.
7pub struct SplitDwarfLoad<R> {
8    /// The dwo id, for looking up in a DWARF package, or for
9    /// verifying an unpacked dwo found on the file system
10    pub dwo_id: gimli::DwoId,
11    /// The compilation directory `path` is relative to.
12    pub comp_dir: Option<R>,
13    /// A path on the filesystem, relative to `comp_dir` to find this dwo.
14    pub path: Option<R>,
15    /// Once the split DWARF data is loaded, the loader is expected
16    /// to call [make_dwo(parent)](gimli::read::Dwarf::make_dwo) before
17    /// returning the data.
18    pub parent: Arc<gimli::Dwarf<R>>,
19}
20
21/// Operations that consult debug information may require additional files
22/// to be loaded if split DWARF is being used. This enum returns the result
23/// of the operation in the `Output` variant, or information about the split
24/// DWARF that is required and a continuation to invoke once it is available
25/// in the `Load` variant.
26///
27/// This enum is intended to be used in a loop like so:
28/// ```no_run
29///   # use addr2line::*;
30///   # use std::sync::Arc;
31///   # let ctx: Context<gimli::EndianSlice<gimli::RunTimeEndian>> = todo!();
32///   # let do_split_dwarf_load = |load: SplitDwarfLoad<gimli::EndianSlice<gimli::RunTimeEndian>>| -> Option<Arc<gimli::Dwarf<gimli::EndianSlice<gimli::RunTimeEndian>>>> { None };
33///   const ADDRESS: u64 = 0xdeadbeef;
34///   let mut r = ctx.find_frames(ADDRESS);
35///   let result = loop {
36///     match r {
37///       LookupResult::Output(result) => break result,
38///       LookupResult::Load { load, continuation } => {
39///         let dwo = do_split_dwarf_load(load);
40///         r = continuation.resume(dwo);
41///       }
42///     }
43///   };
44/// ```
45pub enum LookupResult<L: LookupContinuation> {
46    /// The lookup requires split DWARF data to be loaded.
47    Load {
48        /// The information needed to find the split DWARF data.
49        load: SplitDwarfLoad<<L as LookupContinuation>::Buf>,
50        /// The continuation to resume with the loaded split DWARF data.
51        continuation: L,
52    },
53    /// The lookup has completed and produced an output.
54    Output(<L as LookupContinuation>::Output),
55}
56
57/// This trait represents a partially complete operation that can be resumed
58/// once a load of needed split DWARF data is completed or abandoned by the
59/// API consumer.
60pub trait LookupContinuation: Sized {
61    /// The final output of this operation.
62    type Output;
63    /// The type of reader used.
64    type Buf: gimli::Reader;
65
66    /// Resumes the operation with the provided data.
67    ///
68    /// After the caller loads the split DWARF data required, call this
69    /// method to resume the operation. The return value of this method
70    /// indicates if the computation has completed or if further data is
71    /// required.
72    ///
73    /// If the additional data cannot be located, or the caller does not
74    /// support split DWARF, `resume(None)` can be used to continue the
75    /// operation with the data that is available.
76    fn resume(self, input: Option<Arc<gimli::Dwarf<Self::Buf>>>) -> LookupResult<Self>;
77}
78
79impl<L: LookupContinuation> LookupResult<L> {
80    /// Callers that do not handle split DWARF can call `skip_all_loads`
81    /// to fast-forward to the end result. This result is produced with
82    /// the data that is available and may be less accurate than the
83    /// the results that would be produced if the caller did properly
84    /// support split DWARF.
85    pub fn skip_all_loads(mut self) -> L::Output {
86        loop {
87            self = match self {
88                LookupResult::Output(t) => return t,
89                LookupResult::Load { continuation, .. } => continuation.resume(None),
90            };
91        }
92    }
93
94    pub(crate) fn map<T, F: FnOnce(L::Output) -> T>(
95        self,
96        f: F,
97    ) -> LookupResult<MappedLookup<T, L, F>> {
98        match self {
99            LookupResult::Output(t) => LookupResult::Output(f(t)),
100            LookupResult::Load { load, continuation } => LookupResult::Load {
101                load,
102                continuation: MappedLookup {
103                    original: continuation,
104                    mutator: f,
105                },
106            },
107        }
108    }
109
110    pub(crate) fn unwrap(self) -> L::Output {
111        match self {
112            LookupResult::Output(t) => t,
113            LookupResult::Load { .. } => unreachable!("Internal API misuse"),
114        }
115    }
116}
117
118pub(crate) struct SimpleLookup<T, R, F>
119where
120    F: FnOnce(Option<Arc<gimli::Dwarf<R>>>) -> T,
121    R: gimli::Reader,
122{
123    f: F,
124    phantom: PhantomData<(T, R)>,
125}
126
127impl<T, R, F> SimpleLookup<T, R, F>
128where
129    F: FnOnce(Option<Arc<gimli::Dwarf<R>>>) -> T,
130    R: gimli::Reader,
131{
132    pub(crate) fn new_complete(t: F::Output) -> LookupResult<SimpleLookup<T, R, F>> {
133        LookupResult::Output(t)
134    }
135
136    pub(crate) fn new_needs_load(
137        load: SplitDwarfLoad<R>,
138        f: F,
139    ) -> LookupResult<SimpleLookup<T, R, F>> {
140        LookupResult::Load {
141            load,
142            continuation: SimpleLookup {
143                f,
144                phantom: PhantomData,
145            },
146        }
147    }
148}
149
150impl<T, R, F> LookupContinuation for SimpleLookup<T, R, F>
151where
152    F: FnOnce(Option<Arc<gimli::Dwarf<R>>>) -> T,
153    R: gimli::Reader,
154{
155    type Output = T;
156    type Buf = R;
157
158    fn resume(self, v: Option<Arc<gimli::Dwarf<Self::Buf>>>) -> LookupResult<Self> {
159        LookupResult::Output((self.f)(v))
160    }
161}
162
163pub(crate) struct MappedLookup<T, L, F>
164where
165    L: LookupContinuation,
166    F: FnOnce(L::Output) -> T,
167{
168    original: L,
169    mutator: F,
170}
171
172impl<T, L, F> LookupContinuation for MappedLookup<T, L, F>
173where
174    L: LookupContinuation,
175    F: FnOnce(L::Output) -> T,
176{
177    type Output = T;
178    type Buf = L::Buf;
179
180    fn resume(self, v: Option<Arc<gimli::Dwarf<Self::Buf>>>) -> LookupResult<Self> {
181        match self.original.resume(v) {
182            LookupResult::Output(t) => LookupResult::Output((self.mutator)(t)),
183            LookupResult::Load { load, continuation } => LookupResult::Load {
184                load,
185                continuation: MappedLookup {
186                    original: continuation,
187                    mutator: self.mutator,
188                },
189            },
190        }
191    }
192}
193
194/// Some functions (e.g. `find_frames`) require considering multiple
195/// compilation units, each of which might require their own split DWARF
196/// lookup (and thus produce a continuation).
197///
198/// We store the underlying continuation here as well as a mutator function
199/// that will either a) decide that the result of this continuation is
200/// what is needed and mutate it to the final result or b) produce another
201/// `LookupResult`. `new_lookup` will in turn eagerly drive any non-continuation
202/// `LookupResult` with successive invocations of the mutator, until a new
203/// continuation or a final result is produced. And finally, the impl of
204/// `LookupContinuation::resume` will call `new_lookup` each time the
205/// computation is resumed.
206pub(crate) struct LoopingLookup<T, L, F>
207where
208    L: LookupContinuation,
209    F: FnMut(L::Output) -> ControlFlow<T, LookupResult<L>>,
210{
211    continuation: L,
212    mutator: F,
213}
214
215impl<T, L, F> LoopingLookup<T, L, F>
216where
217    L: LookupContinuation,
218    F: FnMut(L::Output) -> ControlFlow<T, LookupResult<L>>,
219{
220    pub(crate) fn new_complete(t: T) -> LookupResult<Self> {
221        LookupResult::Output(t)
222    }
223
224    pub(crate) fn new_lookup(mut r: LookupResult<L>, mut mutator: F) -> LookupResult<Self> {
225        // Drive the loop eagerly so that we only ever have to represent one state
226        // (the r == ControlFlow::Continue state) in LoopingLookup.
227        loop {
228            match r {
229                LookupResult::Output(l) => match mutator(l) {
230                    ControlFlow::Break(t) => return LookupResult::Output(t),
231                    ControlFlow::Continue(r2) => {
232                        r = r2;
233                    }
234                },
235                LookupResult::Load { load, continuation } => {
236                    return LookupResult::Load {
237                        load,
238                        continuation: LoopingLookup {
239                            continuation,
240                            mutator,
241                        },
242                    };
243                }
244            }
245        }
246    }
247}
248
249impl<T, L, F> LookupContinuation for LoopingLookup<T, L, F>
250where
251    L: LookupContinuation,
252    F: FnMut(L::Output) -> ControlFlow<T, LookupResult<L>>,
253{
254    type Output = T;
255    type Buf = L::Buf;
256
257    fn resume(self, v: Option<Arc<gimli::Dwarf<Self::Buf>>>) -> LookupResult<Self> {
258        let r = self.continuation.resume(v);
259        LoopingLookup::new_lookup(r, self.mutator)
260    }
261}