Coverage Report

Created: 2026-02-14 07:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/addr2line-0.25.1/src/lib.rs
Line
Count
Source
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")]
31
compile_error!("'--all-features' is not supported; use '--features all' instead");
32
33
#[cfg(feature = "std")]
34
extern crate std;
35
36
#[allow(unused_imports)]
37
#[macro_use]
38
extern crate alloc;
39
40
#[cfg(feature = "fallible-iterator")]
41
pub extern crate fallible_iterator;
42
pub extern crate gimli;
43
44
use alloc::sync::Arc;
45
use core::cell::OnceCell;
46
use core::ops::ControlFlow;
47
48
use crate::function::{Function, Functions, InlinedFunction, LazyFunctions};
49
use crate::line::{LazyLines, LineLocationRangeIter, Lines};
50
use crate::lookup::{LoopingLookup, SimpleLookup};
51
use crate::unit::{ResUnit, ResUnits, SupUnits};
52
53
#[cfg(feature = "smallvec")]
54
mod 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"))]
59
mod maybe_small {
60
    pub type Vec<T> = alloc::vec::Vec<T>;
61
    pub type IntoIter<T> = alloc::vec::IntoIter<T>;
62
}
63
64
mod frame;
65
pub use frame::{demangle, demangle_auto, Frame, FrameIter, FunctionName, Location};
66
67
mod function;
68
mod line;
69
70
#[cfg(feature = "loader")]
71
mod loader;
72
#[cfg(feature = "loader")]
73
pub use loader::{Loader, LoaderReader, Symbol};
74
75
mod lookup;
76
pub use lookup::{LookupContinuation, LookupResult, SplitDwarfLoad};
77
78
mod unit;
79
pub use unit::LocationRangeIter;
80
81
type Error = gimli::Error;
82
type LazyResult<T> = OnceCell<Result<T, Error>>;
83
84
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85
enum 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.
95
pub struct Context<R: gimli::Reader> {
96
    sections: Arc<gimli::Dwarf<R>>,
97
    units: ResUnits<R>,
98
    sup_units: SupUnits<R>,
99
}
100
101
impl<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
0
    pub fn from_sections(
107
0
        debug_abbrev: gimli::DebugAbbrev<R>,
108
0
        debug_addr: gimli::DebugAddr<R>,
109
0
        debug_aranges: gimli::DebugAranges<R>,
110
0
        debug_info: gimli::DebugInfo<R>,
111
0
        debug_line: gimli::DebugLine<R>,
112
0
        debug_line_str: gimli::DebugLineStr<R>,
113
0
        debug_ranges: gimli::DebugRanges<R>,
114
0
        debug_rnglists: gimli::DebugRngLists<R>,
115
0
        debug_str: gimli::DebugStr<R>,
116
0
        debug_str_offsets: gimli::DebugStrOffsets<R>,
117
0
        default_section: R,
118
0
    ) -> Result<Self, Error> {
119
0
        Self::from_dwarf(gimli::Dwarf {
120
0
            debug_abbrev,
121
0
            debug_addr,
122
0
            debug_aranges,
123
0
            debug_info,
124
0
            debug_line,
125
0
            debug_line_str,
126
0
            debug_macinfo: default_section.clone().into(),
127
0
            debug_macro: default_section.clone().into(),
128
0
            debug_str,
129
0
            debug_str_offsets,
130
0
            debug_types: default_section.clone().into(),
131
0
            locations: gimli::LocationLists::new(
132
0
                default_section.clone().into(),
133
0
                default_section.into(),
134
0
            ),
135
0
            ranges: gimli::RangeLists::new(debug_ranges, debug_rnglists),
136
0
            file_type: gimli::DwarfFileType::Main,
137
0
            sup: None,
138
0
            abbreviations_cache: gimli::AbbreviationsCache::new(),
139
0
        })
140
0
    }
141
142
    /// Construct a new `Context` from an existing [`gimli::Dwarf`] object.
143
    #[inline]
144
0
    pub fn from_dwarf(sections: gimli::Dwarf<R>) -> Result<Context<R>, Error> {
145
0
        Self::from_arc_dwarf(Arc::new(sections))
146
0
    }
