addr2line/
unit.rs

1use alloc::boxed::Box;
2use alloc::sync::Arc;
3use alloc::vec::Vec;
4use core::cmp;
5
6use crate::lazy::LazyResult;
7use crate::{
8    Context, DebugFile, Error, Function, Functions, LazyFunctions, LazyLines,
9    LineLocationRangeIter, Lines, Location, LookupContinuation, LookupResult, RangeAttributes,
10    SimpleLookup, SplitDwarfLoad,
11};
12
13pub(crate) struct UnitRange {
14    unit_id: usize,
15    min_begin: u64,
16    range: gimli::Range,
17}
18
19pub(crate) struct ResUnit<R: gimli::Reader> {
20    offset: gimli::DebugInfoOffset<R::Offset>,
21    dw_unit: gimli::Unit<R>,
22    pub(crate) lang: Option<gimli::DwLang>,
23    lines: LazyLines,
24    functions: LazyFunctions<R>,
25    dwo: LazyResult<Option<Box<DwoUnit<R>>>>,
26}
27
28type UnitRef<'unit, R> = (DebugFile, gimli::UnitRef<'unit, R>);
29
30impl<R: gimli::Reader> ResUnit<R> {
31    pub(crate) fn unit_ref<'a>(&'a self, sections: &'a gimli::Dwarf<R>) -> gimli::UnitRef<'a, R> {
32        gimli::UnitRef::new(sections, &self.dw_unit)
33    }
34
35    /// Returns the DWARF sections and the unit.
36    ///
37    /// Loads the DWO unit if necessary.
38    pub(crate) fn dwarf_and_unit<'unit, 'ctx: 'unit>(
39        &'unit self,
40        ctx: &'ctx Context<R>,
41    ) -> LookupResult<
42        SimpleLookup<
43            Result<UnitRef<'unit, R>, Error>,
44            R,
45            impl FnOnce(Option<Arc<gimli::Dwarf<R>>>) -> Result<UnitRef<'unit, R>, Error>,
46        >,
47    > {
48        let map_dwo = move |dwo: &'unit Result<Option<Box<DwoUnit<R>>>, Error>| match dwo {
49            Ok(Some(dwo)) => Ok((DebugFile::Dwo, dwo.unit_ref())),
50            Ok(None) => Ok((DebugFile::Primary, self.unit_ref(&*ctx.sections))),
51            Err(e) => Err(*e),
52        };
53        let complete = |dwo| SimpleLookup::new_complete(map_dwo(dwo));
54
55        if let Some(dwo) = self.dwo.borrow() {
56            return complete(dwo);
57        }
58
59        let dwo_id = match self.dw_unit.dwo_id {
60            None => {
61                return complete(self.dwo.borrow_with(|| Ok(None)));
62            }
63            Some(dwo_id) => dwo_id,
64        };
65
66        let comp_dir = self.dw_unit.comp_dir.clone();
67
68        let dwo_name = self.dw_unit.dwo_name().and_then(|s| {
69            if let Some(s) = s {
70                Ok(Some(ctx.sections.attr_string(&self.dw_unit, s)?))
71            } else {
72                Ok(None)
73            }
74        });
75
76        let path = match dwo_name {
77            Ok(v) => v,
78            Err(e) => {
79                return complete(self.dwo.borrow_with(|| Err(e)));
80            }
81        };
82
83        let process_dwo = move |dwo_dwarf: Option<Arc<gimli::Dwarf<R>>>| {
84            let dwo_dwarf = match dwo_dwarf {
85                None => return Ok(None),
86                Some(dwo_dwarf) => dwo_dwarf,
87            };
88            let mut dwo_units = dwo_dwarf.units();
89            let dwo_header = match dwo_units.next()? {
90                Some(dwo_header) => dwo_header,
91                None => return Ok(None),
92            };
93
94            let mut dwo_unit = dwo_dwarf.unit(dwo_header)?;
95            dwo_unit.copy_relocated_attributes(&self.dw_unit);
96            Ok(Some(Box::new(DwoUnit {
97                sections: dwo_dwarf,
98                dw_unit: dwo_unit,
99            })))
100        };
101
102        SimpleLookup::new_needs_load(
103            SplitDwarfLoad {
104                dwo_id,
105                comp_dir,
106                path,
107                parent: ctx.sections.clone(),
108            },
109            move |dwo_dwarf| map_dwo(self.dwo.borrow_with(|| process_dwo(dwo_dwarf))),
110        )
111    }
112
113    pub(crate) fn parse_lines(&self, sections: &gimli::Dwarf<R>) -> Result<Option<&Lines>, Error> {
114        // NB: line information is always stored in the main debug file so this does not need
115        // to handle DWOs.
116        let ilnp = match self.dw_unit.line_program {
117            Some(ref ilnp) => ilnp,
118            None => return Ok(None),
119        };
120        self.lines.borrow(self.unit_ref(sections), ilnp).map(Some)
121    }
122
123    pub(crate) fn parse_functions<'unit, 'ctx: 'unit>(
124        &'unit self,
125        ctx: &'ctx Context<R>,
126    ) -> LookupResult<impl LookupContinuation<Output = Result<&'unit Functions<R>, Error>, Buf = R>>
127    {
128        self.dwarf_and_unit(ctx).map(move |r| {
129            let (_file, unit) = r?;
130            self.functions.borrow(unit)
131        })
132    }
133
134    pub(crate) fn parse_inlined_functions<'unit, 'ctx: 'unit>(
135        &'unit self,
136        ctx: &'ctx Context<R>,
137    ) -> LookupResult<impl LookupContinuation<Output = Result<(), Error>, Buf = R> + 'unit> {
138        self.dwarf_and_unit(ctx).map(move |r| {
139            let (file, unit) = r?;
140            self.functions
141                .borrow(unit)?
142                .parse_inlined_functions(file, unit, ctx)
143        })
144    }
145
146    pub(crate) fn find_location(
147        &self,
148        probe: u64,
149        sections: &gimli::Dwarf<R>,
150    ) -> Result<Option<Location<'_>>, Error> {
151        let Some(lines) = self.parse_lines(sections)? else {
152            return Ok(None);
153        };
154        lines.find_location(probe)
155    }
156
157    #[inline]
158    pub(crate) fn find_location_range(
159        &self,
160        probe_low: u64,
161        probe_high: u64,
162        sections: &gimli::Dwarf<R>,
163    ) -> Result<Option<LineLocationRangeIter<'_>>, Error> {
164        let Some(lines) = self.parse_lines(sections)? else {
165            return Ok(None);
166        };
167        lines.find_location_range(probe_low, probe_high).map(Some)
168    }
169
170    pub(crate) fn find_function_or_location<'unit, 'ctx: 'unit>(
171        &'unit self,
172        probe: u64,
173        ctx: &'ctx Context<R>,
174    ) -> LookupResult<
175        impl LookupContinuation<
176            Output = Result<(Option<&'unit Function<R>>, Option<Location<'unit>>), Error>,
177            Buf = R,
178        >,
179    > {
180        self.dwarf_and_unit(ctx).map(move |r| {
181            let (file, unit) = r?;
182            let functions = self.functions.borrow(unit)?;
183            let function = match functions.find_address(probe) {
184                Some(address) => {
185                    let function_index = functions.addresses[address].function;
186                    let function = &functions.functions[function_index];
187                    Some(function.borrow(file, unit, ctx)?)
188                }
189                None => None,
190            };
191            let location = self.find_location(probe, unit.dwarf)?;
192            Ok((function, location))
193        })
194    }
195}
196
197pub(crate) struct ResUnits<R: gimli::Reader> {
198    ranges: Box<[UnitRange]>,
199    units: Box<[ResUnit<R>]>,
200}
201
202impl<R: gimli::Reader> ResUnits<R> {
203    pub(crate) fn parse(sections: &gimli::Dwarf<R>) -> Result<Self, Error> {
204        // Find all the references to compilation units in .debug_aranges.
205        // Note that we always also iterate through all of .debug_info to
206        // find compilation units, because .debug_aranges may be missing some.
207        let mut aranges = Vec::new();
208        let mut headers = sections.debug_aranges.headers();
209        while let Some(header) = headers.next()? {
210            aranges.push((header.debug_info_offset(), header.offset()));
211        }
212        aranges.sort_by_key(|i| i.0);
213
214        let mut unit_ranges = Vec::new();
215        let mut res_units = Vec::new();
216        let mut units = sections.units();
217        while let Some(header) = units.next()? {
218            let unit_id = res_units.len();
219            let offset = match header.offset().as_debug_info_offset() {
220                Some(offset) => offset,
221                None => continue,
222            };
223            // We mainly want compile units, but we may need to follow references to entries
224            // within other units for function names.  We don't need anything from type units.
225            let mut need_unit_range = match header.type_() {
226                gimli::UnitType::Type { .. } | gimli::UnitType::SplitType { .. } => continue,
227                gimli::UnitType::Partial => {
228                    // Partial units are only needed for references from other units.
229                    // They shouldn't have any address ranges.
230                    false
231                }
232                _ => true,
233            };
234            let dw_unit = match sections.unit(header) {
235                Ok(dw_unit) => dw_unit,
236                Err(_) => continue,
237            };
238            let dw_unit_ref = gimli::UnitRef::new(sections, &dw_unit);
239
240            let mut lang = None;
241            if need_unit_range {
242                let mut entries = dw_unit_ref.entries_raw(None)?;
243
244                let abbrev = match entries.read_abbreviation()? {
245                    Some(abbrev) => abbrev,
246                    None => continue,
247                };
248
249                let mut ranges = RangeAttributes::default();
250                for spec in abbrev.attributes() {
251                    let attr = entries.read_attribute(*spec)?;
252                    match attr.name() {
253                        gimli::DW_AT_low_pc => match attr.value() {
254                            gimli::AttributeValue::Addr(val) => ranges.low_pc = Some(val),
255                            gimli::AttributeValue::DebugAddrIndex(index) => {
256                                ranges.low_pc = Some(dw_unit_ref.address(index)?);
257                            }
258                            _ => {}
259                        },
260                        gimli::DW_AT_high_pc => match attr.value() {
261                            gimli::AttributeValue::Addr(val) => ranges.high_pc = Some(val),
262                            gimli::AttributeValue::DebugAddrIndex(index) => {
263                                ranges.high_pc = Some(dw_unit_ref.address(index)?);
264                            }
265                            gimli::AttributeValue::Udata(val) => ranges.size = Some(val),
266                            _ => {}
267                        },
268                        gimli::DW_AT_ranges => {
269                            ranges.ranges_offset = dw_unit_ref.attr_ranges_offset(attr.value())?;
270                        }
271                        gimli::DW_AT_language => {
272                            if let gimli::AttributeValue::Language(val) = attr.value() {
273                                lang = Some(val);
274                            }
275                        }
276                        _ => {}
277                    }
278                }
279
280                // Find the address ranges for the CU, using in order of preference:
281                // - DW_AT_ranges
282                // - .debug_aranges
283                // - DW_AT_low_pc/DW_AT_high_pc
284                //
285                // Using DW_AT_ranges before .debug_aranges is possibly an arbitrary choice,
286                // but the feeling is that DW_AT_ranges is more likely to be reliable or complete
287                // if it is present.
288                //
289                // .debug_aranges must be used before DW_AT_low_pc/DW_AT_high_pc because
290                // it has been observed on macOS that DW_AT_ranges was not emitted even for
291                // discontiguous CUs.
292                let i = match ranges.ranges_offset {
293                    Some(_) => None,
294                    None => aranges.binary_search_by_key(&offset, |x| x.0).ok(),
295                };
296                if let Some(mut i) = i {
297                    // There should be only one set per CU, but in practice multiple
298                    // sets have been observed. This is probably a compiler bug, but
299                    // either way we need to handle it.
300                    while i > 0 && aranges[i - 1].0 == offset {
301                        i -= 1;
302                    }
303                    for (_, aranges_offset) in aranges[i..].iter().take_while(|x| x.0 == offset) {
304                        let aranges_header = sections.debug_aranges.header(*aranges_offset)?;
305                        let mut aranges = aranges_header.entries();
306                        while let Some(arange) = aranges.next()? {
307                            if arange.length() != 0 {
308                                unit_ranges.push(UnitRange {
309                                    range: arange.range(),
310                                    unit_id,
311                                    min_begin: 0,
312                                });
313                                need_unit_range = false;
314                            }
315                        }
316                    }
317                } else {
318                    need_unit_range &= !ranges.for_each_range(dw_unit_ref, |range| {
319                        unit_ranges.push(UnitRange {
320                            range,
321                            unit_id,
322                            min_begin: 0,
323                        });
324                    })?;
325                }
326            }
327
328            let lines = LazyLines::new();
329            if need_unit_range {
330                // The unit did not declare any ranges.
331                // Try to get some ranges from the line program sequences.
332                if let Some(ref ilnp) = dw_unit_ref.line_program {
333                    if let Ok(lines) = lines.borrow(dw_unit_ref, ilnp) {
334                        for range in lines.ranges() {
335                            unit_ranges.push(UnitRange {
336                                range,
337                                unit_id,
338                                min_begin: 0,
339                            })
340                        }
341                    }
342                }
343            }
344
345            res_units.push(ResUnit {
346                offset,
347                dw_unit,
348                lang,
349                lines,
350                functions: LazyFunctions::new(),
351                dwo: LazyResult::new(),
352            });
353        }
354
355        // Sort this for faster lookup in `Self::find_range`.
356        unit_ranges.sort_by_key(|i| i.range.end);
357
358        // Calculate the `min_begin` field now that we've determined the order of
359        // CUs.
360        let mut min = !0;
361        for i in unit_ranges.iter_mut().rev() {
362            min = min.min(i.range.begin);
363            i.min_begin = min;
364        }
365
366        Ok(ResUnits {
367            ranges: unit_ranges.into_boxed_slice(),
368            units: res_units.into_boxed_slice(),
369        })
370    }
371
372    pub(crate) fn iter(&self) -> impl Iterator<Item = &ResUnit<R>> {
373        self.units.iter()
374    }
375
376    pub(crate) fn find_offset(
377        &self,
378        offset: gimli::DebugInfoOffset<R::Offset>,
379    ) -> Result<&gimli::Unit<R>, Error> {
380        match self
381            .units
382            .binary_search_by_key(&offset.0, |unit| unit.offset.0)
383        {
384            // There is never a DIE at the unit offset or before the first unit.
385            Ok(_) | Err(0) => Err(gimli::Error::NoEntryAtGivenOffset),
386            Err(i) => Ok(&self.units[i - 1].dw_unit),
387        }
388    }
389
390    /// Finds the CUs for the function address given.
391    ///
392    /// There might be multiple CUs whose range contains this address.
393    /// Weak symbols have shown up in the wild which cause this to happen
394    /// but otherwise this can happen if the CU has non-contiguous functions
395    /// but only reports a single range.
396    ///
397    /// Consequently we return an iterator for all CUs which may contain the
398    /// address, and the caller must check if there is actually a function or
399    /// location in the CU for that address.
400    pub(crate) fn find(&self, probe: u64) -> impl Iterator<Item = &ResUnit<R>> {
401        self.find_range(probe, probe + 1).map(|(unit, _range)| unit)
402    }
403
404    /// Finds the CUs covering the range of addresses given.
405    ///
406    /// The range is [low, high) (ie, the upper bound is exclusive). This can return multiple
407    /// ranges for the same unit.
408    #[inline]
409    pub(crate) fn find_range(
410        &self,
411        probe_low: u64,
412        probe_high: u64,
413    ) -> impl Iterator<Item = (&ResUnit<R>, &gimli::Range)> {
414        // Find the position of the next range after a range which
415        // ends at `probe_low` or lower.
416        let pos = match self
417            .ranges
418            .binary_search_by_key(&probe_low, |i| i.range.end)
419        {
420            Ok(i) => i + 1, // Range `i` ends at exactly `probe_low`.
421            Err(i) => i,    // Range `i - 1` ends at a lower address.
422        };
423
424        // Iterate from that position to find matching CUs.
425        self.ranges[pos..]
426            .iter()
427            .take_while(move |i| {
428                // We know that this CU's end is at least `probe_low` because
429                // of our sorted array.
430                debug_assert!(i.range.end >= probe_low);
431
432                // Each entry keeps track of the minimum begin address for the
433                // remainder of the array of unit ranges. If our probe is before
434                // the minimum range begin of this entry, then it's guaranteed
435                // to not fit in any subsequent entries, so we break out.
436                probe_high > i.min_begin
437            })
438            .filter_map(move |i| {
439                // If this CU doesn't actually contain this address, move to the
440                // next CU.
441                if probe_low >= i.range.end || probe_high <= i.range.begin {
442                    return None;
443                }
444                Some((&self.units[i.unit_id], &i.range))
445            })
446    }
447
448    pub(crate) fn find_location_range<'a>(
449        &'a self,
450        probe_low: u64,
451        probe_high: u64,
452        sections: &'a gimli::Dwarf<R>,
453    ) -> Result<LocationRangeIter<'a, R>, Error> {
454        let unit_iter = Box::new(self.find_range(probe_low, probe_high));
455        Ok(LocationRangeIter {
456            unit_iter,
457            iter: None,
458            probe_low,
459            probe_high,
460            sections,
461        })
462    }
463}
464
465/// A DWO unit has its own DWARF sections.
466struct DwoUnit<R: gimli::Reader> {
467    sections: Arc<gimli::Dwarf<R>>,
468    dw_unit: gimli::Unit<R>,
469}
470
471impl<R: gimli::Reader> DwoUnit<R> {
472    fn unit_ref(&self) -> gimli::UnitRef<R> {
473        gimli::UnitRef::new(&self.sections, &self.dw_unit)
474    }
475}
476
477pub(crate) struct SupUnit<R: gimli::Reader> {
478    offset: gimli::DebugInfoOffset<R::Offset>,
479    dw_unit: gimli::Unit<R>,
480}
481
482pub(crate) struct SupUnits<R: gimli::Reader> {
483    units: Box<[SupUnit<R>]>,
484}
485
486impl<R: gimli::Reader> Default for SupUnits<R> {
487    fn default() -> Self {
488        SupUnits {
489            units: Box::default(),
490        }
491    }
492}
493
494impl<R: gimli::Reader> SupUnits<R> {
495    pub(crate) fn parse(sections: &gimli::Dwarf<R>) -> Result<Self, Error> {
496        let mut sup_units = Vec::new();
497        let mut units = sections.units();
498        while let Some(header) = units.next()? {
499            let offset = match header.offset().as_debug_info_offset() {
500                Some(offset) => offset,
501                None => continue,
502            };
503            let dw_unit = match sections.unit(header) {
504                Ok(dw_unit) => dw_unit,
505                Err(_) => continue,
506            };
507            sup_units.push(SupUnit { dw_unit, offset });
508        }
509        Ok(SupUnits {
510            units: sup_units.into_boxed_slice(),
511        })
512    }
513
514    pub(crate) fn find_offset(
515        &self,
516        offset: gimli::DebugInfoOffset<R::Offset>,
517    ) -> Result<&gimli::Unit<R>, Error> {
518        match self
519            .units
520            .binary_search_by_key(&offset.0, |unit| unit.offset.0)
521        {
522            // There is never a DIE at the unit offset or before the first unit.
523            Ok(_) | Err(0) => Err(gimli::Error::NoEntryAtGivenOffset),
524            Err(i) => Ok(&self.units[i - 1].dw_unit),
525        }
526    }
527}
528
529/// Iterator over `Location`s in a range of addresses, returned by `Context::find_location_range`.
530pub struct LocationRangeIter<'ctx, R: gimli::Reader> {
531    unit_iter: Box<dyn Iterator<Item = (&'ctx ResUnit<R>, &'ctx gimli::Range)> + 'ctx>,
532    iter: Option<LineLocationRangeIter<'ctx>>,
533
534    probe_low: u64,
535    probe_high: u64,
536    sections: &'ctx gimli::Dwarf<R>,
537}
538
539impl<'ctx, R: gimli::Reader> LocationRangeIter<'ctx, R> {
540    fn next_loc(&mut self) -> Result<Option<(u64, u64, Location<'ctx>)>, Error> {
541        loop {
542            let iter = self.iter.take();
543            match iter {
544                None => match self.unit_iter.next() {
545                    Some((unit, range)) => {
546                        self.iter = unit.find_location_range(
547                            cmp::max(self.probe_low, range.begin),
548                            cmp::min(self.probe_high, range.end),
549                            self.sections,
550                        )?;
551                    }
552                    None => return Ok(None),
553                },
554                Some(mut iter) => {
555                    if let item @ Some(_) = iter.next() {
556                        self.iter = Some(iter);
557                        return Ok(item);
558                    }
559                }
560            }
561        }
562    }
563}
564
565impl<'ctx, R> Iterator for LocationRangeIter<'ctx, R>
566where
567    R: gimli::Reader + 'ctx,
568{
569    type Item = (u64, u64, Location<'ctx>);
570
571    #[inline]
572    fn next(&mut self) -> Option<Self::Item> {
573        self.next_loc().unwrap_or_default()
574    }
575}
576
577#[cfg(feature = "fallible-iterator")]
578impl<'ctx, R> fallible_iterator::FallibleIterator for LocationRangeIter<'ctx, R>
579where
580    R: gimli::Reader + 'ctx,
581{
582    type Item = (u64, u64, Location<'ctx>);
583    type Error = Error;
584
585    #[inline]
586    fn next(&mut self) -> Result<Option<Self::Item>, Self::Error> {
587        self.next_loc()
588    }
589}