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