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, TransformError};
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     context: &DebugInputContext<R>,
261     addr_tr: &'a AddressTransform,
262     funcs: &'a CompiledFunctionsMetadata,
263     memory_offset: &ModuleMemoryOffset,
264     out_encoding: gimli::Encoding,
265     out_units: &mut write::UnitTable,
266     out_strings: &mut write::StringTable,
267     translated: &mut HashSet<DefinedFuncIndex>,
268     isa: &dyn TargetIsa,
269 ) -> Result<Option<(write::UnitId, UnitRefsMap, PendingDebugInfoRefs)>, Error>
270 where
271     R: Reader,
272 {
273     let mut die_ref_map = UnitRefsMap::new();
274     let mut pending_die_refs = PendingUnitRefs::new();
275     let mut pending_di_refs = PendingDebugInfoRefs::new();
276     let mut stack = Vec::new();
277 
278     // Iterate over all of this compilation unit's entries.
279     let mut entries = unit.entries();
280     let (mut comp_unit, unit_id, file_map, file_index_base, cu_low_pc, wp_die_id, vmctx_die_id) =
281         if let Some((depth_delta, entry)) = entries.next_dfs()? {
282             assert_eq!(depth_delta, 0);
283             let (out_line_program, debug_line_offset, file_map, file_index_base) =
284                 clone_line_program(
285                     &unit,
286                     entry,
287                     addr_tr,
288                     out_encoding,
289                     context.debug_str,
290                     context.debug_str_offsets,
291                     context.debug_line_str,
292                     context.debug_line,
293                     out_strings,
294                 )?;
295 
296             if entry.tag() == gimli::DW_TAG_compile_unit {
297                 let unit_id = out_units.add(write::Unit::new(out_encoding, out_line_program));
298                 let comp_unit = out_units.get_mut(unit_id);
299 
300                 let root_id = comp_unit.root();
301                 die_ref_map.insert(entry.offset(), root_id);
302 
303                 let cu_low_pc = if let Some(AttributeValue::Addr(addr)) =
304                     entry.attr_value(gimli::DW_AT_low_pc)?
305                 {
306                     addr
307                 } else if let Some(AttributeValue::DebugAddrIndex(i)) =
308                     entry.attr_value(gimli::DW_AT_low_pc)?
309                 {
310                     context.debug_addr.get_address(4, unit.addr_base, i)?
311                 } else {
312                     // FIXME? return Err(TransformError("No low_pc for unit header").into());
313                     0
314                 };
315 
316                 clone_die_attributes(
317                     dwarf,
318                     &unit,
319                     entry,
320                     context,
321                     addr_tr,
322                     None,
323                     comp_unit,
324                     root_id,
325                     None,
326                     None,
327                     cu_low_pc,
328                     out_strings,
329                     &mut pending_die_refs,
330                     &mut pending_di_refs,
331                     FileAttributeContext::Root(Some(debug_line_offset)),
332                     isa,
333                 )?;
334 
335                 let (wp_die_id, vmctx_die_id) =
336                     add_internal_types(comp_unit, root_id, out_strings, memory_offset);
337 
338                 stack.push(root_id);
339                 (
340                     comp_unit,
341                     unit_id,
342                     file_map,
343                     file_index_base,
344                     cu_low_pc,
345                     wp_die_id,
346                     vmctx_die_id,
347                 )
348             } else {
349                 return Err(TransformError("Unexpected unit header").into());
350             }
351         } else {
352             return Ok(None); // empty
353         };
354     let mut skip_at_depth = None;
355     let mut current_frame_base = InheritedAttr::new();
356     let mut current_value_range = InheritedAttr::new();
357     let mut current_scope_ranges = InheritedAttr::new();
358     while let Some((depth_delta, entry)) = entries.next_dfs()? {
359         // If `skip_at_depth` is `Some` then we previously decided to skip over
360         // a node and all it's children. Let A be the last node processed, B be
361         // the first node skipped, C be previous node, and D the current node.
362         // Then `cached` is the difference from A to B, `depth` is the diffence
363         // from B to C, and `depth_delta` is the differenc from C to D.
364         let depth_delta = if let Some((depth, cached)) = skip_at_depth {
365             // `new_depth` = B to D
366             let new_depth = depth + depth_delta;
367             // if D is below B continue to skip
368             if new_depth > 0 {
369                 skip_at_depth = Some((new_depth, cached));
370                 continue;
371             }
372             // otherwise process D with `depth_delta` being the difference from A to D
373             skip_at_depth = None;
374             new_depth + cached
375         } else {
376             depth_delta
377         };
378 
379         if !context
380             .reachable
381             .contains(&entry.offset().to_unit_section_offset(&unit))
382             || is_dead_code(&entry)
383         {
384             // entry is not reachable: discarding all its info.
385             // Here B = C so `depth` is 0. A is the previous node so `cached` =
386             // `depth_delta`.
387             skip_at_depth = Some((0, depth_delta));
388             continue;
389         }
390 
391         let new_stack_len = stack.len().wrapping_add(depth_delta as usize);
392         current_frame_base.update(new_stack_len);
393         current_scope_ranges.update(new_stack_len);
394         current_value_range.update(new_stack_len);
395         let range_builder = if entry.tag() == gimli::DW_TAG_subprogram {
396             let range_builder = RangeInfoBuilder::from_subprogram_die(
397                 dwarf, &unit, entry, context, addr_tr, cu_low_pc,
398             )?;
399             if let RangeInfoBuilder::Function(func_index) = range_builder {
400                 if let Some(frame_info) = get_function_frame_info(memory_offset, funcs, func_index)
401                 {
402                     current_value_range.push(new_stack_len, frame_info);
403                 }
404                 translated.insert(func_index);
405                 current_scope_ranges.push(new_stack_len, range_builder.get_ranges(addr_tr));
406                 Some(range_builder)
407             } else {
408                 // FIXME current_scope_ranges.push()
409                 None
410             }
411         } else {
412             let high_pc = entry.attr_value(gimli::DW_AT_high_pc)?;
413             let ranges = entry.attr_value(gimli::DW_AT_ranges)?;
414             if high_pc.is_some() || ranges.is_some() {
415                 let range_builder =
416                     RangeInfoBuilder::from(dwarf, &unit, entry, context, cu_low_pc)?;
417                 current_scope_ranges.push(new_stack_len, range_builder.get_ranges(addr_tr));
418                 Some(range_builder)
419             } else {
420                 None
421             }
422         };
423 
424         if depth_delta <= 0 {
425             for _ in depth_delta..1 {
426                 stack.pop();
427             }
428         } else {
429             assert_eq!(depth_delta, 1);
430         }
431 
432         if let Some(AttributeValue::Exprloc(expr)) = entry.attr_value(gimli::DW_AT_frame_base)? {
433             if let Some(expr) = compile_expression(&expr, unit.encoding(), None)? {
434                 current_frame_base.push(new_stack_len, expr);
435             }
436         }
437 
438         let parent = stack.last().unwrap();
439 
440         if entry.tag() == gimli::DW_TAG_pointer_type || entry.tag() == gimli::DW_TAG_reference_type
441         {
442             // Wrap pointer types.
443             let pointer_kind = match entry.tag() {
444                 gimli::DW_TAG_pointer_type => WebAssemblyPtrKind::Pointer,
445                 gimli::DW_TAG_reference_type => WebAssemblyPtrKind::Reference,
446                 _ => panic!(),
447             };
448             let die_id = replace_pointer_type(
449                 *parent,
450                 pointer_kind,
451                 comp_unit,
452                 wp_die_id,
453                 entry,
454                 &unit,
455                 context,
456                 out_strings,
457                 &mut pending_die_refs,
458             )?;
459             stack.push(die_id);
460             assert_eq!(stack.len(), new_stack_len);
461             die_ref_map.insert(entry.offset(), die_id);
462             continue;
463         }
464 
465         let die_id = comp_unit.add(*parent, entry.tag());
466 
467         stack.push(die_id);
468         assert_eq!(stack.len(), new_stack_len);
469         die_ref_map.insert(entry.offset(), die_id);
470 
471         clone_die_attributes(
472             dwarf,
473             &unit,
474             entry,
475             context,
476             addr_tr,
477             current_value_range.top(),
478             &mut comp_unit,
479             die_id,
480             range_builder,
481             current_scope_ranges.top(),
482             cu_low_pc,
483             out_strings,
484             &mut pending_die_refs,
485             &mut pending_di_refs,
486             FileAttributeContext::Children {
487                 file_map: &file_map,
488                 file_index_base,
489                 frame_base: current_frame_base.top(),
490             },
491             isa,
492         )?;
493 
494         // Data in WebAssembly memory always uses little-endian byte order.
495         // If the native architecture is big-endian, we need to mark all
496         // base types used to refer to WebAssembly memory as little-endian
497         // using the DW_AT_endianity attribute, so that the debugger will
498         // be able to correctly access them.
499         if entry.tag() == gimli::DW_TAG_base_type && isa.endianness() == Endianness::Big {
500             let current_scope = comp_unit.get_mut(die_id);
501             current_scope.set(
502                 gimli::DW_AT_endianity,
503                 write::AttributeValue::Endianity(gimli::DW_END_little),
504             );
505         }
506 
507         if entry.tag() == gimli::DW_TAG_subprogram && !current_scope_ranges.is_empty() {
508             append_vmctx_info(
509                 comp_unit,
510                 die_id,
511                 vmctx_die_id,
512                 addr_tr,
513                 current_value_range.top(),
514                 current_scope_ranges.top().context("range")?,
515                 out_strings,
516                 isa,
517             )?;
518         }
519     }
520     die_ref_map.patch(pending_die_refs, comp_unit);
521     Ok(Some((unit_id, die_ref_map, pending_di_refs)))
522 }
523