rudy_dwarf/function/
mod.rs

1//! Function resolution and metadata extraction
2
3mod index;
4mod variables;
5
6use anyhow::Context as _;
7pub use index::{function_index, FunctionData, FunctionIndex, FunctionIndexEntry};
8use itertools::Itertools;
9use rudy_types::{Layout, PrimitiveLayout, ReferenceLayout};
10pub use variables::{resolve_function_variables, Variable};
11
12use crate::{
13    die::{
14        utils::{get_string_attr, pretty_print_die_entry},
15        UnitRef,
16    },
17    file::RawDie,
18    function::variables::variable,
19    parser::{
20        children::{for_each_child, try_for_each_child},
21        combinators::all,
22        primitives::{attr, is_member_tag, optional_attr, resolve_type_shallow},
23        Parser,
24    },
25    types::{DieLayout, DieTypeDefinition},
26    Die, DwarfDb,
27};
28
29type Result<T> = std::result::Result<T, super::Error>;
30
31pub enum FunctionDeclarationType {
32    Closure,
33    ClassMethodDeclaration,
34    /// Class methods are declared in the class, but implemented elsewhere
35    ClassMethodImplementation,
36    Function {
37        #[allow(dead_code)]
38        inlined: bool,
39    },
40    InlinedFunctionImplementation,
41}
42
43/// Infer what kind of declaration this DIE represents
44///
45/// Some examples:
46///
47/// Closure:
48///
49/// 0x000000c8:         DW_TAG_subprogram
50///                      DW_AT_low_pc      (0x0000000000000158)
51///                      DW_AT_high_pc     (0x000000000000018c)
52///                      DW_AT_frame_base  (DW_OP_reg29 W29)
53///                      DW_AT_linkage_name        ("_ZN3std2rt10lang_start28_$u7b$$u7b$closure$u7d$$u7d$17hfe953699872e52f8E")
54///                      DW_AT_name        ("{closure#0}<()>")
55///                      DW_AT_decl_file   ("/Users/sam/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/std/src/rt.rs")
56///                      DW_AT_decl_line   (199)
57///                      DW_AT_type        (0x00004dda "i32")
58///
59/// Function with generics:
60///
61/// 0x00000132:       DW_TAG_subprogram
62///                    DW_AT_low_pc        (0x00000000000000f8)
63///                    DW_AT_high_pc       (0x0000000000000158)
64///                    DW_AT_frame_base    (DW_OP_reg29 W29)
65///                    DW_AT_linkage_name  ("_ZN3std2rt10lang_start17h3ee7518cb9a82119E")
66///                    DW_AT_name  ("lang_start<()>")
67///                    DW_AT_decl_file     ("/Users/sam/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/std/src/rt.rs")
68///                    DW_AT_decl_line     (192)
69///                    DW_AT_type  (0x00009a1c "isize")
70///
71///
72/// Class method:
73///
74/// 0x000001bc:           DW_TAG_subprogram
75///                        DW_AT_linkage_name      ("_ZN3std4hash6random11RandomState3new17he3583681eab89a20E")
76///                        DW_AT_name      ("new")
77///                        DW_AT_decl_file ("/Users/sam/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/std/src/hash/random.rs")
78///                        DW_AT_decl_line (56)
79///                        DW_AT_type      (0x0000019c "std::hash::random::RandomState")
80///                        DW_AT_declaration       (true)
81///
82/// Implementation of a trait method (with #[inline] annotation:
83///
84/// 0x000001d1:           DW_TAG_subprogram
85///                        DW_AT_linkage_name      ("_ZN73_$LT$std..hash..random..RandomState$u20$as$u20$core..default..Default$GT$7default17h8f7526c79c40ea4cE")
86///                        DW_AT_name      ("default")
87///                        DW_AT_decl_file ("/Users/sam/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/std/src/hash/random.rs")
88///                        DW_AT_decl_line (151)
89///                        DW_AT_type      (0x0000019c "std::hash::random::RandomState")
90///                        DW_AT_inline    (DW_INL_inlined)
91///
92/// Method that was inlined into another function:
93///
94/// NOTE: this is not a subprogram. This would typically appear _inside_
95/// a subprogram DIE and is used to track the function that was inlined here.
96///
97/// 0x0000027e:                 DW_TAG_inlined_subroutine
98///                              DW_AT_abstract_origin     (0x000064ed "_ZN4core4cell13Cell$LT$T$GT$7replace17hcbb859b11ab45ce0E")
99///                              DW_AT_low_pc      (0x00000000000004cc)
100///                              DW_AT_high_pc     (0x00000000000004d4)
101///                              DW_AT_call_file   ("/Users/sam/.rustup/toolchains/stable-aarch64-apple-darwin/lib/rustlib/src/rust/library/core/src/cell.rs")
102///                              DW_AT_call_line   (429)
103///                              DW_AT_call_column (14)
104///
105/// Specification of a class method:
106///
107/// 0x0000923c:   DW_TAG_subprogram
108///                DW_AT_low_pc    (0x0000000000002cd8)
109///                DW_AT_high_pc   (0x0000000000002df4)
110///                DW_AT_frame_base        (DW_OP_reg29 W29)
111///                DW_AT_specification     (0x000058e0 "_ZN5small11TestStruct08method_017h636bec720e368708E")
112#[allow(dead_code)]
113pub fn get_declaration_type(
114    _db: &dyn DwarfDb,
115    die: &RawDie,
116    unit_ref: &UnitRef,
117) -> FunctionDeclarationType {
118    if die.attr(gimli::DW_AT_declaration).ok().flatten().is_some() {
119        return FunctionDeclarationType::ClassMethodDeclaration;
120    }
121    if let Some(gimli::AttributeValue::UnitRef(_)) = die
122        .attr(gimli::DW_AT_specification)
123        .ok()
124        .flatten()
125        .map(|v| v.value())
126    {
127        return FunctionDeclarationType::ClassMethodImplementation;
128    }
129    if let Some(gimli::AttributeValue::DebugInfoRef(_)) = die
130        .attr(gimli::DW_AT_specification)
131        .ok()
132        .flatten()
133        .map(|v| v.value())
134    {
135        return FunctionDeclarationType::ClassMethodImplementation;
136    }
137
138    if let Some(gimli::AttributeValue::UnitRef(_)) = die
139        .attr(gimli::DW_AT_abstract_origin)
140        .ok()
141        .flatten()
142        .map(|v| v.value())
143    {
144        return FunctionDeclarationType::InlinedFunctionImplementation;
145    }
146
147    if let Ok(Some(name)) = get_string_attr(die, gimli::DW_AT_name, unit_ref) {
148        if name.starts_with("{closure#") {
149            return FunctionDeclarationType::Closure;
150        }
151    } else {
152        tracing::error!(
153            "No name attribute for function at {:#010x}. What is this? {}",
154            unit_ref.header.offset().as_debug_info_offset().unwrap().0 + die.offset().0,
155            pretty_print_die_entry(die, unit_ref)
156        );
157    }
158
159    let inlined = matches!(
160        die.attr(gimli::DW_AT_inline)
161            .ok()
162            .flatten()
163            .map(|v| v.value()),
164        Some(gimli::AttributeValue::Inline(gimli::DW_INL_inlined)),
165    );
166
167    FunctionDeclarationType::Function { inlined }
168}
169
170#[derive(Debug, Clone, PartialEq, Eq, Hash, salsa::Update)]
171pub struct FunctionSignature {
172    /// Short name of the function, e.g. "len" for "std::vec::Vec<T>::len"
173    pub name: String,
174
175    /// Params for the function, e.g. "self: &mut Self, index: usize"
176    pub params: Vec<Variable>,
177
178    /// Return type of the function, e.g. "usize" for "std::vec::Vec<T>::len"
179    pub return_type: Option<DieTypeDefinition>,
180
181    /// Somewhat duplicative with the `params` field, this
182    /// determines (a) if we have a self parameter, and (b) what kind of self parameter it is.
183    pub self_type: Option<SelfType>,
184    /// `callable` indicates that we expect this function to be callable.
185    /// This is true if the function has a symbol in the binary.
186    /// If false, it means this is a synthetic method or a function that cannot be called
187    /// (e.g., a trait method without an implementation).
188    pub callable: bool,
189    /// e.g. /path/to/debug_info.rgcu.o 0x12345
190    ///
191    /// Mostly useful for debugging
192    pub debug_location: String,
193}
194
195impl FunctionSignature {
196    pub fn new(
197        name: String,
198        params: Vec<Variable>,
199        return_type: Option<DieTypeDefinition>,
200        self_type: Option<SelfType>,
201        callable: bool,
202        debug_location: String,
203    ) -> Self {
204        Self {
205            name,
206            params,
207            return_type,
208            self_type,
209            callable,
210            debug_location,
211        }
212    }
213}
214
215impl FunctionSignature {
216    pub fn print_sig(&self) -> String {
217        let params = self
218            .params
219            .iter()
220            .map(|p| {
221                format!(
222                    "{}: {}",
223                    p.name.as_deref().unwrap_or("_"),
224                    p.ty.display_name()
225                )
226            })
227            .join(", ");
228        if let Some(ret) = &self.return_type {
229            format!("fn({params}) -> {}", ret.display_name())
230        } else {
231            format!("fn({params})")
232        }
233    }
234}
235
236/// Parser to extract function parameters
237fn function_parameter() -> impl Parser<Variable> {
238    is_member_tag(gimli::DW_TAG_formal_parameter).then(variable())
239}
240
241/// Parser to return function declaration information
242fn function_declaration() -> impl Parser<(String, Option<DieTypeDefinition>, Vec<Variable>)> {
243    all((
244        attr::<String>(gimli::DW_AT_name),
245        optional_attr::<Die>(gimli::DW_AT_type).then(resolve_type_shallow()),
246        // If the child is a formal parameter, then attempt to parse it as a variable
247        try_for_each_child(
248            is_member_tag(gimli::DW_TAG_formal_parameter)
249                .filter()
250                .then(function_parameter()),
251        ),
252    ))
253}
254
255/// Parser to extract function specification information
256fn function_specification() -> impl Parser<Vec<Variable>> {
257    for_each_child(function_parameter())
258}
259
260/// Analyze a function to see if it's a method for the target type
261#[salsa::tracked]
262pub fn resolve_function_signature<'db>(
263    db: &'db dyn DwarfDb,
264    function_index_entry: FunctionIndexEntry<'db>,
265) -> Result<FunctionSignature> {
266    let FunctionData {
267        declaration_die,
268        specification_die,
269        alternate_locations,
270        ..
271    } = function_index_entry.data(db);
272
273    let (name, return_type, parameters) = function_declaration()
274        .parse(db, *declaration_die)
275        .context("parsing function declaration")?;
276
277    let parameters = if let Some(specification_die) = specification_die {
278        // If no parameters in declaration, try to get them from specification
279        function_specification()
280            .parse(db, *specification_die)
281            .context("parsing function specification")?
282    } else {
283        parameters
284    };
285
286    let self_type = if let Some(first_param) = parameters.first() {
287        // If the first parameter is self-like, determine its type
288        let first_param_name = &first_param.name;
289        if matches!(
290            first_param_name.as_deref(),
291            Some("self" | "&self" | "&mut self")
292        ) {
293            Some(SelfType::from_param_type(first_param.ty.layout.as_ref()))
294        } else {
295            None
296        }
297    } else {
298        None
299    };
300
301    let mut debug_location = format!("Declaration: {}", declaration_die.location(db));
302    if let Some(spec) = specification_die {
303        debug_location.push_str(&format!("\nSpecification: {}", spec.location(db)));
304    }
305    for location in alternate_locations.iter() {
306        debug_location.push_str(&format!("\nAlternate: {}", location.location(db)));
307    }
308
309    Ok(FunctionSignature::new(
310        name,
311        parameters,
312        return_type,
313        self_type,
314        true, // callable by default
315        debug_location,
316    ))
317}
318
319/// Type of self parameter in a method
320#[derive(Debug, Clone, Copy, serde::Serialize, PartialEq, Eq, Hash, PartialOrd, Ord)]
321pub enum SelfType {
322    /// `self` - takes ownership
323    Owned,
324    /// `&self` - immutable reference
325    Borrowed,
326    /// `&mut self` - mutable reference
327    BorrowedMut,
328}
329
330impl SelfType {
331    pub fn from_param_type(param_type: &DieLayout) -> Self {
332        match param_type {
333            Layout::Primitive(PrimitiveLayout::Reference(ReferenceLayout {
334                mutable: true,
335                ..
336            })) => Self::BorrowedMut,
337            Layout::Primitive(PrimitiveLayout::Reference(ReferenceLayout {
338                mutable: false,
339                ..
340            })) => Self::Borrowed,
341            _ => Self::Owned,
342        }
343    }
344}