1 use crate::debug::Reader;
2 use crate::debug::transform::utils::resolve_die_ref;
3 
4 use super::address_transform::AddressTransform;
5 use super::expression::{CompiledExpression, FunctionFrameInfo, compile_expression};
6 use super::range_info_builder::RangeInfoBuilder;
7 use super::refs::{PendingDebugInfoRefs, PendingUnitRefs};
8 use super::unit::InheritedAttr;
9 use super::{TransformError, dbi_log};
10 use cranelift_codegen::isa::TargetIsa;
11 use gimli::{
12     AttributeValue, DebugLineOffset, DebuggingInformationEntry, UnitOffset, UnitRef, write,
13 };
14 use wasmtime_environ::error::{Error, bail};
15 
16 #[derive(Debug)]
17 pub(crate) enum EntryAttributesContext<'a> {
18     Root(Option<DebugLineOffset>),
19     Children {
20         depth: usize,
21         subprograms: &'a mut InheritedAttr<SubprogramContext>,
22         file_map: &'a [write::FileId],
23         file_index_base: u64,
24         frame_base: Option<&'a CompiledExpression>,
25     },
26 }
27 
28 #[derive(Debug)]
29 pub struct SubprogramContext {
30     pub obj_ptr: UnitOffset,
31     pub param_num: isize,
32 }
33 
34 fn is_exprloc_to_loclist_allowed(attr_name: gimli::constants::DwAt) -> bool {
35     match attr_name {
36         gimli::DW_AT_location
37         | gimli::DW_AT_string_length
38         | gimli::DW_AT_return_addr
39         | gimli::DW_AT_data_member_location
40         | gimli::DW_AT_frame_base
41         | gimli::DW_AT_segment
42         | gimli::DW_AT_static_link
43         | gimli::DW_AT_use_location
44         | gimli::DW_AT_vtable_elem_location => true,
45         _ => false,
46     }
47 }
48 
49 pub(crate) fn clone_die_attributes<'a>(
50     unit: UnitRef<Reader<'a>>,
51     entry: &DebuggingInformationEntry<Reader<'a>>,
52     addr_tr: &AddressTransform,
53     frame_info: Option<&FunctionFrameInfo>,
54     out_unit: &mut write::Unit,
55     out_entry_id: write::UnitEntryId,
56     subprogram_range_builder: Option<RangeInfoBuilder>,
57     scope_ranges: Option<&Vec<(u64, u64)>>,
58     out_strings: &mut write::StringTable,
59     pending_die_refs: &mut PendingUnitRefs,
60     pending_di_refs: &mut PendingDebugInfoRefs,
61     mut attr_context: EntryAttributesContext<'_>,
62     isa: &dyn TargetIsa,
63 ) -> Result<(), Error> {
64     let unit_encoding = unit.encoding();
65 
66     let range_info = if let Some(subprogram_range_builder) = subprogram_range_builder {
67         subprogram_range_builder
68     } else {
69         // FIXME for CU: currently address_transform operate on a single
70         // function range, and when CU spans multiple ranges the
71         // transformation may be incomplete.
72         RangeInfoBuilder::from(unit, entry)?
73     };
74     range_info.build(addr_tr, out_unit, out_entry_id);
75 
76     let mut is_obj_ptr = false;
77     prepare_die_context(unit, entry, &mut attr_context, &mut is_obj_ptr)?;
78 
79     let mut attrs = entry.attrs();
80     while let Some(attr) = attrs.next()? {
81         match attr.name() {
82             gimli::DW_AT_low_pc | gimli::DW_AT_high_pc | gimli::DW_AT_ranges => {
83                 // Handled by RangeInfoBuilder.
84                 continue;
85             }
86             gimli::DW_AT_object_pointer => {
87                 // Our consumers cannot handle 'this' typed as a non-pointer (recall
88                 // we translate all pointers to wrapper types), making it unusable.
89                 // To remedy this, we 'strip' instance-ness off of methods by removing
90                 // DW_AT_object_pointer and renaming 'this' to '__this'.
91                 if let EntryAttributesContext::Children {
92                     depth,
93                     ref mut subprograms,
94                     ..
95                 } = attr_context
96                 {
97                     if let Some(ref mut subprogram) = subprograms.top_with_depth_mut(depth) {
98                         // We expect this to reference a child entry in the same unit.
99                         if let Some(unit_offs) = match attr.value() {
100                             AttributeValue::DebugInfoRef(di_ref) => {
101                                 di_ref.to_unit_offset(&unit.header)
102                             }
103                             AttributeValue::UnitRef(unit_ref) => Some(unit_ref),
104                             _ => None,
105                         } {
106                             subprogram.obj_ptr = unit_offs;
107                             dbi_log!("Stripped DW_AT_object_pointer");
108                             continue;
109                         }
110                     }
111                 }
112             }
113             gimli::DW_AT_str_offsets_base
114             | gimli::DW_AT_addr_base
115             | gimli::DW_AT_rnglists_base
116             | gimli::DW_AT_loclists_base
117             | gimli::DW_AT_dwo_name
118             | gimli::DW_AT_GNU_addr_base
119             | gimli::DW_AT_GNU_ranges_base
120             | gimli::DW_AT_GNU_dwo_name
121             | gimli::DW_AT_GNU_dwo_id => {
122                 // DWARF encoding details that we don't need to copy.
123                 continue;
124             }
125             _ => {}
126         }
127 
128         if is_obj_ptr {
129             match attr.name() {
130                 gimli::DW_AT_artificial => {
131                     dbi_log!("Object pointer: stripped DW_AT_artificial");
132                     continue;
133                 }
134                 gimli::DW_AT_name => {
135                     let old_name: &str = &unit.attr_string(attr.value())?.to_string_lossy();
136                     let new_name = format!("__{old_name}");
137                     dbi_log!(
138                         "Object pointer: renamed '{}' -> '{}'",
139                         old_name,
140                         new_name.as_str()
141                     );
142 
143                     let attr_value = write::AttributeValue::StringRef(out_strings.add(new_name));
144                     out_unit
145                         .get_mut(out_entry_id)
146                         .set(gimli::DW_AT_name, attr_value);
147                     continue;
148                 }
149                 _ => {}
150             }
151         }
152 
153         let attr_value = attr.value();
154         let out_attr_value = match attr_value {
155             AttributeValue::Addr(u) => {
156                 let addr = addr_tr.translate(u).unwrap_or(write::Address::Constant(0));
157                 write::AttributeValue::Address(addr)
158             }
159             AttributeValue::DebugAddrIndex(i) => {
160                 let u = unit.address(i)?;
161                 let addr = addr_tr.translate(u).unwrap_or(write::Address::Constant(0));
162                 write::AttributeValue::Address(addr)
163             }
164             AttributeValue::Block(d) => write::AttributeValue::Block(d.to_vec()),
165             AttributeValue::Udata(u) => write::AttributeValue::Udata(u),
166             AttributeValue::Data1(d) => write::AttributeValue::Data1(d),
167             AttributeValue::Data2(d) => write::AttributeValue::Data2(d),
168             AttributeValue::Data4(d) => write::AttributeValue::Data4(d),
169             AttributeValue::Data8(d) => write::AttributeValue::Data8(d),
170             AttributeValue::Sdata(d) => write::AttributeValue::Sdata(d),
171             AttributeValue::Flag(f) => write::AttributeValue::Flag(f),
172             AttributeValue::DebugLineRef(line_program_offset) => {
173                 if let EntryAttributesContext::Root(o) = attr_context {
174                     if o != Some(line_program_offset) {
175                         return Err(TransformError("invalid debug_line offset").into());
176                     }
177                     write::AttributeValue::LineProgramRef
178                 } else {
179                     return Err(TransformError("unexpected debug_line index attribute").into());
180                 }
181             }
182             AttributeValue::FileIndex(i) => {
183                 if let EntryAttributesContext::Children {
184                     file_map,
185                     file_index_base,
186                     ..
187                 } = attr_context
188                 {
189                     let index = usize::try_from(i - file_index_base)
190                         .ok()
191                         .and_then(|i| file_map.get(i).copied());
192                     match index {
193                         Some(index) => write::AttributeValue::FileIndex(Some(index)),
194                         // This was seen to be invalid in #8884 and #8904 so
195                         // ignore this seemingly invalid DWARF from LLVM
196                         None => continue,
197                     }
198                 } else {
199                     return Err(TransformError("unexpected file index attribute").into());
200                 }
201             }
202             AttributeValue::String(d) => write::AttributeValue::String(d.to_vec()),
203             AttributeValue::DebugStrRef(_) | AttributeValue::DebugStrOffsetsIndex(_) => {
204                 let s = unit.attr_string(attr_value)?.to_vec();
205                 write::AttributeValue::StringRef(out_strings.add(s))
206             }
207             AttributeValue::RangeListsRef(_) | AttributeValue::DebugRngListsIndex(_) => {
208                 let r = unit.attr_ranges_offset(attr_value)?.unwrap();
209                 let range_info = RangeInfoBuilder::from_ranges_ref(unit, r)?;
210                 let range_list_id = range_info.build_ranges(addr_tr, &mut out_unit.ranges);
211                 write::AttributeValue::RangeListRef(range_list_id)
212             }
213             AttributeValue::LocationListsRef(_) | AttributeValue::DebugLocListsIndex(_) => {
214                 let r = unit.attr_locations_offset(attr_value)?.unwrap();
215                 let mut locs = unit.locations(r)?;
216                 let frame_base =
217                     if let EntryAttributesContext::Children { frame_base, .. } = attr_context {
218                         frame_base
219                     } else {
220                         None
221                     };
222 
223                 let mut result: Option<Vec<_>> = None;
224                 while let Some(loc) = locs.next()? {
225                     if let Some(expr) = compile_expression(&loc.data, unit_encoding, frame_base)? {
226                         let chunk = expr
227                             .build_with_locals(
228                                 &[(loc.range.begin, loc.range.end)],
229                                 addr_tr,
230                                 frame_info,
231                                 isa,
232                             )
233                             .expressions
234                             .filter(|i| {
235                                 // Ignore empty range
236                                 if let Ok((_, 0, _)) = i { false } else { true }
237                             })
238                             .map(|i| {
239                                 i.map(|(start, len, expr)| write::Location::StartLength {
240                                     begin: start,
241                                     length: len,
242                                     data: expr,
243                                 })
244                             })
245                             .collect::<Result<Vec<_>, _>>()?;
246                         match &mut result {
247                             Some(r) => r.extend(chunk),
248                             x @ None => *x = Some(chunk),
249                         }
250                     } else {
251                         // FIXME _expr contains invalid expression
252                         continue; // ignore entry
253                     }
254                 }
255                 if result.is_none() {
256                     continue; // no valid locations
257                 }
258                 let list_id = out_unit.locations.add(write::LocationList(result.unwrap()));
259                 write::AttributeValue::LocationListRef(list_id)
260             }
261             AttributeValue::Exprloc(_) if attr.name() == gimli::DW_AT_frame_base => {
262                 // We do not really "rewrite" the frame base so much as replace it outright.
263                 // References to it through the DW_OP_fbreg opcode will be expanded below.
264                 let mut cfa = write::Expression::new();
265                 cfa.op(gimli::DW_OP_call_frame_cfa);
266                 write::AttributeValue::Exprloc(cfa)
267             }
268             AttributeValue::Exprloc(ref expr) => {
269                 let frame_base =
270                     if let EntryAttributesContext::Children { frame_base, .. } = attr_context {
271                         frame_base
272                     } else {
273                         None
274                     };
275                 if let Some(expr) = compile_expression(expr, unit_encoding, frame_base)? {
276                     if expr.is_simple() {
277                         if let Some(expr) = expr.build() {
278                             write::AttributeValue::Exprloc(expr)
279                         } else {
280                             continue;
281                         }
282                     } else {
283                         // Conversion to loclist is required.
284                         if let Some(scope_ranges) = scope_ranges {
285                             let built_expression =
286                                 expr.build_with_locals(scope_ranges, addr_tr, frame_info, isa);
287                             let exprs = built_expression
288                                 .expressions
289                                 .collect::<Result<Vec<_>, _>>()?;
290                             if exprs.is_empty() {
291                                 continue;
292                             }
293                             // Micro-optimization all expressions alike, use one exprloc.
294                             let mut single_expr: Option<write::Expression> = None;
295                             if built_expression.covers_entire_scope {
296                                 for (_, _, expr) in &exprs {
297                                     if let Some(ref prev_expr) = single_expr {
298                                         if expr == prev_expr {
299                                             continue; // the same expression
300                                         }
301                                         single_expr = None;
302                                         break;
303                                     }
304                                     single_expr = Some(expr.clone())
305                                 }
306                             }
307                             if let Some(expr) = single_expr {
308                                 write::AttributeValue::Exprloc(expr)
309                             } else if is_exprloc_to_loclist_allowed(attr.name()) {
310                                 // Converting exprloc to loclist.
311                                 let mut locs = Vec::new();
312                                 for (begin, length, data) in exprs {
313                                     if length == 0 {
314                                         // Ignore empty range
315                                         continue;
316                                     }
317                                     locs.push(write::Location::StartLength {
318                                         begin,
319                                         length,
320                                         data,
321                                     });
322                                 }
323                                 let list_id = out_unit.locations.add(write::LocationList(locs));
324                                 write::AttributeValue::LocationListRef(list_id)
325                             } else {
326                                 continue;
327                             }
328                         } else {
329                             continue;
330                         }
331                     }
332                 } else {
333                     // FIXME _expr contains invalid expression
334                     continue; // ignore attribute
335                 }
336             }
337             AttributeValue::Encoding(e) => write::AttributeValue::Encoding(e),
338             AttributeValue::DecimalSign(e) => write::AttributeValue::DecimalSign(e),
339             AttributeValue::Endianity(e) => write::AttributeValue::Endianity(e),
340             AttributeValue::Accessibility(e) => write::AttributeValue::Accessibility(e),
341             AttributeValue::Visibility(e) => write::AttributeValue::Visibility(e),
342             AttributeValue::Virtuality(e) => write::AttributeValue::Virtuality(e),
343             AttributeValue::Language(e) => write::AttributeValue::Language(e),
344             AttributeValue::AddressClass(e) => write::AttributeValue::AddressClass(e),
345             AttributeValue::IdentifierCase(e) => write::AttributeValue::IdentifierCase(e),
346             AttributeValue::CallingConvention(e) => write::AttributeValue::CallingConvention(e),
347             AttributeValue::Inline(e) => write::AttributeValue::Inline(e),
348             AttributeValue::Ordering(e) => write::AttributeValue::Ordering(e),
349             AttributeValue::UnitRef(offset) => {
350                 pending_die_refs.insert(out_entry_id, attr.name(), offset);
351                 continue;
352             }
353             AttributeValue::DebugInfoRef(offset) => {
354                 pending_di_refs.insert(out_entry_id, attr.name(), offset);
355                 continue;
356             }
357             a => bail!("Unexpected attribute: {a:?}"),
358         };
359         let out_entry: &mut write::DebuggingInformationEntry = out_unit.get_mut(out_entry_id);
360         out_entry.set(attr.name(), out_attr_value);
361     }
362     Ok(())
363 }
364 
365 fn prepare_die_context<'a>(
366     unit: UnitRef<Reader<'a>>,
367     entry: &DebuggingInformationEntry<Reader<'a>>,
368     attr_context: &mut EntryAttributesContext<'_>,
369     is_obj_ptr: &mut bool,
370 ) -> Result<(), Error> {
371     let EntryAttributesContext::Children {
372         depth, subprograms, ..
373     } = attr_context
374     else {
375         return Ok(());
376     };
377 
378     // Update the current context based on what kind of entry this is.
379     match entry.tag() {
380         gimli::DW_TAG_subprogram | gimli::DW_TAG_inlined_subroutine | gimli::DW_TAG_entry_point => {
381             // Push the 'context' of there being no parameters (yet).
382             subprograms.push(
383                 *depth,
384                 SubprogramContext {
385                     obj_ptr: UnitOffset { 0: 0 },
386                     param_num: -1,
387                 },
388             );
389         }
390         gimli::DW_TAG_formal_parameter => {
391             // Formal parameter tags can be parented by catch blocks
392             // and such - not just subprogram DIEs. So we need to check
393             // that this DIE is indeed a direct child of a subprogram.
394             if let Some(subprogram) = subprograms.top_with_depth_mut(*depth - 1) {
395                 subprogram.param_num += 1;
396 
397                 if subprogram.obj_ptr == entry.offset()
398                     || is_obj_ptr_param(unit, entry, subprogram.param_num)?
399                 {
400                     *is_obj_ptr = true;
401                 }
402             }
403         }
404         _ => {}
405     }
406     Ok(())
407 }
408 
409 fn is_obj_ptr_param<'a>(
410     unit: UnitRef<Reader<'a>>,
411     entry: &DebuggingInformationEntry<Reader<'a>>,
412     param_num: isize,
413 ) -> Result<bool, Error> {
414     debug_assert!(entry.tag() == gimli::DW_TAG_formal_parameter);
415 
416     // This logic was taken loosely from LLDB. It is known
417     // that it is not fully correct (doesn't handle 'deduced
418     // this', for example).
419     // Q: DWARF includes DW_AT_object_pointer as we use it,
420     // why do we need this heuristic as well?
421     // A: Declarations do not include DW_AT_object_pointer.
422     if param_num == 0
423         && entry.attr_value(gimli::DW_AT_artificial)? == Some(AttributeValue::Flag(true))
424     {
425         // Either this has no name (declarations omit them), or its explicitly "this".
426         let name = entry.attr_value(gimli::DW_AT_name)?;
427         if name.is_none() || unit.attr_string(name.unwrap())?.slice().eq(b"this") {
428             // Finally, a type check. We expect a pointer.
429             if let Some(type_attr) = entry.attr_value(gimli::DW_AT_type)? {
430                 if let Some(type_die) = resolve_die_ref(unit, &type_attr)? {
431                     return Ok(type_die.tag() == gimli::DW_TAG_pointer_type);
432                 }
433             }
434         }
435     };
436 
437     return Ok(false);
438 }
439