1 use super::address_transform::AddressTransform;
2 use super::attr::{clone_die_attributes, FileAttributeContext};
3 use super::expression::compile_expression;
4 use super::line_program::clone_line_program;
5 use super::range_info_builder::RangeInfoBuilder;
6 use super::refs::{PendingDebugInfoRefs, PendingUnitRefs, UnitRefsMap};
7 use super::utils::{add_internal_types, append_vmctx_info};
8 use super::DebugInputContext;
9 use crate::debug::{Compilation, Reader};
10 use anyhow::{Context, Error};
11 use cranelift_codegen::ir::Endianness;
12 use cranelift_codegen::isa::TargetIsa;
13 use gimli::write;
14 use gimli::{AttributeValue, DebuggingInformationEntry, Unit};
15 use std::collections::HashSet;
16 use wasmtime_environ::StaticModuleIndex;
17 use wasmtime_versioned_export_macros::versioned_stringify_ident;
18 
19 struct InheritedAttr<T> {
20     stack: Vec<(usize, T)>,
21 }
22 
23 impl<T> InheritedAttr<T> {
24     fn new() -> Self {
25         InheritedAttr { stack: Vec::new() }
26     }
27 
28     fn update(&mut self, depth: usize) {
29         while !self.stack.is_empty() && self.stack.last().unwrap().0 >= depth {
30             self.stack.pop();
31         }
32     }
33 
34     fn push(&mut self, depth: usize, value: T) {
35         self.stack.push((depth, value));
36     }
37 
38     fn top(&self) -> Option<&T> {
39         self.stack.last().map(|entry| &entry.1)
40     }
41 
42     fn is_empty(&self) -> bool {
43         self.stack.is_empty()
44     }
45 }
46 
47 fn get_base_type_name(
48     type_entry: &DebuggingInformationEntry<Reader<'_>>,
49     unit: &Unit<Reader<'_>, usize>,
50     context: &DebugInputContext<Reader<'_>>,
51 ) -> Result<String, Error> {
52     // FIXME remove recursion.
53     if let Some(AttributeValue::UnitRef(ref offset)) = type_entry.attr_value(gimli::DW_AT_type)? {
54         let mut entries = unit.entries_at_offset(*offset)?;
55         entries.next_entry()?;
56         if let Some(die) = entries.current() {
57             if let Some(AttributeValue::DebugStrRef(str_offset)) =
58                 die.attr_value(gimli::DW_AT_name)?
59             {
60                 return Ok(String::from(
61                     context.debug_str.get_str(str_offset)?.to_string()?,
62                 ));
63             }
64             match die.tag() {
65                 gimli::DW_TAG_const_type => {
66                     return Ok(format!("const {}", get_base_type_name(die, unit, context)?));
67                 }
68                 gimli::DW_TAG_pointer_type => {
69                     return Ok(format!("{}*", get_base_type_name(die, unit, context)?));
70                 }
71                 gimli::DW_TAG_reference_type => {
72                     return Ok(format!("{}&", get_base_type_name(die, unit, context)?));
73                 }
74                 gimli::DW_TAG_array_type => {
75                     return Ok(format!("{}[]", get_base_type_name(die, unit, context)?));
76                 }
77                 _ => (),
78             }
79         }
80     }
81     Ok(String::from("??"))
82 }
83 
84 enum WebAssemblyPtrKind {
85     Reference,
86     Pointer,
87 }
88 
89 /// Replaces WebAssembly pointer type DIE with the wrapper
90 /// which natively represented by offset in a Wasm memory.
91 ///
92 /// `pointer_type_entry` is a DW_TAG_pointer_type entry (e.g. `T*`),
93 /// which refers its base type (e.g. `T`), or is a
94 /// DW_TAG_reference_type (e.g. `T&`).
95 ///
96 /// The generated wrapper is a structure that contains only the
97 /// `__ptr` field. The utility operators overloads is added to
98 /// provide better debugging experience.
99 ///
100 /// Wrappers of pointer and reference types are identical except for
101 /// their name -- they are formatted and accessed from a debugger
102 /// the same way.
103 ///
104 /// Notice that "resolve_vmctx_memory_ptr" is external/builtin
105 /// subprogram that is not part of Wasm code.
106 fn replace_pointer_type(
107     parent_id: write::UnitEntryId,
108     kind: WebAssemblyPtrKind,
109     comp_unit: &mut write::Unit,
110     wp_die_id: write::UnitEntryId,
111     pointer_type_entry: &DebuggingInformationEntry<Reader<'_>>,
112     unit: &Unit<Reader<'_>, usize>,
113     context: &DebugInputContext<Reader<'_>>,
114     out_strings: &mut write::StringTable,
115     pending_die_refs: &mut PendingUnitRefs,
116 ) -> Result<write::UnitEntryId, Error> {
117     const WASM_PTR_LEN: u8 = 4;
118 
119     macro_rules! add_tag {
120         ($parent_id:ident, $tag:expr => $die:ident as $die_id:ident { $($a:path = $v:expr),* }) => {
121             let $die_id = comp_unit.add($parent_id, $tag);
122             #[allow(unused_variables)]
123             let $die = comp_unit.get_mut($die_id);
124             $( $die.set($a, $v); )*
125         };
126     }
127 
128     // Build DW_TAG_structure_type for the wrapper:
129     //  .. DW_AT_name = "WebAssemblyPtrWrapper<T>",
130     //  .. DW_AT_byte_size = 4,
131     let name = match kind {
132         WebAssemblyPtrKind::Pointer => format!(
133             "WebAssemblyPtrWrapper<{}>",
134             get_base_type_name(pointer_type_entry, unit, context)?
135         ),
136         WebAssemblyPtrKind::Reference => format!(
137             "WebAssemblyRefWrapper<{}>",
138             get_base_type_name(pointer_type_entry, unit, context)?
139         ),
140     };
141     add_tag!(parent_id, gimli::DW_TAG_structure_type => wrapper_die as wrapper_die_id {
142         gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add(name.as_str())),
143         gimli::DW_AT_byte_size = write::AttributeValue::Data1(WASM_PTR_LEN)
144     });
145 
146     // Build DW_TAG_pointer_type for `WebAssemblyPtrWrapper<T>*`:
147     //  .. DW_AT_type = <wrapper_die>
148     add_tag!(parent_id, gimli::DW_TAG_pointer_type => wrapper_ptr_type as wrapper_ptr_type_id {
149         gimli::DW_AT_type = write::AttributeValue::UnitRef(wrapper_die_id)
150     });
151 
152     let base_type_id = pointer_type_entry.attr_value(gimli::DW_AT_type)?;
153     // Build DW_TAG_reference_type for `T&`:
154     //  .. DW_AT_type = <base_type>
155     add_tag!(parent_id, gimli::DW_TAG_reference_type => ref_type as ref_type_id {});
156     if let Some(AttributeValue::UnitRef(ref offset)) = base_type_id {
157         pending_die_refs.insert(ref_type_id, gimli::DW_AT_type, *offset);
158     }
159 
160     // Build DW_TAG_pointer_type for `T*`:
161     //  .. DW_AT_type = <base_type>
162     add_tag!(parent_id, gimli::DW_TAG_pointer_type => ptr_type as ptr_type_id {});
163     if let Some(AttributeValue::UnitRef(ref offset)) = base_type_id {
164         pending_die_refs.insert(ptr_type_id, gimli::DW_AT_type, *offset);
165     }
166 
167     // Build wrapper_die's DW_TAG_template_type_parameter:
168     //  .. DW_AT_name = "T"
169     //  .. DW_AT_type = <base_type>
170     add_tag!(wrapper_die_id, gimli::DW_TAG_template_type_parameter => t_param_die as t_param_die_id {
171         gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("T"))
172     });
173     if let Some(AttributeValue::UnitRef(ref offset)) = base_type_id {
174         pending_die_refs.insert(t_param_die_id, gimli::DW_AT_type, *offset);
175     }
176 
177     // Build wrapper_die's DW_TAG_member for `__ptr`:
178     //  .. DW_AT_name = "__ptr"
179     //  .. DW_AT_type = <wp_die>
180     //  .. DW_AT_location = 0
181     add_tag!(wrapper_die_id, gimli::DW_TAG_member => m_die as m_die_id {
182         gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("__ptr")),
183         gimli::DW_AT_type = write::AttributeValue::UnitRef(wp_die_id),
184         gimli::DW_AT_data_member_location = write::AttributeValue::Data1(0)
185     });
186 
187     // Build wrapper_die's DW_TAG_subprogram for `ptr()`:
188     //  .. DW_AT_linkage_name = "resolve_vmctx_memory_ptr"
189     //  .. DW_AT_name = "ptr"
190     //  .. DW_AT_type = <ptr_type>
191     //  .. DW_TAG_formal_parameter
192     //  ..  .. DW_AT_type = <wrapper_ptr_type>
193     //  ..  .. DW_AT_artificial = 1
194     add_tag!(wrapper_die_id, gimli::DW_TAG_subprogram => deref_op_die as deref_op_die_id {
195         gimli::DW_AT_linkage_name = write::AttributeValue::StringRef(out_strings.add(versioned_stringify_ident!(resolve_vmctx_memory_ptr))),
196         gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("ptr")),
197         gimli::DW_AT_type = write::AttributeValue::UnitRef(ptr_type_id)
198     });
199     add_tag!(deref_op_die_id, gimli::DW_TAG_formal_parameter => deref_op_this_param as deref_op_this_param_id {
200         gimli::DW_AT_type = write::AttributeValue::UnitRef(wrapper_ptr_type_id),
201         gimli::DW_AT_artificial = write::AttributeValue::Flag(true)
202     });
203 
204     // Build wrapper_die's DW_TAG_subprogram for `operator*`:
205     //  .. DW_AT_linkage_name = "resolve_vmctx_memory_ptr"
206     //  .. DW_AT_name = "operator*"
207     //  .. DW_AT_type = <ref_type>
208     //  .. DW_TAG_formal_parameter
209     //  ..  .. DW_AT_type = <wrapper_ptr_type>
210     //  ..  .. DW_AT_artificial = 1
211     add_tag!(wrapper_die_id, gimli::DW_TAG_subprogram => deref_op_die as deref_op_die_id {
212         gimli::DW_AT_linkage_name = write::AttributeValue::StringRef(out_strings.add(versioned_stringify_ident!(resolve_vmctx_memory_ptr))),
213         gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("operator*")),
214         gimli::DW_AT_type = write::AttributeValue::UnitRef(ref_type_id)
215     });
216     add_tag!(deref_op_die_id, gimli::DW_TAG_formal_parameter => deref_op_this_param as deref_op_this_param_id {
217         gimli::DW_AT_type = write::AttributeValue::UnitRef(wrapper_ptr_type_id),
218         gimli::DW_AT_artificial = write::AttributeValue::Flag(true)
219     });
220 
221     // Build wrapper_die's DW_TAG_subprogram for `operator->`:
222     //  .. DW_AT_linkage_name = "resolve_vmctx_memory_ptr"
223     //  .. DW_AT_name = "operator->"
224     //  .. DW_AT_type = <ptr_type>
225     //  .. DW_TAG_formal_parameter
226     //  ..  .. DW_AT_type = <wrapper_ptr_type>
227     //  ..  .. DW_AT_artificial = 1
228     add_tag!(wrapper_die_id, gimli::DW_TAG_subprogram => deref_op_die as deref_op_die_id {
229         gimli::DW_AT_linkage_name = write::AttributeValue::StringRef(out_strings.add(versioned_stringify_ident!(resolve_vmctx_memory_ptr))),
230         gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("operator->")),
231         gimli::DW_AT_type = write::AttributeValue::UnitRef(ptr_type_id)
232     });
233     add_tag!(deref_op_die_id, gimli::DW_TAG_formal_parameter => deref_op_this_param as deref_op_this_param_id {
234         gimli::DW_AT_type = write::AttributeValue::UnitRef(wrapper_ptr_type_id),
235         gimli::DW_AT_artificial = write::AttributeValue::Flag(true)
236     });
237 
238     Ok(wrapper_die_id)
239 }
240 
241 fn is_dead_code(entry: &DebuggingInformationEntry<Reader<'_>>) -> bool {
242     const TOMBSTONE: u64 = u32::MAX as u64;
243 
244     match entry.attr_value(gimli::DW_AT_low_pc) {
245         Ok(Some(AttributeValue::Addr(addr))) => addr == TOMBSTONE,
246         _ => false,
247     }
248 }
249 
250 pub(crate) fn clone_unit(
251     compilation: &mut Compilation<'_>,
252     module: StaticModuleIndex,
253     unit: &Unit<Reader<'_>, usize>,
254     split_unit: Option<&Unit<Reader<'_>, usize>>,
255     split_dwarf: Option<&gimli::Dwarf<Reader<'_>>>,
256     context: &DebugInputContext<Reader<'_>>,
257     addr_tr: &AddressTransform,
258     out_encoding: gimli::Encoding,
259     out_units: &mut write::UnitTable,
260     out_strings: &mut write::StringTable,
261     translated: &mut HashSet<usize>,
262     isa: &dyn TargetIsa,
263 ) -> Result<Option<(write::UnitId, UnitRefsMap, PendingDebugInfoRefs)>, Error> {
264     let mut die_ref_map = UnitRefsMap::new();
265     let mut pending_die_refs = PendingUnitRefs::new();
266     let mut pending_di_refs = PendingDebugInfoRefs::new();
267     let mut stack = Vec::new();
268 
269     let mut program_unit = unit;
270     let mut skeleton_die = None;
271 
272     // Get entries in outer scope to avoid borrowing on short lived temporary.
273     let mut skeleton_entries = unit.entries();
274     if let Some(unit) = split_unit {
275         program_unit = unit;
276 
277         // From the spec, a skeleton unit has no children so we can assume the first, and only, entry is the DW_TAG_skeleton_unit (https://dwarfstd.org/doc/DWARF5.pdf).
278         if let Some(die_tuple) = skeleton_entries.next_dfs()? {
279             skeleton_die = Some(die_tuple.1);
280         }
281     }
282 
283     let dwarf = &compilation.translations[module].debuginfo.dwarf;
284     let memory_offset = &compilation.module_memory_offsets[module];
285 
286     // Iterate over all of this compilation unit's entries.
287     let mut entries = program_unit.entries();
288     let (mut comp_unit, unit_id, file_map, file_index_base, cu_low_pc, wp_die_id, vmctx_die_id) =
289         if let Some((depth_delta, entry)) = entries.next_dfs()? {
290             assert_eq!(depth_delta, 0);
291             let (out_line_program, debug_line_offset, file_map, file_index_base) =
292                 clone_line_program(
293                     split_dwarf.unwrap_or(dwarf),
294                     dwarf,
295                     program_unit,
296                     entry,
297                     skeleton_die,
298                     addr_tr,
299                     out_encoding,
300                     context.debug_str,
301                     context.debug_line,
302                     out_strings,
303                 )?;
304 
305             if entry.tag() == gimli::DW_TAG_compile_unit {
306                 let unit_id = out_units.add(write::Unit::new(out_encoding, out_line_program));
307                 let comp_unit = out_units.get_mut(unit_id);
308 
309                 let root_id = comp_unit.root();
310                 die_ref_map.insert(entry.offset(), root_id);
311                 let cu_low_pc = unit.low_pc;
312 
313                 clone_die_attributes(
314                     split_dwarf.unwrap_or(dwarf),
315                     &program_unit,
316                     entry,
317                     context,
318                     addr_tr,
319                     None,
320                     comp_unit,
321                     root_id,
322                     None,
323                     None,
324                     cu_low_pc,
325                     out_strings,
326                     &mut pending_die_refs,
327                     &mut pending_di_refs,
328                     FileAttributeContext::Root(Some(debug_line_offset)),
329                     isa,
330                 )?;
331 
332                 let (wp_die_id, vmctx_die_id) =
333                     add_internal_types(comp_unit, root_id, out_strings, memory_offset);
334 
335                 stack.push(root_id);
336                 (
337                     comp_unit,
338                     unit_id,
339                     file_map,
340                     file_index_base,
341                     cu_low_pc,
342                     wp_die_id,
343                     vmctx_die_id,
344                 )
345             } else {
346                 // Can happen when the DWARF is split and we dont have the package/dwo files.
347                 // This is a better user experience than errorring.
348                 return Ok(None); // empty:
349             }
350         } else {
351             return Ok(None); // empty
352         };
353     let mut skip_at_depth = None;
354     let mut current_frame_base = InheritedAttr::new();
355     let mut current_value_range = InheritedAttr::new();
356     let mut current_scope_ranges = InheritedAttr::new();
357     while let Some((depth_delta, entry)) = entries.next_dfs()? {
358         // If `skip_at_depth` is `Some` then we previously decided to skip over
359         // a node and all it's children. Let A be the last node processed, B be
360         // the first node skipped, C be previous node, and D the current node.
361         // Then `cached` is the difference from A to B, `depth` is the difference
362         // from B to C, and `depth_delta` is the differenc from C to D.
363         let depth_delta = if let Some((depth, cached)) = skip_at_depth {
364             // `new_depth` = B to D
365             let new_depth = depth + depth_delta;
366             // if D is below B continue to skip
367             if new_depth > 0 {
368                 skip_at_depth = Some((new_depth, cached));
369                 continue;
370             }
371             // otherwise process D with `depth_delta` being the difference from A to D
372             skip_at_depth = None;
373             new_depth + cached
374         } else {
375             depth_delta
376         };
377 
378         if !context
379             .reachable
380             .contains(&entry.offset().to_unit_section_offset(&unit))
381             || is_dead_code(&entry)
382         {
383             // entry is not reachable: discarding all its info.
384             // Here B = C so `depth` is 0. A is the previous node so `cached` =
385             // `depth_delta`.
386             skip_at_depth = Some((0, depth_delta));
387             continue;
388         }
389 
390         let new_stack_len = stack.len().wrapping_add(depth_delta as usize);
391         current_frame_base.update(new_stack_len);
392         current_scope_ranges.update(new_stack_len);
393         current_value_range.update(new_stack_len);
394         let range_builder = if entry.tag() == gimli::DW_TAG_subprogram {
395             let range_builder = RangeInfoBuilder::from_subprogram_die(
396                 dwarf, &unit, entry, context, addr_tr, cu_low_pc,
397             )?;
398             if let RangeInfoBuilder::Function(func) = range_builder {
399                 let frame_info = compilation.function_frame_info(module, func);
400                 current_value_range.push(new_stack_len, frame_info);
401                 let (symbol, _) = compilation.function(module, func);
402                 translated.insert(symbol);
403                 current_scope_ranges.push(new_stack_len, range_builder.get_ranges(addr_tr));
404                 Some(range_builder)
405             } else {
406                 // FIXME current_scope_ranges.push()
407                 None
408             }
409         } else {
410             let high_pc = entry.attr_value(gimli::DW_AT_high_pc)?;
411             let ranges = entry.attr_value(gimli::DW_AT_ranges)?;
412             if high_pc.is_some() || ranges.is_some() {
413                 let range_builder =
414                     RangeInfoBuilder::from(dwarf, &unit, entry, context, cu_low_pc)?;
415                 current_scope_ranges.push(new_stack_len, range_builder.get_ranges(addr_tr));
416                 Some(range_builder)
417             } else {
418                 None
419             }
420         };
421 
422         if depth_delta <= 0 {
423             for _ in depth_delta..1 {
424                 stack.pop();
425             }
426         } else {
427             assert_eq!(depth_delta, 1);
428         }
429 
430         if let Some(AttributeValue::Exprloc(expr)) = entry.attr_value(gimli::DW_AT_frame_base)? {
431             if let Some(expr) = compile_expression(&expr, unit.encoding(), None)? {
432                 current_frame_base.push(new_stack_len, expr);
433             }
434         }
435 
436         let parent = stack.last().unwrap();
437 
438         if entry.tag() == gimli::DW_TAG_pointer_type || entry.tag() == gimli::DW_TAG_reference_type
439         {
440             // Wrap pointer types.
441             let pointer_kind = match entry.tag() {
442                 gimli::DW_TAG_pointer_type => WebAssemblyPtrKind::Pointer,
443                 gimli::DW_TAG_reference_type => WebAssemblyPtrKind::Reference,
444                 _ => panic!(),
445             };
446             let die_id = replace_pointer_type(
447                 *parent,
448                 pointer_kind,
449                 comp_unit,
450                 wp_die_id,
451                 entry,
452                 &unit,
453                 context,
454                 out_strings,
455                 &mut pending_die_refs,
456             )?;
457             stack.push(die_id);
458             assert_eq!(stack.len(), new_stack_len);
459             die_ref_map.insert(entry.offset(), die_id);
460             continue;
461         }
462 
463         let die_id = comp_unit.add(*parent, entry.tag());
464 
465         stack.push(die_id);
466         assert_eq!(stack.len(), new_stack_len);
467         die_ref_map.insert(entry.offset(), die_id);
468 
469         clone_die_attributes(
470             split_dwarf.unwrap_or(dwarf),
471             &unit,
472             entry,
473             context,
474             addr_tr,
475             current_value_range.top(),
476             &mut comp_unit,
477             die_id,
478             range_builder,
479             current_scope_ranges.top(),
480             cu_low_pc,
481             out_strings,
482             &mut pending_die_refs,
483             &mut pending_di_refs,
484             FileAttributeContext::Children {
485                 file_map: &file_map,
486                 file_index_base,
487                 frame_base: current_frame_base.top(),
488             },
489             isa,
490         )?;
491 
492         // Data in WebAssembly memory always uses little-endian byte order.
493         // If the native architecture is big-endian, we need to mark all
494         // base types used to refer to WebAssembly memory as little-endian
495         // using the DW_AT_endianity attribute, so that the debugger will
496         // be able to correctly access them.
497         if entry.tag() == gimli::DW_TAG_base_type && isa.endianness() == Endianness::Big {
498             let current_scope = comp_unit.get_mut(die_id);
499             current_scope.set(
500                 gimli::DW_AT_endianity,
501                 write::AttributeValue::Endianity(gimli::DW_END_little),
502             );
503         }
504 
505         if entry.tag() == gimli::DW_TAG_subprogram && !current_scope_ranges.is_empty() {
506             append_vmctx_info(
507                 comp_unit,
508                 die_id,
509                 vmctx_die_id,
510                 addr_tr,
511                 current_value_range.top(),
512                 current_scope_ranges.top().context("range")?,
513                 out_strings,
514                 isa,
515             )?;
516         }
517     }
518     die_ref_map.patch(pending_die_refs, comp_unit);
519     Ok(Some((unit_id, die_ref_map, pending_di_refs)))
520 }
521