Coverage Report

Created: 2025-10-29 07:05

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