addr2line/
lib.rs

1//! `addr2line` provides a cross-platform library for retrieving per-address debug information
2//! from files with DWARF debug information. Given an address, it can return the file name,
3//! line number, and function name associated with that address, as well as the inline call
4//! stack leading to that address.
5//!
6//! At the lowest level, the library uses a [`Context`] to cache parsed information so that
7//! multiple lookups are efficient. To create a `Context`, you first need to open and parse the
8//! file using an object file parser such as [`object`](https://github.com/gimli-rs/object),
9//! create a [`gimli::Dwarf`], and finally call [`Context::from_dwarf`].
10//!
11//! Location information is obtained with [`Context::find_location`] or
12//! [`Context::find_location_range`]. Function information is obtained with
13//! [`Context::find_frames`], which returns a frame for each inline function. Each frame
14//! contains both name and location.
15//!
16//! The library also provides a [`Loader`] which internally memory maps the files,
17//! uses the `object` crate to do the parsing, and creates a `Context`.
18//! The `Context` is not exposed, but the `Loader` provides the same functionality
19//! via [`Loader::find_location`], [`Loader::find_location_range`], and
20//! [`Loader::find_frames`]. The `Loader` also provides [`Loader::find_symbol`]
21//! to use the symbol table instead of DWARF debugging information.
22//! The `Loader` will load Mach-O dSYM files and split DWARF files as needed.
23//!
24//! The crate has a CLI wrapper around the library which provides some of
25//! the functionality of the `addr2line` command line tool distributed with
26//! [GNU binutils](https://sourceware.org/binutils/docs/binutils/addr2line.html).
27#![deny(missing_docs)]
28#![no_std]
29
30#[cfg(feature = "cargo-all")]
31compile_error!("'--all-features' is not supported; use '--features all' instead");
32
33#[cfg(feature = "std")]
34extern crate std;
35
36#[allow(unused_imports)]
37#[macro_use]
38extern crate alloc;
39
40#[cfg(feature = "fallible-iterator")]
41pub extern crate fallible_iterator;
42pub extern crate gimli;
43
44use alloc::sync::Arc;
45use core::ops::ControlFlow;
46
47use crate::function::{Function, Functions, InlinedFunction, LazyFunctions};
48use crate::line::{LazyLines, LineLocationRangeIter, Lines};
49use crate::lookup::{LoopingLookup, SimpleLookup};
50use crate::unit::{ResUnit, ResUnits, SupUnits};
51
52#[cfg(feature = "smallvec")]
53mod maybe_small {
54    pub type Vec<T> = smallvec::SmallVec<[T; 16]>;
55    pub type IntoIter<T> = smallvec::IntoIter<[T; 16]>;
56}
57#[cfg(not(feature = "smallvec"))]
58mod maybe_small {
59    pub type Vec<T> = alloc::vec::Vec<T>;
60    pub type IntoIter<T> = alloc::vec::IntoIter<T>;
61}
62
63mod frame;
64pub use frame::{demangle, demangle_auto, Frame, FrameIter, FunctionName, Location};
65
66mod function;
67mod lazy;
68mod line;
69
70#[cfg(feature = "loader")]
71mod loader;
72#[cfg(feature = "loader")]
73pub use loader::{Loader, LoaderReader};
74
75mod lookup;
76pub use lookup::{LookupContinuation, LookupResult, SplitDwarfLoad};
77
78mod unit;
79pub use unit::LocationRangeIter;
80
81type Error = gimli::Error;
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84enum DebugFile {
85    Primary,
86    Supplementary,
87    Dwo,
88}
89
90/// The state necessary to perform address to line translation.
91///
92/// Constructing a `Context` is somewhat costly, so users should aim to reuse `Context`s
93/// when performing lookups for many addresses in the same executable.
94pub struct Context<R: gimli::Reader> {
95    sections: Arc<gimli::Dwarf<R>>,
96    units: ResUnits<R>,
97    sup_units: SupUnits<R>,
98}
99
100impl<R: gimli::Reader> Context<R> {
101    /// Construct a new `Context` from DWARF sections.
102    ///
103    /// This method does not support using a supplementary object file.
104    #[allow(clippy::too_many_arguments)]
105    pub fn from_sections(
106        debug_abbrev: gimli::DebugAbbrev<R>,
107        debug_addr: gimli::DebugAddr<R>,
108        debug_aranges: gimli::DebugAranges<R>,
109        debug_info: gimli::DebugInfo<R>,
110        debug_line: gimli::DebugLine<R>,
111        debug_line_str: gimli::DebugLineStr<R>,
112        debug_ranges: gimli::DebugRanges<R>,
113        debug_rnglists: gimli::DebugRngLists<R>,
114        debug_str: gimli::DebugStr<R>,
115        debug_str_offsets: gimli::DebugStrOffsets<R>,
116        default_section: R,
117    ) -> Result<Self, Error> {
118        Self::from_dwarf(gimli::Dwarf {
119            debug_abbrev,
120            debug_addr,
121            debug_aranges,
122            debug_info,
123            debug_line,
124            debug_line_str,
125            debug_str,
126            debug_str_offsets,
127            debug_types: default_section.clone().into(),
128            locations: gimli::LocationLists::new(
129                default_section.clone().into(),
130                default_section.into(),
131            ),
132            ranges: gimli::RangeLists::new(debug_ranges, debug_rnglists),
133            file_type: gimli::DwarfFileType::Main,
134            sup: None,
135            abbreviations_cache: gimli::AbbreviationsCache::new(),
136        })
137    }
138
139    /// Construct a new `Context` from an existing [`gimli::Dwarf`] object.
140    #[inline]
141    pub fn from_dwarf(sections: gimli::Dwarf<R>) -> Result<Context<R>, Error> {
142        Self::from_arc_dwarf(Arc::new(sections))
143    }
144
145    /// Construct a new `Context` from an existing [`gimli::Dwarf`] object.
146    #[inline]
147    pub fn from_arc_dwarf(sections: Arc<gimli::Dwarf<R>>) -> Result<Context<R>, Error> {
148        let units = ResUnits::parse(&sections)?;
149        let sup_units = if let Some(sup) = sections.sup.as_ref() {
150            SupUnits::parse(sup)?
151        } else {
152            SupUnits::default()
153        };
154        Ok(Context {
155            sections,
156            units,
157            sup_units,
158        })
159    }
160}
161
162impl<R: gimli::Reader> Context<R> {
163    /// Find the DWARF unit corresponding to the given virtual memory address.
164    pub fn find_dwarf_and_unit(
165        &self,
166        probe: u64,
167    ) -> LookupResult<impl LookupContinuation<Output = Option<gimli::UnitRef<R>>, Buf = R>> {
168        let mut units_iter = self.units.find(probe);
169        if let Some(unit) = units_iter.next() {
170            return LoopingLookup::new_lookup(
171                unit.find_function_or_location(probe, self),
172                move |r| {
173                    ControlFlow::Break(match r {
174                        Ok((Some(_), _)) | Ok((_, Some(_))) => {
175                            let (_file, unit) = unit
176                                .dwarf_and_unit(self)
177                                // We've already been through both error cases here to get to this point.
178                                .unwrap()
179                                .unwrap();
180                            Some(unit)
181                        }
182                        _ => match units_iter.next() {
183                            Some(next_unit) => {
184                                return ControlFlow::Continue(
185                                    next_unit.find_function_or_location(probe, self),
186                                );
187                            }
188                            None => None,
189                        },
190                    })
191                },
192            );
193        }
194
195        LoopingLookup::new_complete(None)
196    }
197
198    /// Find the source file and line corresponding to the given virtual memory address.
199    pub fn find_location(&self, probe: u64) -> Result<Option<Location<'_>>, Error> {
200        for unit in self.units.find(probe) {
201            if let Some(location) = unit.find_location(probe, &self.sections)? {
202                return Ok(Some(location));
203            }
204        }
205        Ok(None)
206    }
207
208    /// Return source file and lines for a range of addresses. For each location it also
209    /// returns the address and size of the range of the underlying instructions.
210    pub fn find_location_range(
211        &self,
212        probe_low: u64,
213        probe_high: u64,
214    ) -> Result<LocationRangeIter<'_, R>, Error> {
215        self.units
216            .find_location_range(probe_low, probe_high, &self.sections)
217    }
218
219    /// Return an iterator for the function frames corresponding to the given virtual
220    /// memory address.
221    ///
222    /// If the probe address is not for an inline function then only one frame is
223    /// returned.
224    ///
225    /// If the probe address is for an inline function then the first frame corresponds
226    /// to the innermost inline function.  Subsequent frames contain the caller and call
227    /// location, until an non-inline caller is reached.
228    pub fn find_frames(
229        &self,
230        probe: u64,
231    ) -> LookupResult<impl LookupContinuation<Output = Result<FrameIter<'_, R>, Error>, Buf = R>>
232    {
233        let mut units_iter = self.units.find(probe);
234        if let Some(unit) = units_iter.next() {
235            LoopingLookup::new_lookup(unit.find_function_or_location(probe, self), move |r| {
236                ControlFlow::Break(match r {
237                    Err(e) => Err(e),
238                    Ok((Some(function), location)) => {
239                        let inlined_functions = function.find_inlined_functions(probe);
240                        Ok(FrameIter::new_frames(
241                            unit,
242                            &self.sections,
243                            function,
244                            inlined_functions,
245                            location,
246                        ))
247                    }
248                    Ok((None, Some(location))) => Ok(FrameIter::new_location(location)),
249                    Ok((None, None)) => match units_iter.next() {
250                        Some(next_unit) => {
251                            return ControlFlow::Continue(
252                                next_unit.find_function_or_location(probe, self),
253                            );
254                        }
255                        None => Ok(FrameIter::new_empty()),
256                    },
257                })
258            })
259        } else {
260            LoopingLookup::new_complete(Ok(FrameIter::new_empty()))
261        }
262    }
263
264    /// Preload units for `probe`.
265    ///
266    /// The iterator returns pairs of `SplitDwarfLoad`s containing the
267    /// information needed to locate and load split DWARF for `probe` and
268    /// a matching callback to invoke once that data is available.
269    ///
270    /// If this method is called, and all of the returned closures are invoked,
271    /// addr2line guarantees that any future API call for the address `probe`
272    /// will not require the loading of any split DWARF.
273    ///
274    /// ```no_run
275    ///   # use addr2line::*;
276    ///   # use std::sync::Arc;
277    ///   # let ctx: Context<gimli::EndianSlice<gimli::RunTimeEndian>> = todo!();
278    ///   # let do_split_dwarf_load = |load: SplitDwarfLoad<gimli::EndianSlice<gimli::RunTimeEndian>>| -> Option<Arc<gimli::Dwarf<gimli::EndianSlice<gimli::RunTimeEndian>>>> { None };
279    ///   const ADDRESS: u64 = 0xdeadbeef;
280    ///   ctx.preload_units(ADDRESS).for_each(|(load, callback)| {
281    ///     let dwo = do_split_dwarf_load(load);
282    ///     callback(dwo);
283    ///   });
284    ///
285    ///   let frames_iter = match ctx.find_frames(ADDRESS) {
286    ///     LookupResult::Output(result) => result,
287    ///     LookupResult::Load { .. } => unreachable!("addr2line promised we wouldn't get here"),
288    ///   };
289    ///
290    ///   // ...
291    /// ```
292    pub fn preload_units(
293        &'_ self,
294        probe: u64,
295    ) -> impl Iterator<
296        Item = (
297            SplitDwarfLoad<R>,
298            impl FnOnce(Option<Arc<gimli::Dwarf<R>>>) -> Result<(), gimli::Error> + '_,
299        ),
300    > {
301        self.units
302            .find(probe)
303            .filter_map(move |unit| match unit.dwarf_and_unit(self) {
304                LookupResult::Output(_) => None,
305                LookupResult::Load { load, continuation } => Some((load, |result| {
306                    continuation.resume(result).unwrap().map(|_| ())
307                })),
308            })
309    }
310
311    /// Initialize all line data structures. This is used for benchmarks.
312    #[doc(hidden)]
313    pub fn parse_lines(&self) -> Result<(), Error> {
314        for unit in self.units.iter() {
315            unit.parse_lines(&self.sections)?;
316        }
317        Ok(())
318    }
319
320    /// Initialize all function data structures. This is used for benchmarks.
321    #[doc(hidden)]
322    pub fn parse_functions(&self) -> Result<(), Error> {
323        for unit in self.units.iter() {
324            unit.parse_functions(self).skip_all_loads()?;
325        }
326        Ok(())
327    }
328
329    /// Initialize all inlined function data structures. This is used for benchmarks.
330    #[doc(hidden)]
331    pub fn parse_inlined_functions(&self) -> Result<(), Error> {
332        for unit in self.units.iter() {
333            unit.parse_inlined_functions(self).skip_all_loads()?;
334        }
335        Ok(())
336    }
337}
338
339impl<R: gimli::Reader> Context<R> {
340    // Find the unit containing the given offset, and convert the offset into a unit offset.
341    fn find_unit(
342        &self,
343        offset: gimli::DebugInfoOffset<R::Offset>,
344        file: DebugFile,
345    ) -> Result<(&gimli::Unit<R>, gimli::UnitOffset<R::Offset>), Error> {
346        let unit = match file {
347            DebugFile::Primary => self.units.find_offset(offset)?,
348            DebugFile::Supplementary => self.sup_units.find_offset(offset)?,
349            DebugFile::Dwo => return Err(gimli::Error::NoEntryAtGivenOffset),
350        };
351
352        let unit_offset = offset
353            .to_unit_offset(&unit.header)
354            .ok_or(gimli::Error::NoEntryAtGivenOffset)?;
355        Ok((unit, unit_offset))
356    }
357}
358
359struct RangeAttributes<R: gimli::Reader> {
360    low_pc: Option<u64>,
361    high_pc: Option<u64>,
362    size: Option<u64>,
363    ranges_offset: Option<gimli::RangeListsOffset<<R as gimli::Reader>::Offset>>,
364}
365
366impl<R: gimli::Reader> Default for RangeAttributes<R> {
367    fn default() -> Self {
368        RangeAttributes {
369            low_pc: None,
370            high_pc: None,
371            size: None,
372            ranges_offset: None,
373        }
374    }
375}
376
377impl<R: gimli::Reader> RangeAttributes<R> {
378    fn for_each_range<F: FnMut(gimli::Range)>(
379        &self,
380        unit: gimli::UnitRef<R>,
381        mut f: F,
382    ) -> Result<bool, Error> {
383        let mut added_any = false;
384        let mut add_range = |range: gimli::Range| {
385            if range.begin < range.end {
386                f(range);
387                added_any = true
388            }
389        };
390        if let Some(ranges_offset) = self.ranges_offset {
391            let mut range_list = unit.ranges(ranges_offset)?;
392            while let Some(range) = range_list.next()? {
393                add_range(range);
394            }
395        } else if let (Some(begin), Some(end)) = (self.low_pc, self.high_pc) {
396            add_range(gimli::Range { begin, end });
397        } else if let (Some(begin), Some(size)) = (self.low_pc, self.size) {
398            // If `begin` is a -1 tombstone, this will overflow and the check in
399            // `add_range` will ignore it.
400            let end = begin.wrapping_add(size);
401            add_range(gimli::Range { begin, end });
402        }
403        Ok(added_any)
404    }
405}
406
407#[cfg(test)]
408mod tests {
409    #[test]
410    fn context_is_send() {
411        fn assert_is_send<T: Send>() {}
412        assert_is_send::<crate::Context<gimli::read::EndianSlice<'_, gimli::LittleEndian>>>();
413    }
414}