1 use super::address_transform::AddressTransform;
2 use super::attr::{clone_die_attributes, FileAttributeContext};
3 use super::debug_transform_logging::{
4     dbi_log, log_begin_input_die, log_end_output_die, log_end_output_die_skipped,
5     log_get_cu_summary,
6 };
7 use super::expression::compile_expression;
8 use super::line_program::clone_line_program;
9 use super::range_info_builder::RangeInfoBuilder;
10 use super::refs::{PendingDebugInfoRefs, PendingUnitRefs, UnitRefsMap};
11 use super::utils::{add_internal_types, append_vmctx_info};
12 use super::DebugInputContext;
13 use crate::debug::{Compilation, Reader};
14 use anyhow::{Context, Error};
15 use cranelift_codegen::ir::Endianness;
16 use cranelift_codegen::isa::TargetIsa;
17 use gimli::write;
18 use gimli::{AttributeValue, DebuggingInformationEntry, Dwarf, Unit};
19 use std::collections::HashSet;
20 use wasmtime_environ::StaticModuleIndex;
21 use wasmtime_versioned_export_macros::versioned_stringify_ident;
22 
23 struct InheritedAttr<T> {
24     stack: Vec<(usize, T)>,
25 }
26 
27 impl<T> InheritedAttr<T> {
28     fn new() -> Self {
29         InheritedAttr { stack: Vec::new() }
30     }
31 
32     fn update(&mut self, depth: usize) {
33         while !self.stack.is_empty() && self.stack.last().unwrap().0 >= depth {
34             self.stack.pop();
35         }
36     }
37 
38     fn push(&mut self, depth: usize, value: T) {
39         self.stack.push((depth, value));
40     }
41 
42     fn top(&self) -> Option<&T> {
43         self.stack.last().map(|entry| &entry.1)
44     }
45 
46     fn is_empty(&self) -> bool {
47         self.stack.is_empty()
48     }
49 }
50 
51 fn get_base_type_name(
52     type_entry: &DebuggingInformationEntry<Reader<'_>>,
53     unit: &Unit<Reader<'_>, usize>,
54     dwarf: &Dwarf<Reader<'_>>,
55 ) -> Result<String, Error> {
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(value) = die.attr_value(gimli::DW_AT_name)? {
62                 return Ok(String::from(dwarf.attr_string(unit, value)?.to_string()?));
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 skeleton_dwarf = &compilation.translations[module].debuginfo.dwarf;
270     let memory_offset = &compilation.module_memory_offsets[module];
271 
272     // Iterate over all of this compilation unit's entries.
273     let dwarf = split_dwarf.unwrap_or(skeleton_dwarf);
274     let unit = split_unit.unwrap_or(skeleton_unit);
275     let mut entries = unit.entries();
276     dbi_log!("Cloning CU {:?}", log_get_cu_summary(unit));
277 
278     let (mut out_unit, out_unit_id, file_map, file_index_base, wp_die_id, vmctx_die_id) =
279         if let Some((depth_delta, entry)) = entries.next_dfs()? {
280             assert_eq!(depth_delta, 0);
281             let (out_line_program, debug_line_offset, file_map, file_index_base) =
282                 clone_line_program(
283                     skeleton_dwarf,
284                     skeleton_unit,
285                     unit.name,
286                     addr_tr,
287                     out_encoding,
288                     out_strings,
289                 )?;
290 
291             if entry.tag() == gimli::DW_TAG_compile_unit {
292                 log_begin_input_die(dwarf, unit, entry, 0);
293                 let out_unit_id = out_units.add(write::Unit::new(out_encoding, out_line_program));
294                 let out_unit = out_units.get_mut(out_unit_id);
295 
296                 let out_root_id = out_unit.root();
297                 die_ref_map.insert(entry.offset(), out_root_id);
298 
299                 clone_die_attributes(
300                     dwarf,
301                     &unit,
302                     entry,
303                     addr_tr,
304                     None,
305                     out_unit,
306                     out_root_id,
307                     None,
308                     None,
309                     out_strings,
310                     &mut pending_die_refs,
311                     &mut pending_di_refs,
312                     FileAttributeContext::Root(Some(debug_line_offset)),
313                     isa,
314                 )?;
315                 if split_unit.is_some() {
316                     if let Some((_, skeleton_entry)) = skeleton_unit.entries().next_dfs()? {
317                         clone_die_attributes(
318                             skeleton_dwarf,
319                             skeleton_unit,
320                             skeleton_entry,
321                             addr_tr,
322                             None,
323                             out_unit,
324                             out_root_id,
325                             None,
326                             None,
327                             out_strings,
328                             &mut pending_die_refs,
329                             &mut pending_di_refs,
330                             FileAttributeContext::Root(Some(debug_line_offset)),
331                             isa,
332                         )?;
333                     }
334                 }
335 
336                 let (wp_die_id, vmctx_die_id) =
337                     add_internal_types(out_unit, out_root_id, out_strings, memory_offset);
338 
339                 log_end_output_die(entry, unit, out_root_id, out_unit, out_strings, 0);
340                 stack.push(out_root_id);
341                 (
342                     out_unit,
343                     out_unit_id,
344                     file_map,
345                     file_index_base,
346                     wp_die_id,
347                     vmctx_die_id,
348                 )
349             } else {
350                 // Can happen when the DWARF is split and we dont have the package/dwo files.
351                 // This is a better user experience than errorring.
352                 dbi_log!("... skipped: split DW_TAG_compile_unit entry missing");
353                 return Ok(None); // empty:
354             }
355         } else {
356             dbi_log!("... skipped: empty CU (no DW_TAG_compile_unit entry)");
357             return Ok(None); // empty
358         };
359     let mut current_depth = 0;
360     let mut skip_at_depth = None;
361     let mut current_frame_base = InheritedAttr::new();
362     let mut current_value_range = InheritedAttr::new();
363     let mut current_scope_ranges = InheritedAttr::new();
364     while let Some((depth_delta, entry)) = entries.next_dfs()? {
365         current_depth += depth_delta;
366         log_begin_input_die(dwarf, unit, entry, current_depth);
367 
368         // If `skip_at_depth` is `Some` then we previously decided to skip over
369         // a node and all it's children. Let A be the last node processed, B be
370         // the first node skipped, C be previous node, and D the current node.
371         // Then `cached` is the difference from A to B, `depth` is the difference
372         // from B to C, and `depth_delta` is the differenc from C to D.
373         let depth_delta = if let Some((depth, cached)) = skip_at_depth {
374             // `new_depth` = B to D
375             let new_depth = depth + depth_delta;
376             // if D is below B continue to skip
377             if new_depth > 0 {
378                 skip_at_depth = Some((new_depth, cached));
379                 log_end_output_die_skipped(entry, unit, "unreachable", current_depth);
380                 continue;
381             }
382             // otherwise process D with `depth_delta` being the difference from A to D
383             skip_at_depth = None;
384             new_depth + cached
385         } else {
386             depth_delta
387         };
388 
389         if !context
390             .reachable
391             .contains(&entry.offset().to_unit_section_offset(&unit))
392             || is_dead_code(&entry)
393         {
394             // entry is not reachable: discarding all its info.
395             // Here B = C so `depth` is 0. A is the previous node so `cached` =
396             // `depth_delta`.
397             skip_at_depth = Some((0, depth_delta));
398             log_end_output_die_skipped(entry, unit, "unreachable", current_depth);
399             continue;
400         }
401 
402         let new_stack_len = stack.len().wrapping_add(depth_delta as usize);
403         current_frame_base.update(new_stack_len);
404         current_scope_ranges.update(new_stack_len);
405         current_value_range.update(new_stack_len);
406         let range_builder = if entry.tag() == gimli::DW_TAG_subprogram {
407             let range_builder =
408                 RangeInfoBuilder::from_subprogram_die(dwarf, &unit, entry, addr_tr)?;
409             if let RangeInfoBuilder::Function(func) = range_builder {
410                 let frame_info = compilation.function_frame_info(module, func);
411                 current_value_range.push(new_stack_len, frame_info);
412                 let (symbol, _) = compilation.function(module, func);
413                 translated.insert(symbol);
414                 current_scope_ranges.push(new_stack_len, range_builder.get_ranges(addr_tr));
415                 Some(range_builder)
416             } else {
417                 // FIXME current_scope_ranges.push()
418                 None
419             }
420         } else {
421             let high_pc = entry.attr_value(gimli::DW_AT_high_pc)?;
422             let ranges = entry.attr_value(gimli::DW_AT_ranges)?;
423             if high_pc.is_some() || ranges.is_some() {
424                 let range_builder = RangeInfoBuilder::from(dwarf, &unit, entry)?;
425                 current_scope_ranges.push(new_stack_len, range_builder.get_ranges(addr_tr));
426                 Some(range_builder)
427             } else {
428                 None
429             }
430         };
431 
432         if depth_delta <= 0 {
433             for _ in depth_delta..1 {
434                 stack.pop();
435             }
436         } else {
437             assert_eq!(depth_delta, 1);
438         }
439 
440         if let Some(AttributeValue::Exprloc(expr)) = entry.attr_value(gimli::DW_AT_frame_base)? {
441             if let Some(expr) = compile_expression(&expr, unit.encoding(), None)? {
442                 current_frame_base.push(new_stack_len, expr);
443             }
444         }
445 
446         let parent = stack.last().unwrap();
447 
448         if entry.tag() == gimli::DW_TAG_pointer_type || entry.tag() == gimli::DW_TAG_reference_type
449         {
450             // Wrap pointer types.
451             let pointer_kind = match entry.tag() {
452                 gimli::DW_TAG_pointer_type => WebAssemblyPtrKind::Pointer,
453                 gimli::DW_TAG_reference_type => WebAssemblyPtrKind::Reference,
454                 _ => panic!(),
455             };
456             let die_id = replace_pointer_type(
457                 *parent,
458                 pointer_kind,
459                 out_unit,
460                 wp_die_id,
461                 entry,
462                 unit,
463                 dwarf,
464                 out_strings,
465                 &mut pending_die_refs,
466             )?;
467             stack.push(die_id);
468             assert_eq!(stack.len(), new_stack_len);
469             die_ref_map.insert(entry.offset(), die_id);
470             log_end_output_die(entry, unit, die_id, out_unit, out_strings, current_depth);
471             continue;
472         }
473 
474         let out_die_id = out_unit.add(*parent, entry.tag());
475 
476         stack.push(out_die_id);
477         assert_eq!(stack.len(), new_stack_len);
478         die_ref_map.insert(entry.offset(), out_die_id);
479 
480         clone_die_attributes(
481             dwarf,
482             &unit,
483             entry,
484             addr_tr,
485             current_value_range.top(),
486             &mut out_unit,
487             out_die_id,
488             range_builder,
489             current_scope_ranges.top(),
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 = out_unit.get_mut(out_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                 out_unit,
517                 out_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         log_end_output_die(
528             entry,
529             unit,
530             out_die_id,
531             out_unit,
532             out_strings,
533             current_depth,
534         );
535     }
536     die_ref_map.patch(pending_die_refs, out_unit);
537     Ok(Some((out_unit_id, die_ref_map, pending_di_refs)))
538 }
539