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