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