Unexecuted instantiation: <addr2line::Context<gimli::read::endian_slice::EndianSlice<gimli::endianity::LittleEndian>>>::from_dwarf
Unexecuted instantiation: <addr2line::Context<_>>::from_dwarf
147
148
    /// Construct a new `Context` from an existing [`gimli::Dwarf`] object.
149
    #[inline]
150
0
    pub fn from_arc_dwarf(sections: Arc<gimli::Dwarf<R>>) -> Result<Context<R>, Error> {
151
0
        let units = ResUnits::parse(&sections)?;
152
0
        let sup_units = if let Some(sup) = sections.sup.as_ref() {
153
0
            SupUnits::parse(sup)?
154
        } else {
155
0
            SupUnits::default()
156
        };
157
0
        Ok(Context {
158
0
            sections,
159
0
            units,
160
0
            sup_units,
161
0
        })
162
0
    }
Unexecuted instantiation: <addr2line::Context<gimli::read::endian_slice::EndianSlice<gimli::endianity::LittleEndian>>>::from_arc_dwarf
Unexecuted instantiation: <addr2line::Context<_>>::from_arc_dwarf
163
}
164
165
impl<R: gimli::Reader> Context<R> {
166
    /// Find the DWARF unit corresponding to the given virtual memory address.
167
0
    pub fn find_dwarf_and_unit(
168
0
        &self,
169
0
        probe: u64,
170
0
    ) -> LookupResult<impl LookupContinuation<Output = Option<gimli::UnitRef<'_, R>>, Buf = R>>
171
    {
172
0
        let mut units_iter = self.units.find(probe);
173
0
        if let Some(unit) = units_iter.next() {
174
0
            return LoopingLookup::new_lookup(
175
0
                unit.find_function_or_location(probe, self),
176
0
                move |r| {
177
0
                    ControlFlow::Break(match r {
178
                        Ok((Some(_), _)) | Ok((_, Some(_))) => {
179
0
                            let (_file, unit) = unit
180
0
                                .dwarf_and_unit(self)
181
0
                                // We've already been through both error cases here to get to this point.
182
0
                                .unwrap()
183
0
                                .unwrap();
184
0
                            Some(unit)
185
                        }
186
0
                        _ => match units_iter.next() {
187
0
                            Some(next_unit) => {
188
0
                                return ControlFlow::Continue(
189
0
                                    next_unit.find_function_or_location(probe, self),
190
0
                                );
191
                            }
192
0
                            None => None,
193
                        },
194
                    })
195
0
                },
196
            );
197
0
        }
198
199
0
        LoopingLookup::new_complete(None)
200
0
    }
201
202
    /// Find the source file and line corresponding to the given virtual memory address.
203
0
    pub fn find_location(&self, probe: u64) -> Result<Option<Location<'_>>, Error> {
204
0
        for unit in self.units.find(probe) {
205
0
            if let Some(location) = unit.find_location(probe, &self.sections)? {
206
0
                return Ok(Some(location));
207
0
            }
208
        }
209
0
        Ok(None)
210
0
    }
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
0
    pub fn find_location_range(
215
0
        &self,
216
0
        probe_low: u64,
217
0
        probe_high: u64,
218
0
    ) -> Result<LocationRangeIter<'_, R>, Error> {
219
0
        self.units
220
0
            .find_location_range(probe_low, probe_high, &self.sections)
221
0
    }
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
0
    pub fn find_frames(
233
0
        &self,
234
0
        probe: u64,
235
0
    ) -> LookupResult<impl LookupContinuation<Output = Result<FrameIter<'_, R>, Error>, Buf = R>>
