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