addr2line/
frame.rs

1use alloc::borrow::Cow;
2use alloc::string::String;
3use core::iter;
4
5use crate::{maybe_small, Error, Function, InlinedFunction, ResUnit};
6
7/// A source location.
8pub struct Location<'a> {
9    /// The file name.
10    pub file: Option<&'a str>,
11    /// The line number.
12    pub line: Option<u32>,
13    /// The column number.
14    ///
15    /// A value of `Some(0)` indicates the left edge.
16    pub column: Option<u32>,
17}
18
19/// A function frame.
20pub struct Frame<'ctx, R: gimli::Reader> {
21    /// The DWARF unit offset corresponding to the DIE of the function.
22    pub dw_die_offset: Option<gimli::UnitOffset<R::Offset>>,
23    /// The name of the function.
24    pub function: Option<FunctionName<R>>,
25    /// The source location corresponding to this frame.
26    pub location: Option<Location<'ctx>>,
27}
28
29/// An iterator over function frames.
30pub struct FrameIter<'ctx, R>(FrameIterState<'ctx, R>)
31where
32    R: gimli::Reader;
33
34enum FrameIterState<'ctx, R>
35where
36    R: gimli::Reader,
37{
38    Empty,
39    Location(Option<Location<'ctx>>),
40    Frames(FrameIterFrames<'ctx, R>),
41}
42
43struct FrameIterFrames<'ctx, R>
44where
45    R: gimli::Reader,
46{
47    unit: &'ctx ResUnit<R>,
48    sections: &'ctx gimli::Dwarf<R>,
49    function: &'ctx Function<R>,
50    inlined_functions: iter::Rev<maybe_small::IntoIter<&'ctx InlinedFunction<R>>>,
51    next: Option<Location<'ctx>>,
52}
53
54impl<'ctx, R> FrameIter<'ctx, R>
55where
56    R: gimli::Reader + 'ctx,
57{
58    pub(crate) fn new_empty() -> Self {
59        FrameIter(FrameIterState::Empty)
60    }
61
62    pub(crate) fn new_location(location: Location<'ctx>) -> Self {
63        FrameIter(FrameIterState::Location(Some(location)))
64    }
65
66    pub(crate) fn new_frames(
67        unit: &'ctx ResUnit<R>,
68        sections: &'ctx gimli::Dwarf<R>,
69        function: &'ctx Function<R>,
70        inlined_functions: maybe_small::Vec<&'ctx InlinedFunction<R>>,
71        location: Option<Location<'ctx>>,
72    ) -> Self {
73        FrameIter(FrameIterState::Frames(FrameIterFrames {
74            unit,
75            sections,
76            function,
77            inlined_functions: inlined_functions.into_iter().rev(),
78            next: location,
79        }))
80    }
81
82    /// Advances the iterator and returns the next frame.
83    #[allow(clippy::should_implement_trait)]
84    pub fn next(&mut self) -> Result<Option<Frame<'ctx, R>>, Error> {
85        let frames = match &mut self.0 {
86            FrameIterState::Empty => return Ok(None),
87            FrameIterState::Location(location) => {
88                // We can't move out of a mutable reference, so use `take` instead.
89                let location = location.take();
90                self.0 = FrameIterState::Empty;
91                return Ok(Some(Frame {
92                    dw_die_offset: None,
93                    function: None,
94                    location,
95                }));
96            }
97            FrameIterState::Frames(frames) => frames,
98        };
99
100        let loc = frames.next.take();
101        let func = match frames.inlined_functions.next() {
102            Some(func) => func,
103            None => {
104                let frame = Frame {
105                    dw_die_offset: Some(frames.function.dw_die_offset),
106                    function: frames.function.name.clone().map(|name| FunctionName {
107                        name,
108                        language: frames.unit.lang,
109                    }),
110                    location: loc,
111                };
112                self.0 = FrameIterState::Empty;
113                return Ok(Some(frame));
114            }
115        };
116
117        let mut next = Location {
118            file: None,
119            line: if func.call_line != 0 {
120                Some(func.call_line)
121            } else {
122                None
123            },
124            column: if func.call_column != 0 {
125                Some(func.call_column)
126            } else {
127                None
128            },
129        };
130        if let Some(call_file) = func.call_file {
131            if let Some(lines) = frames.unit.parse_lines(frames.sections)? {
132                next.file = lines.file(call_file);
133            }
134        }
135        frames.next = Some(next);
136
137        Ok(Some(Frame {
138            dw_die_offset: Some(func.dw_die_offset),
139            function: func.name.clone().map(|name| FunctionName {
140                name,
141                language: frames.unit.lang,
142            }),
143            location: loc,
144        }))
145    }
146}
147
148#[cfg(feature = "fallible-iterator")]
149impl<'ctx, R> fallible_iterator::FallibleIterator for FrameIter<'ctx, R>
150where
151    R: gimli::Reader + 'ctx,
152{
153    type Item = Frame<'ctx, R>;
154    type Error = Error;
155
156    #[inline]
157    fn next(&mut self) -> Result<Option<Frame<'ctx, R>>, Error> {
158        self.next()
159    }
160}
161
162/// A function name.
163pub struct FunctionName<R: gimli::Reader> {
164    /// The name of the function.
165    pub name: R,
166    /// The language of the compilation unit containing this function.
167    pub language: Option<gimli::DwLang>,
168}
169
170impl<R: gimli::Reader> FunctionName<R> {
171    /// The raw name of this function before demangling.
172    pub fn raw_name(&self) -> Result<Cow<'_, str>, Error> {
173        self.name.to_string_lossy()
174    }
175
176    /// The name of this function after demangling (if applicable).
177    pub fn demangle(&self) -> Result<Cow<'_, str>, Error> {
178        self.raw_name().map(|x| demangle_auto(x, self.language))
179    }
180}
181
182/// Demangle a symbol name using the demangling scheme for the given language.
183///
184/// Returns `None` if demangling failed or is not required.
185#[allow(unused_variables)]
186pub fn demangle(name: &str, language: gimli::DwLang) -> Option<String> {
187    match language {
188        #[cfg(feature = "rustc-demangle")]
189        gimli::DW_LANG_Rust => rustc_demangle::try_demangle(name)
190            .ok()
191            .as_ref()
192            .map(|x| format!("{:#}", x)),
193        #[cfg(feature = "cpp_demangle")]
194        gimli::DW_LANG_C_plus_plus
195        | gimli::DW_LANG_C_plus_plus_03
196        | gimli::DW_LANG_C_plus_plus_11
197        | gimli::DW_LANG_C_plus_plus_14 => cpp_demangle::Symbol::new(name)
198            .ok()
199            .and_then(|x| x.demangle(&Default::default()).ok()),
200        _ => None,
201    }
202}
203
204/// Apply 'best effort' demangling of a symbol name.
205///
206/// If `language` is given, then only the demangling scheme for that language
207/// is used.
208///
209/// If `language` is `None`, then heuristics are used to determine how to
210/// demangle the name. Currently, these heuristics are very basic.
211///
212/// If demangling fails or is not required, then `name` is returned unchanged.
213pub fn demangle_auto(name: Cow<'_, str>, language: Option<gimli::DwLang>) -> Cow<'_, str> {
214    match language {
215        Some(language) => demangle(name.as_ref(), language),
216        None => demangle(name.as_ref(), gimli::DW_LANG_Rust)
217            .or_else(|| demangle(name.as_ref(), gimli::DW_LANG_C_plus_plus)),
218    }
219    .map(Cow::from)
220    .unwrap_or(name)
221}