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        let sections = Arc::new(sections);
143        let units = ResUnits::parse(&sections)?;
144        let sup_units = if let Some(sup) = sections.sup.as_ref() {
145            SupUnits::parse(sup)?
146        } else {
147            SupUnits::default()
148        };
149        Ok(Context {
150            sections,
151            units,
152            sup_units,
153        })
154    }
155}
156
157impl<R: gimli::Reader> Context<R> {
158    /// Find the DWARF unit corresponding to the given virtual memory address.
159    pub fn find_dwarf_and_unit(
160        &self,
161        probe: u64,
162    ) -> LookupResult<impl LookupContinuation<Output = Option<gimli::UnitRef<R>>, Buf = R>> {
163        let mut units_iter = self.units.find(probe);
164        if let Some(unit) = units_iter.next() {
165            return LoopingLookup::new_lookup(
166                unit.find_function_or_location(probe, self),
167                move |r| {
168                    ControlFlow::Break(match r {
169                        Ok((Some(_), _)) | Ok((_, Some(_))) => {
170                            let (_file, unit) = unit
171                                .dwarf_and_unit(self)
172                                // We've already been through both error cases here to get to this point.
173                                .unwrap()
174                                .unwrap();
175                            Some(unit)
176                        }
177                        _ => match units_iter.next() {
178                            Some(next_unit) => {
179                                return ControlFlow::Continue(
180                                    next_unit.find_function_or_location(probe, self),
181                                );
182                            }
183                            None => None,
184                        },
185                    })
186                },
187            );
188        }
189
190        LoopingLookup::new_complete(None)
191    }
192
193    /// Find the source file and line corresponding to the given virtual memory address.
194    pub fn find_location(&self, probe: u64) -> Result<Option<Location<'_>>, Error> {
195        for unit in self.units.find(probe) {
196            if let Some(location) = unit.find_location(probe, &self.sections)? {
197                return Ok(Some(location));
198            }
199        }
200        Ok(None)
201    }
202
203    /// Return source file and lines for a range of addresses. For each location it also
204    /// returns the address and size of the range of the underlying instructions.
205    pub fn find_location_range(
206        &self,
207        probe_low: u64,
208        probe_high: u64,
209    ) -> Result<LocationRangeIter<'_, R>, Error> {
210        self.units
211            .find_location_range(probe_low, probe_high, &self.sections)
212    }
213
214    /// Return an iterator for the function frames corresponding to the given virtual
215    /// memory address.
216    ///
217    /// If the probe address is not for an inline function then only one frame is
218    /// returned.
219    ///
220    /// If the probe address is for an inline function then the first frame corresponds
221    /// to the innermost inline function.  Subsequent frames contain the caller and call
222    /// location, until an non-inline caller is reached.
223    pub fn find_frames(
224        &self,
225        probe: u64,
226    ) -> LookupResult<impl LookupContinuation<Output = Result<FrameIter<'_, R>, Error>, Buf = R>>
227    {
228        let mut units_iter = self.units.find(probe);
229        if let Some(unit) = units_iter.next() {
230            LoopingLookup::new_lookup(unit.find_function_or_location(probe, self), move |r| {
231                ControlFlow::Break(match r {
232                    Err(e) => Err(e),
233                    Ok((Some(function), location)) => {
234                        let inlined_functions = function.find_inlined_functions(probe);
235                        Ok(FrameIter::new_frames(
236                            unit,
237                            &self.sections,
238                            function,
239                            inlined_functions,
240                            location,
241                        ))
242                    }
243                    Ok((None, Some(location))) => Ok(FrameIter::new_location(location)),
244                    Ok((None, None)) => match units_iter.next() {
245                        Some(next_unit) => {
246                            return ControlFlow::Continue(
247                                next_unit.find_function_or_location(probe, self),
248                            );
249                        }
250                        None => Ok(FrameIter::new_empty()),
251                    },
252                })
253            })
254        } else {
255            LoopingLookup::new_complete(Ok(FrameIter::new_empty()))
256        }
257    }
258
259    /// Preload units for `probe`.
260    ///
261    /// The iterator returns pairs of `SplitDwarfLoad`s containing the
262    /// information needed to locate and load split DWARF for `probe` and
263    /// a matching callback to invoke once that data is available.
264    ///
265    /// If this method is called, and all of the returned closures are invoked,
266    /// addr2line guarantees that any future API call for the address `probe`
267    /// will not require the loading of any split DWARF.
268    ///
269    /// ```no_run
270    ///   # use addr2line::*;
271    ///   # use std::sync::Arc;
272    ///   # let ctx: Context<gimli::EndianSlice<gimli::RunTimeEndian>> = todo!();
273    ///   # let do_split_dwarf_load = |load: SplitDwarfLoad<gimli::EndianSlice<gimli::RunTimeEndian>>| -> Option<Arc<gimli::Dwarf<gimli::EndianSlice<gimli::RunTimeEndian>>>> { None };
274    ///   const ADDRESS: u64 = 0xdeadbeef;
275    ///   ctx.preload_units(ADDRESS).for_each(|(load, callback)| {
276    ///     let dwo = do_split_dwarf_load(load);
277    ///     callback(dwo);
278    ///   });
279    ///
280    ///   let frames_iter = match ctx.find_frames(ADDRESS) {
281    ///     LookupResult::Output(result) => result,
282    ///     LookupResult::Load { .. } => unreachable!("addr2line promised we wouldn't get here"),
283    ///   };
284    ///
285    ///   // ...
286    /// ```
287    pub fn preload_units(
288        &'_ self,
289        probe: u64,
290    ) -> impl Iterator<
291        Item = (
292            SplitDwarfLoad<R>,
293            impl FnOnce(Option<Arc<gimli::Dwarf<R>>>) -> Result<(), gimli::Error> + '_,
294        ),
295    > {
296        self.units
297            .find(probe)
298            .filter_map(move |unit| match unit.dwarf_and_unit(self) {
299                LookupResult::Output(_) => None,
300                LookupResult::Load { load, continuation } => Some((load, |result| {
301                    continuation.resume(result).unwrap().map(|_| ())
302                })),
303            })
304    }
305
306    /// Initialize all line data structures. This is used for benchmarks.
307    #[doc(hidden)]
308    pub fn parse_lines(&self) -> Result<(), Error> {
309        for unit in self.units.iter() {
310            unit.parse_lines(&self.sections)?;
311        }
312        Ok(())
313    }
314
315    /// Initialize all function data structures. This is used for benchmarks.
316    #[doc(hidden)]
317    pub fn parse_functions(&self) -> Result<(), Error> {
318        for unit in self.units.iter() {
319            unit.parse_functions(self).skip_all_loads()?;
320        }
321        Ok(())
322    }
323
324    /// Initialize all inlined function data structures. This is used for benchmarks.
325    #[doc(hidden)]
326    pub fn parse_inlined_functions(&self) -> Result<(), Error> {
327        for unit in self.units.iter() {
328            unit.parse_inlined_functions(self).skip_all_loads()?;
329        }
330        Ok(())
331    }
332}
333
334impl<R: gimli::Reader> Context<R> {
335    // Find the unit containing the given offset, and convert the offset into a unit offset.
336    fn find_unit(
337        &self,
338        offset: gimli::DebugInfoOffset<R::Offset>,
339        file: DebugFile,
340    ) -> Result<(&gimli::Unit<R>, gimli::UnitOffset<R::Offset>), Error> {
341        let unit = match file {
342            DebugFile::Primary => self.units.find_offset(offset)?,
343            DebugFile::Supplementary => self.sup_units.find_offset(offset)?,
344            DebugFile::Dwo => return Err(gimli::Error::NoEntryAtGivenOffset),
345        };
346
347        let unit_offset = offset
348            .to_unit_offset(&unit.header)
349            .ok_or(gimli::Error::NoEntryAtGivenOffset)?;
350        Ok((unit, unit_offset))
351    }
352}
353
354struct RangeAttributes<R: gimli::Reader> {
355    low_pc: Option<u64>,
356    high_pc: Option<u64>,
357    size: Option<u64>,
358    ranges_offset: Option<gimli::RangeListsOffset<<R as gimli::Reader>::Offset>>,
359}
360
361impl<R: gimli::Reader> Default for RangeAttributes<R> {
362    fn default() -> Self {
363        RangeAttributes {
364            low_pc: None,
365            high_pc: None,
366            size: None,
367            ranges_offset: None,
368        }
369    }
370}
371
372impl<R: gimli::Reader> RangeAttributes<R> {
373    fn for_each_range<F: FnMut(gimli::Range)>(
374        &self,
375        unit: gimli::UnitRef<R>,
376        mut f: F,
377    ) -> Result<bool, Error> {
378        let mut added_any = false;
379        let mut add_range = |range: gimli::Range| {
380            if range.begin < range.end {
381                f(range);
382                added_any = true
383            }
384        };
385        if let Some(ranges_offset) = self.ranges_offset {
386            let mut range_list = unit.ranges(ranges_offset)?;
387            while let Some(range) = range_list.next()? {
388                add_range(range);
389            }
390        } else if let (Some(begin), Some(end)) = (self.low_pc, self.high_pc) {
391            add_range(gimli::Range { begin, end });
392        } else if let (Some(begin), Some(size)) = (self.low_pc, self.size) {
393            add_range(gimli::Range {
394                begin,
395                end: begin + size,
396            });
397        }
398        Ok(added_any)
399    }
400}
401
402#[cfg(test)]
403mod tests {
404    #[test]
405    fn context_is_send() {
406        fn assert_is_send<T: Send>() {}
407        assert_is_send::<crate::Context<gimli::read::EndianSlice<'_, gimli::LittleEndian>>>();
408    }
409}