Skip to main content

probe_rs_debug/
function_die.rs

1use gimli::{Dwarf, UnitOffset};
2use std::ops::Range;
3
4use crate::{GimliReader, MemoryInterface, stack_frame::StackFrameInfo};
5
6use super::{
7    ColumnType, DebugError, DebugInfo, SourceLocation, VariableLocation, debug_info, extract_file,
8    unit_info::{ExpressionResult, UnitInfo},
9};
10
11pub(crate) type Die = gimli::DebuggingInformationEntry<debug_info::GimliReader, usize>;
12
13/// Reference to a DIE for a function
14#[derive(Clone)]
15pub(crate) struct FunctionDie<'data> {
16    /// A reference to the compilation unit this function belongs to.
17    pub(crate) unit_info: &'data UnitInfo,
18    /// The DIE (Debugging Information Entry) for the function.
19    pub(crate) function_die: Die,
20    /// The optional specification DIE for the function, if it has one, paired with the
21    /// compilation unit it belongs to.
22    /// - For regular functions, this applies to the `function_die`.
23    /// - For inlined functions, this applies to the `abstract_die`.
24    ///
25    /// The specification DIE will contain separately declared attributes,
26    /// e.g. for the function name.
27    /// See DWARF spec, 2.13.2.
28    ///
29    /// The unit can differ from `unit_info`, because the abstract origin of an inlined
30    /// function may live in another compilation unit (cross-unit `DW_FORM_ref_addr`).
31    pub(crate) specification_die: Option<(&'data UnitInfo, Die)>,
32    /// Only present for inlined functions, where this is a reference
33    /// to the declaration of the function, paired with the compilation unit it belongs to.
34    ///
35    /// The unit can differ from `unit_info` (cross-unit `DW_FORM_ref_addr`).
36    pub(crate) abstract_die: Option<(&'data UnitInfo, Die)>,
37    /// The address ranges for which this function is valid.
38    pub(crate) ranges: Vec<Range<u64>>,
39}
40
41impl<'a> FunctionDie<'a> {
42    pub(crate) fn function_ranges(
43        function_die: &Die,
44        unit_info: &UnitInfo,
45        dwarf: &Dwarf<GimliReader>,
46    ) -> Result<Option<Vec<Range<u64>>>, DebugError> {
47        let (gimli::DW_TAG_subprogram | gimli::DW_TAG_inlined_subroutine) = function_die.tag()
48        else {
49            // We only need DIEs for functions, so we can ignore all other DIEs.
50            return Ok(None);
51        };
52
53        // Validate the function DIE ranges, and confirm this DIE applies to the requested address.
54        let mut gimli_ranges = dwarf.die_ranges(&unit_info.unit, function_die)?;
55        let mut die_ranges = Vec::new();
56        while let Ok(Some(gimli_range)) = gimli_ranges.next() {
57            if gimli_range.begin == 0 {
58                // TODO: The DW_AT_subprograms with low_pc == 0 cause overlapping ranges with other 'valid' function dies, and obscures the correct function die.
59                // We need to understand what those mean, and how to handle them correctly.
60                return Ok(None);
61            }
62            die_ranges.push(gimli_range.begin..gimli_range.end);
63        }
64
65        Ok(Some(die_ranges))
66    }
67
68    /// Create a new function DIE reference.
69    /// We only return DIE's that are functions, with valid address ranges that represent machine code
70    /// relevant to the address/program counter specified.
71    /// Other DIE's will return None, and should be ignored.
72    pub(crate) fn new(
73        function_die: Die,
74        unit_info: &'a UnitInfo,
75        debug_info: &'a DebugInfo,
76        address: u64,
77    ) -> Result<Option<Self>, DebugError> {
78        let is_inlined_function = match function_die.tag() {
79            gimli::DW_TAG_subprogram => false,
80            gimli::DW_TAG_inlined_subroutine => true,
81            _ => {
82                // We only need DIEs for functions, so we can ignore all other DIEs.
83                return Ok(None);
84            }
85        };
86
87        let Some(die_ranges) = Self::function_ranges(&function_die, unit_info, &debug_info.dwarf)?
88        else {
89            return Ok(None);
90        };
91        if !die_ranges.iter().any(|range| range.contains(&address)) {
92            return Ok(None);
93        }
94
95        let specification_die;
96
97        // For inlined functions, we also need to find the abstract origin.
98        let abstract_die = if is_inlined_function {
99            let Some((abstract_unit, abstract_die)) = debug_info
100                .resolve_die_reference_with_unit_info(
101                    gimli::DW_AT_abstract_origin,
102                    &function_die,
103                    unit_info,
104                )
105            else {
106                tracing::debug!("No abstract origin found for inlined function");
107                return Ok(None);
108            };
109            // The abstract origin may reside in a different compilation unit, referenced via a
110            // cross-unit `DW_FORM_ref_addr`. Its `DW_AT_specification`, however, is a
111            // *unit-relative* reference, so it must be resolved against the abstract origin's
112            // own unit. Resolving it against the concrete unit (`unit_info`) lands on an
113            // unrelated DIE and yields garbage attributes (e.g. nonsensical inline call_line).
114            specification_die = debug_info.resolve_die_reference_with_unit_info(
115                gimli::DW_AT_specification,
116                &abstract_die,
117                abstract_unit,
118            );
119            Some((abstract_unit, abstract_die))
120        } else {
121            specification_die = debug_info.resolve_die_reference_with_unit_info(
122                gimli::DW_AT_specification,
123                &function_die,
124                unit_info,
125            );
126            None
127        };
128
129        Ok(Some(Self {
130            unit_info,
131            function_die,
132            specification_die,
133            abstract_die,
134            ranges: die_ranges,
135        }))
136    }
137
138    /// Test whether the given address is contained in the address ranges of this function.
139    /// Use this, instead of checking for values between `low_pc()` and `high_pc()`, because
140    /// the address ranges can be disjointed.
141    pub(crate) fn range_contains(&self, address: u64) -> bool {
142        self.ranges.iter().any(|range| range.contains(&address))
143    }
144
145    /// Returns the lowest valid address for which this function DIE is valid.
146    /// Please use `range_contains()` to check whether an address is contained in the range.
147    pub(crate) fn low_pc(&self) -> Option<u64> {
148        self.ranges.first().map(|range| range.start)
149    }
150
151    /// Returns the highest valid address for which this function DIE is valid.
152    /// Please use `range_contains()` to check whether an address is contained in the range.
153    pub(crate) fn high_pc(&self) -> Option<u64> {
154        self.ranges.last().map(|range| range.end)
155    }
156
157    /// Returns whether this is an inlined function DIE reference.
158    pub(crate) fn is_inline(&self) -> bool {
159        self.abstract_die.is_some()
160    }
161
162    /// Returns the function name described by the die.
163    pub(crate) fn function_name(&self, debug_info: &super::DebugInfo) -> Option<String> {
164        let Some(fn_name_attr) = self.attribute(debug_info, gimli::DW_AT_name) else {
165            tracing::debug!("DW_AT_name attribute not found, unable to retrieve function name");
166            return None;
167        };
168        let value = fn_name_attr.value();
169        let gimli::AttributeValue::DebugStrRef(fn_name_ref) = value else {
170            tracing::debug!("Unexpected attribute value for DW_AT_name: {:?}", value);
171            return None;
172        };
173        match debug_info.dwarf.string(fn_name_ref) {
174            Ok(fn_name_raw) => {
175                let function_name = String::from_utf8_lossy(&fn_name_raw);
176
177                let language = crate::language::from_dwarf(self.unit_info.get_language());
178                Some(language.format_function_name(function_name.as_ref(), self, debug_info))
179            }
180            Err(error) => {
181                tracing::debug!("No value for DW_AT_name: {:?}: error", error);
182
183                None
184            }
185        }
186    }
187
188    /// Get the call site of an inlined function.
189    ///
190    /// If this function is not inlined (`is_inline()` returns false),
191    /// this function returns `None`.
192    pub(crate) fn inline_call_location(
193        &self,
194        debug_info: &super::DebugInfo,
195    ) -> Option<SourceLocation> {
196        if !self.is_inline() {
197            return None;
198        }
199
200        let file_name_attr = self.attribute(debug_info, gimli::DW_AT_call_file)?;
201
202        let path = extract_file(debug_info, &self.unit_info.unit, file_name_attr.value())?;
203        let line = self
204            .attribute(debug_info, gimli::DW_AT_call_line)
205            .and_then(|line| line.udata_value());
206
207        let column =
208            self.attribute(debug_info, gimli::DW_AT_call_column)
209                .map(|column| match column.udata_value() {
210                    None => ColumnType::LeftEdge,
211                    Some(c) => ColumnType::Column(c),
212                });
213
214        let address = self.low_pc();
215
216        Some(SourceLocation {
217            line,
218            column,
219            path,
220            address,
221        })
222    }
223
224    /// Resolve an attribute by looking through both the specification and die, or abstract specification and die, entries.
225    pub(crate) fn attribute(
226        &self,
227        debug_info: &super::DebugInfo,
228        attribute_name: gimli::DwAt,
229    ) -> Option<debug_info::GimliAttribute> {
230        let attribute = collapsed_attribute(
231            &self.function_die,
232            self.specification_die.as_ref().map(|(_, die)| die),
233            attribute_name,
234        );
235
236        if attribute.is_some() {
237            return attribute.cloned();
238        }
239
240        // For inlined function, the *abstract instance* has to be checked if we cannot find the
241        // attribute on the *concrete instance*. The abstract instance my also be a reference to a specification.
242        if let Some((abstract_unit, abstract_die)) = &self.abstract_die {
243            let inlined_specification_die = debug_info.resolve_die_reference(
244                gimli::DW_AT_specification,
245                abstract_die,
246                abstract_unit,
247            );
248            let inline_attribute = collapsed_attribute(
249                abstract_die,
250                inlined_specification_die.as_ref(),
251                attribute_name,
252            );
253
254            if inline_attribute.is_some() {
255                return inline_attribute.cloned();
256            }
257        }
258
259        None
260    }
261
262    /// Try to retrieve the frame base for this function
263    pub fn frame_base(
264        &self,
265        debug_info: &super::DebugInfo,
266        memory: &mut dyn MemoryInterface,
267        frame_info: StackFrameInfo,
268    ) -> Result<Option<u64>, DebugError> {
269        match self.unit_info.extract_location(
270            debug_info,
271            &self.function_die,
272            &VariableLocation::Unknown,
273            memory,
274            frame_info,
275        )? {
276            ExpressionResult::Location(VariableLocation::Address(address)) => Ok(Some(address)),
277            ExpressionResult::Location(VariableLocation::RegisterValue(value)) => {
278                Ok(value.try_into().ok())
279            }
280            _ => Ok(None),
281        }
282    }
283
284    /// Returns the parent DIE offset of the function's declaration, together with the unit
285    /// that offset is relative to.
286    ///
287    /// The declaration is the specification DIE if present (which may live in a different
288    /// compilation unit than `unit_info`), otherwise the concrete function DIE.
289    pub(crate) fn parent_offset(&self) -> Option<(&'a UnitInfo, UnitOffset)> {
290        let (unit, offset) = match &self.specification_die {
291            Some((unit, die)) => (*unit, die.offset()),
292            None => (self.unit_info, self.function_die.offset()),
293        };
294        unit.parent_offset(offset).map(|parent| (unit, parent))
295    }
296}
297
298// Try to retrieve the attribute from the specification or the function DIE.
299fn collapsed_attribute<'a>(
300    function_die: &'a Die,
301    specification_die: Option<&'a Die>,
302    attribute_name: gimli::DwAt,
303) -> Option<&'a debug_info::GimliAttribute> {
304    specification_die
305        .as_ref()
306        .and_then(|specification_die| specification_die.attr(attribute_name))
307        .or_else(|| function_die.attr(attribute_name))
308}