Skip to main content

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