236
    {
237
0
        let mut units_iter = self.units.find(probe);
238
0
        if let Some(unit) = units_iter.next() {
239
0
            LoopingLookup::new_lookup(unit.find_function_or_location(probe, self), move |r| {
240
0
                ControlFlow::Break(match r {
241
0
                    Err(e) => Err(e),
242
0
                    Ok((Some(function), location)) => {
243
0
                        let inlined_functions = function.find_inlined_functions(probe);
244
0
                        Ok(FrameIter::new_frames(
245
0
                            unit,
246
0
                            &self.sections,
247
0
                            function,
248
0
                            inlined_functions,
249
0
                            location,
250
0
                        ))
251
                    }
252
0
                    Ok((None, Some(location))) => Ok(FrameIter::new_location(location)),
253
0
                    Ok((None, None)) => match units_iter.next() {
254
0
                        Some(next_unit) => {
255
0
                            return ControlFlow::Continue(
256
0
                                next_unit.find_function_or_location(probe, self),
257
0
                            );
258
                        }
259
0
                        None => Ok(FrameIter::new_empty()),
260
                    },
261
                })
262
0
            })
Unexecuted instantiation: <addr2line::Context<gimli::read::endian_slice::EndianSlice<gimli::endianity::LittleEndian>>>::find_frames::{closure#0}
Unexecuted instantiation: <addr2line::Context<_>>::find_frames::{closure#0}
263
        } else {
264
0
            LoopingLookup::new_complete(Ok(FrameIter::new_empty()))
265
        }
266
0
    }
Unexecuted instantiation: <addr2line::Context<gimli::read::endian_slice::EndianSlice<gimli::endianity::LittleEndian>>>::find_frames
Unexecuted instantiation: <addr2line::Context<_>>::find_frames
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
0
    pub fn preload_units(
297
0
        &'_ self,
298
0
        probe: u64,
299
0
    ) -> impl Iterator<
300
0
        Item = (
301
0
            SplitDwarfLoad<R>,
302
0
            impl FnOnce(Option<Arc<gimli::Dwarf<R>>>) -> Result<(), gimli::Error> + '_,
303
0
        ),
304
0
    > {
305
0
        self.units
306
0
            .find(probe)
307
0
            .filter_map(move |unit| match unit.dwarf_and_unit(self) {
308
0
                LookupResult::Output(_) => None,
309
0
                LookupResult::Load { load, continuation } => Some((load, |result| {
310
0
                    continuation.resume(result).unwrap().map(|_| ())
311
0
                })),
312
0
            })
313
0
    }
314
315
    /// Initialize all line data structures. This is used for benchmarks.
316
    #[doc(hidden)]
317
0
    pub fn parse_lines(&self) -> Result<(), Error> {
318
0
        for unit in self.units.iter() {
319
0
            unit.parse_lines(&self.sections)?;
320
        }
321
0
        Ok(())
322
0
    }
323
324
    /// Initialize all function data structures. This is used for benchmarks.
325
    #[doc(hidden)]
326
0
    pub fn parse_functions(&self) -> Result<(), Error> {
327
0
        for unit in self.units.iter() {
328
0
            unit.parse_functions(self).skip_all_loads()?;
329
        }
330
0
        Ok(())
331
0
    }
332
333
    /// Initialize all inlined function data structures. This is used for benchmarks.
334
    #[doc(hidden)]
335
0
    pub fn parse_inlined_functions(&self) -> Result<(), Error> {
336
0
        for unit in self.units.iter() {
337
0
            unit.parse_inlined_functions(self).skip_all_loads()?;
338
        }
339
0
        Ok(())
340
0
    }
341
}
342
343
impl<R: gimli::Reader> Context<R> {
344
    // Find the unit containing the given offset, and convert the offset into a unit offset.
345
0
    fn find_unit(
346
0
        &self,
347
0
        offset: gimli::DebugInfoOffset<R::Offset>,
348
0
        file: DebugFile,
349
0
    ) -> Result<(&gimli::Unit<R>, gimli::UnitOffset<R::Offset>), Error> {
350
0
        let unit = match file {
351
0
            DebugFile::Primary => self.units.find_offset(offset)?,
352
0
            DebugFile::Supplementary => self.sup_units.find_offset(offset)?,
353
0
            DebugFile::Dwo => return Err(gimli::Error::NoEntryAtGivenOffset),
354
        };
355
356
0
        let unit_offset = offset
357
0
            .to_unit_offset(&unit.header)
358
0
            .ok_or(gimli::Error::NoEntryAtGivenOffset)?;
359
0
        Ok((unit, unit_offset))
360
0
    }
Unexecuted instantiation: <addr2line::Context<gimli::read::endian_slice::EndianSlice<gimli::endianity::LittleEndian>>>::find_unit
Unexecuted instantiation: <addr2line::Context<_>>::find_unit
361
}
362
363
struct 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
370
impl<R: gimli::Reader> Default for RangeAttributes<R> {
371
0
    fn default() -> Self {
372
0
        RangeAttributes {
373
0
            low_pc: None,
374
0
            high_pc: None,
375
0
            size: None,
376
0
            ranges_offset: None,
377
0
        }
378
0
    }
Unexecuted instantiation: <addr2line::RangeAttributes<gimli::read::endian_slice::EndianSlice<gimli::endianity::LittleEndian>> as core::default::Default>::default
Unexecuted instantiation: <addr2line::RangeAttributes<_> as core::default::Default>::default
379
}
380
381
impl<R: gimli::Reader> RangeAttributes<R> {
382
0
    fn for_each_range<F: FnMut(gimli::Range)>(
383
0
        &self,
384
0
        unit: gimli::UnitRef<R>,
385
0
        mut f: F,
386
0
    ) -> Result<bool, Error> {
387
0
        let mut added_any = false;
388
0
        let mut add_range = |range: gimli::Range| {
389
0
            if range.begin < range.end {
390
0
                f(range);
391
0
                added_any = true
392
0
            }
393
0
        };
Unexecuted instantiation: <addr2line::RangeAttributes<gimli::read::endian_slice::EndianSlice<gimli::endianity::LittleEndian>>>::for_each_range::<<addr2line::function::Functions<gimli::read::endian_slice::EndianSlice<gimli::endianity::LittleEndian>>>::parse::{closure#0}>::{closure#0}
Unexecuted instantiation: <addr2line::RangeAttributes<gimli::read::endian_slice::EndianSlice<gimli::endianity::LittleEndian>>>::for_each_range::<<addr2line::function::InlinedFunction<gimli::read::endian_slice::EndianSlice<gimli::endianity::LittleEndian>>>::parse::{closure#0}>::{closure#0}
Unexecuted instantiation: <addr2line::RangeAttributes<gimli::read::endian_slice::EndianSlice<gimli::endianity::LittleEndian>>>::for_each_range::<<addr2line::unit::ResUnits<gimli::read::endian_slice::EndianSlice<gimli::endianity::LittleEndian>>>::parse::{closure#3}>::{closure#0}
Unexecuted instantiation: <addr2line::RangeAttributes<_>>::for_each_range::<_>::{closure#0}
394
0
        if let Some(ranges_offset) = self.ranges_offset {
395
0
            let mut range_list = unit.ranges(ranges_offset)?;
396
0
            while let Some(range) = range_list.next()? {
397
0
                add_range(range);
398
0
            }
399
0
        } else if let (Some(begin), Some(end)) = (self.low_pc, self.high_pc) {
400
0
            add_range(gimli::Range { begin, end });
401
0
        } else if let (Some(begin), Some(size)) = (self.low_pc, self.size) {
402
0
            // If `begin` is a -1 tombstone, this will overflow and the check in
403
0
            // `add_range` will ignore it.
404
0
            let end = begin.wrapping_add(size);
405
0
            add_range(gimli::Range { begin, end });
406
0
        }
407
0
        Ok(added_any)
408
0
    }
Unexecuted instantiation: <addr2line::RangeAttributes<gimli::read::endian_slice::EndianSlice<gimli::endianity::LittleEndian>>>::for_each_range::<<addr2line::function::Functions<gimli::read::endian_slice::EndianSlice<gimli::endianity::LittleEndian>>>::parse::{closure#0}>
Unexecuted instantiation: <addr2line::RangeAttributes<gimli::read::endian_slice::EndianSlice<gimli::endianity::LittleEndian>>>::for_each_range::<<addr2line::function::InlinedFunction<gimli::read::endian_slice::EndianSlice<gimli::endianity::LittleEndian>>>::parse::{closure#0}>
Unexecuted instantiation: <addr2line::RangeAttributes<gimli::read::endian_slice::EndianSlice<gimli::endianity::LittleEndian>>>::for_each_range::<<addr2line::unit::ResUnits<gimli::read::endian_slice::EndianSlice<gimli::endianity::LittleEndian>>>::parse::{closure#3}>
Unexecuted instantiation: <addr2line::RangeAttributes<_>>::for_each_range::<_>
409
}
410
411
#[cfg(test)]
412
mod 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
}