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}