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, Dwarf, 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     dwarf: &Dwarf<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                     dwarf.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, dwarf)?));
67                 }
68                 gimli::DW_TAG_pointer_type => {
69                     return Ok(format!("{}*", get_base_type_name(die, unit, dwarf)?));
70                 }
71                 gimli::DW_TAG_reference_type => {
72                     return Ok(format!("{}&", get_base_type_name(die, unit, dwarf)?));
73                 }
74                 gimli::DW_TAG_array_type => {
75                     return Ok(format!("{}[]", get_base_type_name(die, unit, dwarf)?));
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     dwarf: &Dwarf<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, dwarf)?
135         ),
136         WebAssemblyPtrKind::Reference => format!(
137             "WebAssemblyRefWrapper<{}>",
138             get_base_type_name(pointer_type_entry, unit, dwarf)?
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     skeleton_unit: &Unit<Reader<'_>, usize>,
254     split_unit: Option<&Unit<Reader<'_>, usize>>,
255     split_dwarf: Option<&Dwarf<Reader<'_>>>,
256     context: &DebugInputContext,
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 skeleton_die = None;
270 
271     // Get entries in outer scope to avoid borrowing on short lived temporary.
272     let mut skeleton_entries = skeleton_unit.entries();
273     if split_unit.is_some() {
274         // 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).
275         if let Some(die_tuple) = skeleton_entries.next_dfs()? {
276             skeleton_die = Some(die_tuple.1);
277         }
278     }
279 
280     let skeleton_dwarf = &compilation.translations[module].debuginfo.dwarf;
281     let memory_offset = &compilation.module_memory_offsets[module];
282 
283     // Iterate over all of this compilation unit's entries.
284     let dwarf = split_dwarf.unwrap_or(skeleton_dwarf);
285     let unit = split_unit.unwrap_or(skeleton_unit);
286     let mut entries = unit.entries();
287     let (mut comp_unit, unit_id, file_map, file_index_base, cu_low_pc, wp_die_id, vmctx_die_id) =
288         if let Some((depth_delta, entry)) = entries.next_dfs()? {
289             assert_eq!(depth_delta, 0);
290             let (out_line_program, debug_line_offset, file_map, file_index_base) =
291                 clone_line_program(
292                     dwarf,
293                     skeleton_dwarf,
294                     unit,
295                     entry,
296                     skeleton_die,
297                     addr_tr,
298                     out_encoding,
299                     &skeleton_dwarf.debug_str,
300                     &skeleton_dwarf.debug_line,
301                     out_strings,
302                 )?;
303 
304             if entry.tag() == gimli::DW_TAG_compile_unit {
305                 let unit_id = out_units.add(write::Unit::new(out_encoding, out_line_program));
306                 let comp_unit = out_units.get_mut(unit_id);
307 
308                 let root_id = comp_unit.root();
309                 die_ref_map.insert(entry.offset(), root_id);
310                 let cu_low_pc = unit.low_pc;
311 
312                 clone_die_attributes(
313                     dwarf,
314                     &unit,
315                     entry,
316                     addr_tr,
317                     None,
318                     comp_unit,
319                     root_id,
320                     None,
321                     None,
322                     cu_low_pc,
323                     out_strings,
324                     &mut pending_die_refs,
325                     &mut pending_di_refs,
326                     FileAttributeContext::Root(Some(debug_line_offset)),
327                     isa,
328                 )?;
329 
330                 let (wp_die_id, vmctx_die_id) =
331                     add_internal_types(comp_unit, root_id, out_strings, memory_offset);
332 
333                 stack.push(root_id);
334                 (
335                     comp_unit,
336                     unit_id,
337                     file_map,
338                     file_index_base,
339                     cu_low_pc,
340                     wp_die_id,
341                     vmctx_die_id,
342                 )
343             } else {
344                 // Can happen when the DWARF is split and we dont have the package/dwo files.
345                 // This is a better user experience than errorring.
346                 return Ok(None); // empty:
347             }
348         } else {
349             return Ok(None); // empty
350         };
351     let mut skip_at_depth = None;
352     let mut current_frame_base = InheritedAttr::new();
353     let mut current_value_range = InheritedAttr::new();
354     let mut current_scope_ranges = InheritedAttr::new();
355     while let Some((depth_delta, entry)) = entries.next_dfs()? {
356         // If `skip_at_depth` is `Some` then we previously decided to skip over
357         // a node and all it's children. Let A be the last node processed, B be
358         // the first node skipped, C be previous node, and D the current node.
359         // Then `cached` is the difference from A to B, `depth` is the difference
360         // from B to C, and `depth_delta` is the differenc from C to D.
361         let depth_delta = if let Some((depth, cached)) = skip_at_depth {
362             // `new_depth` = B to D
363             let new_depth = depth + depth_delta;
364             // if D is below B continue to skip
365             if new_depth > 0 {
366                 skip_at_depth = Some((new_depth, cached));
367                 continue;
368             }
369             // otherwise process D with `depth_delta` being the difference from A to D
370             skip_at_depth = None;
371             new_depth + cached
372         } else {
373             depth_delta
374         };
375 
376         if !context
377             .reachable
378             .contains(&entry.offset().to_unit_section_offset(&unit))
379             || is_dead_code(&entry)
380         {
381             // entry is not reachable: discarding all its info.
382             // Here B = C so `depth` is 0. A is the previous node so `cached` =
383             // `depth_delta`.
384             skip_at_depth = Some((0, depth_delta));
385             continue;
386         }
387 
388         let new_stack_len = stack.len().wrapping_add(depth_delta as usize);
389         current_frame_base.update(new_stack_len);
390         current_scope_ranges.update(new_stack_len);
391         current_value_range.update(new_stack_len);
392         let range_builder = if entry.tag() == gimli::DW_TAG_subprogram {
393             let range_builder =
394                 RangeInfoBuilder::from_subprogram_die(dwarf, &unit, entry, addr_tr, cu_low_pc)?;
395             if let RangeInfoBuilder::Function(func) = range_builder {
396                 let frame_info = compilation.function_frame_info(module, func);
397                 current_value_range.push(new_stack_len, frame_info);
398                 let (symbol, _) = compilation.function(module, func);
399                 translated.insert(symbol);
400                 current_scope_ranges.push(new_stack_len, range_builder.get_ranges(addr_tr));
401                 Some(range_builder)
402             } else {
403                 // FIXME current_scope_ranges.push()
404                 None
405             }
406         } else {
407             let high_pc = entry.attr_value(gimli::DW_AT_high_pc)?;
408             let ranges = entry.attr_value(gimli::DW_AT_ranges)?;
409             if high_pc.is_some() || ranges.is_some() {
410                 let range_builder = RangeInfoBuilder::from(dwarf, &unit, entry, cu_low_pc)?;
411                 current_scope_ranges.push(new_stack_len, range_builder.get_ranges(addr_tr));
412                 Some(range_builder)
413             } else {
414                 None
415             }
416         };
417 
418         if depth_delta <= 0 {
419             for _ in depth_delta..1 {
420                 stack.pop();
421             }
422         } else {
423             assert_eq!(depth_delta, 1);
424         }
425 
426         if let Some(AttributeValue::Exprloc(expr)) = entry.attr_value(gimli::DW_AT_frame_base)? {
427             if let Some(expr) = compile_expression(&expr, unit.encoding(), None)? {
428                 current_frame_base.push(new_stack_len, expr);
429             }
430         }
431 
432         let parent = stack.last().unwrap();
433 
434         if entry.tag() == gimli::DW_TAG_pointer_type || entry.tag() == gimli::DW_TAG_reference_type
435         {
436             // Wrap pointer types.
437             let pointer_kind = match entry.tag() {
438                 gimli::DW_TAG_pointer_type => WebAssemblyPtrKind::Pointer,
439                 gimli::DW_TAG_reference_type => WebAssemblyPtrKind::Reference,
440                 _ => panic!(),
441             };
442             let die_id = replace_pointer_type(
443                 *parent,
444                 pointer_kind,
445                 comp_unit,
446                 wp_die_id,
447                 entry,
448                 unit,
449                 dwarf,
450                 out_strings,
451                 &mut pending_die_refs,
452             )?;
453             stack.push(die_id);
454             assert_eq!(stack.len(), new_stack_len);
455             die_ref_map.insert(entry.offset(), die_id);
456             continue;
457         }
458 
459         let die_id = comp_unit.add(*parent, entry.tag());
460 
461         stack.push(die_id);
462         assert_eq!(stack.len(), new_stack_len);
463         die_ref_map.insert(entry.offset(), die_id);
464 
465         clone_die_attributes(
466             dwarf,
467             &unit,
468             entry,
469             addr_tr,
470             current_value_range.top(),
471             &mut comp_unit,
472             die_id,
473             range_builder,
474             current_scope_ranges.top(),
475             cu_low_pc,
476             out_strings,
477             &mut pending_die_refs,
478             &mut pending_di_refs,
479             FileAttributeContext::Children {
480                 file_map: &file_map,
481                 file_index_base,
482                 frame_base: current_frame_base.top(),
483             },
484             isa,
485         )?;
486 
487         // Data in WebAssembly memory always uses little-endian byte order.
488         // If the native architecture is big-endian, we need to mark all
489         // base types used to refer to WebAssembly memory as little-endian
490         // using the DW_AT_endianity attribute, so that the debugger will
491         // be able to correctly access them.
492         if entry.tag() == gimli::DW_TAG_base_type && isa.endianness() == Endianness::Big {
493             let current_scope = comp_unit.get_mut(die_id);
494             current_scope.set(
495                 gimli::DW_AT_endianity,
496                 write::AttributeValue::Endianity(gimli::DW_END_little),
497             );
498         }
499 
500         if entry.tag() == gimli::DW_TAG_subprogram && !current_scope_ranges.is_empty() {
501             append_vmctx_info(
502                 comp_unit,
503                 die_id,
504                 vmctx_die_id,
505                 addr_tr,
506                 current_value_range.top(),
507                 current_scope_ranges.top().context("range")?,
508                 out_strings,
509                 isa,
510             )?;
511         }
512     }
513     die_ref_map.patch(pending_die_refs, comp_unit);
514     Ok(Some((unit_id, die_ref_map, pending_di_refs)))
515 }
516