1 use crate::debug::Reader;
2 use crate::debug::transform::utils::resolve_die_ref;
3 
4 use super::address_transform::AddressTransform;
5 use super::dbi_log;
6 use super::expression::{CompiledExpression, FunctionFrameInfo, compile_expression};
7 use super::range_info_builder::RangeInfoBuilder;
8 use super::unit::InheritedAttr;
9 use cranelift_codegen::isa::TargetIsa;
10 use gimli::{AttributeValue, UnitOffset, write};
11 use wasmtime_environ::error::Error;
12 
13 #[derive(Debug)]
14 pub(crate) struct EntryAttributesContext<'a> {
15     pub subprograms: &'a mut InheritedAttr<SubprogramContext>,
16     pub frame_base: Option<&'a CompiledExpression>,
17 }
18 
19 #[derive(Debug)]
20 pub struct SubprogramContext {
21     pub obj_ptr: UnitOffset,
22     pub param_num: isize,
23 }
24 
is_exprloc_to_loclist_allowed(attr_name: gimli::constants::DwAt) -> bool25 fn is_exprloc_to_loclist_allowed(attr_name: gimli::constants::DwAt) -> bool {
26     match attr_name {
27         gimli::DW_AT_location
28         | gimli::DW_AT_string_length
29         | gimli::DW_AT_return_addr
30         | gimli::DW_AT_data_member_location
31         | gimli::DW_AT_frame_base
32         | gimli::DW_AT_segment
33         | gimli::DW_AT_static_link
34         | gimli::DW_AT_use_location
35         | gimli::DW_AT_vtable_elem_location => true,
36         _ => false,
37     }
38 }
39 
clone_die_attributes<'a>( convert_unit: &mut gimli::write::ConvertUnit<Reader<'a>>, entry: &gimli::write::ConvertUnitEntry<Reader<'a>>, addr_tr: &AddressTransform, frame_info: Option<&FunctionFrameInfo>, out_entry_id: write::UnitEntryId, subprogram_range_builder: Option<RangeInfoBuilder>, scope_ranges: Option<&Vec<(u64, u64)>>, mut attr_context: EntryAttributesContext, isa: &dyn TargetIsa, ) -> Result<(), Error>40 pub(crate) fn clone_die_attributes<'a>(
41     convert_unit: &mut gimli::write::ConvertUnit<Reader<'a>>,
42     entry: &gimli::write::ConvertUnitEntry<Reader<'a>>,
43     addr_tr: &AddressTransform,
44     frame_info: Option<&FunctionFrameInfo>,
45     out_entry_id: write::UnitEntryId,
46     subprogram_range_builder: Option<RangeInfoBuilder>,
47     scope_ranges: Option<&Vec<(u64, u64)>>,
48     mut attr_context: EntryAttributesContext,
49     isa: &dyn TargetIsa,
50 ) -> Result<(), Error> {
51     let unit = entry.read_unit;
52     let unit_encoding = unit.encoding();
53 
54     let range_info = if let Some(subprogram_range_builder) = subprogram_range_builder {
55         subprogram_range_builder
56     } else {
57         // FIXME for CU: currently address_transform operate on a single
58         // function range, and when CU spans multiple ranges the
59         // transformation may be incomplete.
60         RangeInfoBuilder::from(entry)?
61     };
62     range_info.build(addr_tr, convert_unit.unit, out_entry_id);
63 
64     let mut is_obj_ptr = false;
65     prepare_die_context(entry, &mut attr_context, &mut is_obj_ptr)?;
66 
67     for attr in &entry.attrs {
68         match attr.name() {
69             gimli::DW_AT_low_pc | gimli::DW_AT_high_pc | gimli::DW_AT_ranges => {
70                 // Handled by RangeInfoBuilder.
71                 continue;
72             }
73             gimli::DW_AT_object_pointer => {
74                 // Our consumers cannot handle 'this' typed as a non-pointer (recall
75                 // we translate all pointers to wrapper types), making it unusable.
76                 // To remedy this, we 'strip' instance-ness off of methods by removing
77                 // DW_AT_object_pointer and renaming 'this' to '__this'.
78                 if let Some(ref mut subprogram) =
79                     attr_context.subprograms.top_with_depth_mut(entry.depth)
80                 {
81                     // We expect this to reference a child entry in the same unit.
82                     if let Some(unit_offs) = match attr.value() {
83                         AttributeValue::DebugInfoRef(di_ref) => di_ref.to_unit_offset(&unit.header),
84                         AttributeValue::UnitRef(unit_ref) => Some(unit_ref),
85                         _ => None,
86                     } {
87                         subprogram.obj_ptr = unit_offs;
88                         dbi_log!("Stripped DW_AT_object_pointer");
89                         continue;
90                     }
91                 }
92             }
93             _ => {}
94         }
95 
96         if is_obj_ptr {
97             match attr.name() {
98                 gimli::DW_AT_artificial => {
99                     dbi_log!("Object pointer: stripped DW_AT_artificial");
100                     continue;
101                 }
102                 gimli::DW_AT_name => {
103                     let old_name: &str = &unit.attr_string(attr.value())?.to_string_lossy();
104                     let new_name = format!("__{old_name}");
105                     dbi_log!(
106                         "Object pointer: renamed '{}' -> '{}'",
107                         old_name,
108                         new_name.as_str()
109                     );
110 
111                     let attr_value =
112                         write::AttributeValue::StringRef(convert_unit.strings.add(new_name));
113                     convert_unit
114                         .unit
115                         .get_mut(out_entry_id)
116                         .set(gimli::DW_AT_name, attr_value);
117                     continue;
118                 }
119                 _ => {}
120             }
121         }
122 
123         let attr_value = attr.value();
124         let out_attr_value = match attr_value {
125             AttributeValue::Addr(u) => {
126                 let addr = addr_tr.translate(u).unwrap_or(write::Address::Constant(0));
127                 write::AttributeValue::Address(addr)
128             }
129             AttributeValue::DebugAddrIndex(i) => {
130                 let u = unit.address(i)?;
131                 let addr = addr_tr.translate(u).unwrap_or(write::Address::Constant(0));
132                 write::AttributeValue::Address(addr)
133             }
134             AttributeValue::RangeListsRef(_) | AttributeValue::DebugRngListsIndex(_) => {
135                 let r = unit.attr_ranges_offset(attr_value)?.unwrap();
136                 let range_info = RangeInfoBuilder::from_ranges_ref(unit, r)?;
137                 let range_list_id = range_info.build_ranges(addr_tr, &mut convert_unit.unit.ranges);
138                 write::AttributeValue::RangeListRef(range_list_id)
139             }
140             AttributeValue::LocationListsRef(_) | AttributeValue::DebugLocListsIndex(_) => {
141                 let mut locs = unit.attr_locations(attr_value)?.unwrap();
142                 let frame_base = attr_context.frame_base;
143 
144                 let mut result: Option<Vec<_>> = None;
145                 while let Some(loc) = locs.next()? {
146                     if let Some(expr) = compile_expression(&loc.data, unit_encoding, frame_base)? {
147                         let chunk = expr
148                             .build_with_locals(
149                                 &[(loc.range.begin, loc.range.end)],
150                                 addr_tr,
151                                 frame_info,
152                                 isa,
153                             )
154                             .expressions
155                             .filter(|i| {
156                                 // Ignore empty range
157                                 if let Ok((_, 0, _)) = i { false } else { true }
158                             })
159                             .map(|i| {
160                                 i.map(|(start, len, expr)| write::Location::StartLength {
161                                     begin: start,
162                                     length: len,
163                                     data: expr,
164                                 })
165                             })
166                             .collect::<Result<Vec<_>, _>>()?;
167                         match &mut result {
168                             Some(r) => r.extend(chunk),
169                             x @ None => *x = Some(chunk),
170                         }
171                     } else {
172                         // FIXME _expr contains invalid expression
173                         continue; // ignore entry
174                     }
175                 }
176                 if result.is_none() {
177                     continue; // no valid locations
178                 }
179                 let list_id = convert_unit
180                     .unit
181                     .locations
182                     .add(write::LocationList(result.unwrap()));
183                 write::AttributeValue::LocationListRef(list_id)
184             }
185             AttributeValue::Exprloc(_) if attr.name() == gimli::DW_AT_frame_base => {
186                 // We do not really "rewrite" the frame base so much as replace it outright.
187                 // References to it through the DW_OP_fbreg opcode will be expanded below.
188                 let mut cfa = write::Expression::new();
189                 cfa.op(gimli::DW_OP_call_frame_cfa);
190                 write::AttributeValue::Exprloc(cfa)
191             }
192             AttributeValue::Exprloc(ref expr) => {
193                 let frame_base = attr_context.frame_base;
194                 if let Some(expr) = compile_expression(expr, unit_encoding, frame_base)? {
195                     if expr.is_simple() {
196                         if let Some(expr) = expr.build() {
197                             write::AttributeValue::Exprloc(expr)
198                         } else {
199                             continue;
200                         }
201                     } else {
202                         // Conversion to loclist is required.
203                         if let Some(scope_ranges) = scope_ranges {
204                             let built_expression =
205                                 expr.build_with_locals(scope_ranges, addr_tr, frame_info, isa);
206                             let exprs = built_expression
207                                 .expressions
208                                 .collect::<Result<Vec<_>, _>>()?;
209                             if exprs.is_empty() {
210                                 continue;
211                             }
212                             // Micro-optimization all expressions alike, use one exprloc.
213                             let mut single_expr: Option<write::Expression> = None;
214                             if built_expression.covers_entire_scope {
215                                 for (_, _, expr) in &exprs {
216                                     if let Some(ref prev_expr) = single_expr {
217                                         if expr == prev_expr {
218                                             continue; // the same expression
219                                         }
220                                         single_expr = None;
221                                         break;
222                                     }
223                                     single_expr = Some(expr.clone())
224                                 }
225                             }
226                             if let Some(expr) = single_expr {
227                                 write::AttributeValue::Exprloc(expr)
228                             } else if is_exprloc_to_loclist_allowed(attr.name()) {
229                                 // Converting exprloc to loclist.
230                                 let mut locs = Vec::new();
231                                 for (begin, length, data) in exprs {
232                                     if length == 0 {
233                                         // Ignore empty range
234                                         continue;
235                                     }
236                                     locs.push(write::Location::StartLength {
237                                         begin,
238                                         length,
239                                         data,
240                                     });
241                                 }
242                                 let list_id =
243                                     convert_unit.unit.locations.add(write::LocationList(locs));
244                                 write::AttributeValue::LocationListRef(list_id)
245                             } else {
246                                 continue;
247                             }
248                         } else {
249                             continue;
250                         }
251                     }
252                 } else {
253                     // FIXME _expr contains invalid expression
254                     continue; // ignore attribute
255                 }
256             }
257             // No other attributes contain addresses or address offsets.
258             _ => match convert_unit.convert_attribute_value(unit, attr, &|_| None) {
259                 Ok(value) => value,
260                 Err(e) => {
261                     // Invalid `FileIndex` was seen in #8884 and #8904. In general it's
262                     // better to ignore invalid or unknown DWARF rather then failing outright.
263                     dbi_log!(
264                         "Ignoring entry {:x?} attribute {} = {:x?}: {e}",
265                         entry.offset.to_unit_section_offset(&convert_unit.read_unit),
266                         attr.name(),
267                         attr_value,
268                     );
269                     continue;
270                 }
271             },
272         };
273         let out_entry = convert_unit.unit.get_mut(out_entry_id);
274         out_entry.set(attr.name(), out_attr_value);
275     }
276     Ok(())
277 }
278 
prepare_die_context<'a>( entry: &gimli::write::ConvertUnitEntry<Reader<'a>>, attr_context: &mut EntryAttributesContext, is_obj_ptr: &mut bool, ) -> Result<(), Error>279 fn prepare_die_context<'a>(
280     entry: &gimli::write::ConvertUnitEntry<Reader<'a>>,
281     attr_context: &mut EntryAttributesContext,
282     is_obj_ptr: &mut bool,
283 ) -> Result<(), Error> {
284     let subprograms = &mut attr_context.subprograms;
285 
286     // Update the current context based on what kind of entry this is.
287     match entry.tag {
288         gimli::DW_TAG_subprogram | gimli::DW_TAG_inlined_subroutine | gimli::DW_TAG_entry_point => {
289             // Push the 'context' of there being no parameters (yet).
290             subprograms.push(
291                 entry.depth,
292                 SubprogramContext {
293                     obj_ptr: UnitOffset { 0: 0 },
294                     param_num: -1,
295                 },
296             );
297         }
298         gimli::DW_TAG_formal_parameter => {
299             // Formal parameter tags can be parented by catch blocks
300             // and such - not just subprogram DIEs. So we need to check
301             // that this DIE is indeed a direct child of a subprogram.
302             if let Some(subprogram) = subprograms.top_with_depth_mut(entry.depth - 1) {
303                 subprogram.param_num += 1;
304 
305                 if subprogram.obj_ptr == entry.offset
306                     || is_obj_ptr_param(entry, subprogram.param_num)?
307                 {
308                     *is_obj_ptr = true;
309                 }
310             }
311         }
312         _ => {}
313     }
314     Ok(())
315 }
316 
is_obj_ptr_param( entry: &gimli::write::ConvertUnitEntry<Reader<'_>>, param_num: isize, ) -> Result<bool, Error>317 fn is_obj_ptr_param(
318     entry: &gimli::write::ConvertUnitEntry<Reader<'_>>,
319     param_num: isize,
320 ) -> Result<bool, Error> {
321     debug_assert_eq!(entry.tag, gimli::DW_TAG_formal_parameter);
322     let unit = entry.read_unit;
323 
324     // This logic was taken loosely from LLDB. It is known
325     // that it is not fully correct (doesn't handle 'deduced
326     // this', for example).
327     // Q: DWARF includes DW_AT_object_pointer as we use it,
328     // why do we need this heuristic as well?
329     // A: Declarations do not include DW_AT_object_pointer.
330     if param_num == 0
331         && entry.attr_value(gimli::DW_AT_artificial) == Some(AttributeValue::Flag(true))
332     {
333         // Either this has no name (declarations omit them), or its explicitly "this".
334         let name = entry.attr_value(gimli::DW_AT_name);
335         if name.is_none() || unit.attr_string(name.unwrap())?.slice().eq(b"this") {
336             // Finally, a type check. We expect a pointer.
337             if let Some(type_attr) = entry.attr_value(gimli::DW_AT_type) {
338                 if let Some(type_die) = resolve_die_ref(unit, &type_attr)? {
339                     return Ok(type_die.tag == gimli::DW_TAG_pointer_type);
340                 }
341             }
342         }
343     };
344 
345     return Ok(false);
346 }
347