wasmtime_cranelift/debug/transform/
unit.rs

1use super::address_transform::AddressTransform;
2use super::attr::{clone_die_attributes, FileAttributeContext};
3use super::expression::compile_expression;
4use super::line_program::clone_line_program;
5use super::range_info_builder::RangeInfoBuilder;
6use super::refs::{PendingDebugInfoRefs, PendingUnitRefs, UnitRefsMap};
7use super::utils::{add_internal_types, append_vmctx_info, get_function_frame_info};
8use super::{DebugInputContext, Reader, TransformError};
9use crate::debug::ModuleMemoryOffset;
10use crate::CompiledFunctions;
11use anyhow::{Context, Error};
12use cranelift_codegen::ir::Endianness;
13use cranelift_codegen::isa::TargetIsa;
14use gimli::write;
15use gimli::{AttributeValue, DebuggingInformationEntry, Unit};
16use std::collections::HashSet;
17use wasmtime_environ::DefinedFuncIndex;
18
19struct InheritedAttr<T> {
20    stack: Vec<(usize, T)>,
21}
22
23impl<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
47fn get_base_type_name<R>(
48    type_entry: &DebuggingInformationEntry<R>,
49    unit: &Unit<R, R::Offset>,
50    context: &DebugInputContext<R>,
51) -> Result<String, Error>
52where
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
87enum 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.
109fn 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>
120where
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
247fn 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
256pub(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>
269where
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}