wasmtime_cranelift/debug/transform/
unit.rs

1use super::address_transform::AddressTransform;
2use super::attr::{clone_die_attributes, EntryAttributesContext};
3use 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};
7use super::expression::compile_expression;
8use super::line_program::clone_line_program;
9use super::range_info_builder::RangeInfoBuilder;
10use super::refs::{PendingDebugInfoRefs, PendingUnitRefs, UnitRefsMap};
11use super::synthetic::ModuleSyntheticUnit;
12use super::utils::{append_vmctx_info, resolve_die_ref};
13use super::DebugInputContext;
14use crate::debug::{Compilation, Reader};
15use anyhow::{Context, Error};
16use cranelift_codegen::ir::Endianness;
17use cranelift_codegen::isa::TargetIsa;
18use gimli::write;
19use gimli::{AttributeValue, DebuggingInformationEntry, Dwarf, Unit};
20use std::collections::HashSet;
21use wasmtime_environ::StaticModuleIndex;
22use wasmtime_versioned_export_macros::versioned_stringify_ident;
23
24#[derive(Debug)]
25pub struct InheritedAttr<T> {
26    stack: Vec<(usize, T)>,
27}
28
29impl<T> InheritedAttr<T> {
30    fn new() -> Self {
31        InheritedAttr { stack: Vec::new() }
32    }
33
34    fn update(&mut self, depth: usize) {
35        while !self.stack.is_empty() && self.stack.last().unwrap().0 >= depth {
36            self.stack.pop();
37        }
38    }
39
40    pub fn push(&mut self, depth: usize, value: T) {
41        self.stack.push((depth, value));
42    }
43
44    pub fn top(&self) -> Option<&T> {
45        self.stack.last().map(|entry| &entry.1)
46    }
47
48    pub fn top_with_depth_mut(&mut self, depth: usize) -> Option<&mut T> {
49        self.stack
50            .last_mut()
51            .filter(|entry| entry.0 == depth)
52            .map(|entry| &mut entry.1)
53    }
54
55    fn is_empty(&self) -> bool {
56        self.stack.is_empty()
57    }
58}
59
60fn get_base_type_name(
61    type_entry: &DebuggingInformationEntry<Reader<'_>>,
62    unit: &Unit<Reader<'_>>,
63    dwarf: &Dwarf<Reader<'_>>,
64) -> Result<String, Error> {
65    // FIXME remove recursion.
66    if let Some(die_ref) = type_entry.attr_value(gimli::DW_AT_type)? {
67        if let Some(ref die) = resolve_die_ref(unit, &die_ref)? {
68            if let Some(value) = die.attr_value(gimli::DW_AT_name)? {
69                return Ok(String::from(dwarf.attr_string(unit, value)?.to_string()?));
70            }
71            match die.tag() {
72                gimli::DW_TAG_const_type => {
73                    return Ok(format!("const {}", get_base_type_name(die, unit, dwarf)?));
74                }
75                gimli::DW_TAG_pointer_type => {
76                    return Ok(format!("{}*", get_base_type_name(die, unit, dwarf)?));
77                }
78                gimli::DW_TAG_reference_type => {
79                    return Ok(format!("{}&", get_base_type_name(die, unit, dwarf)?));
80                }
81                gimli::DW_TAG_array_type => {
82                    return Ok(format!("{}[]", get_base_type_name(die, unit, dwarf)?));
83                }
84                _ => (),
85            }
86        }
87    }
88    Ok(String::from("??"))
89}
90
91enum WebAssemblyPtrKind {
92    Reference,
93    Pointer,
94}
95
96/// Replaces WebAssembly pointer type DIE with the wrapper
97/// which natively represented by offset in a Wasm memory.
98///
99/// `pointer_type_entry` is a DW_TAG_pointer_type entry (e.g. `T*`),
100/// which refers its base type (e.g. `T`), or is a
101/// DW_TAG_reference_type (e.g. `T&`).
102///
103/// The generated wrapper is a structure that contains only the
104/// `__ptr` field. The utility operators overloads is added to
105/// provide better debugging experience.
106///
107/// Wrappers of pointer and reference types are identical except for
108/// their name -- they are formatted and accessed from a debugger
109/// the same way.
110///
111/// Notice that "resolve_vmctx_memory_ptr" is external/builtin
112/// subprogram that is not part of Wasm code.
113fn replace_pointer_type(
114    parent_id: write::UnitEntryId,
115    kind: WebAssemblyPtrKind,
116    comp_unit: &mut write::Unit,
117    wasm_ptr_die_ref: write::Reference,
118    pointer_type_entry: &DebuggingInformationEntry<Reader<'_>>,
119    unit: &Unit<Reader<'_>, usize>,
120    dwarf: &Dwarf<Reader<'_>>,
121    out_strings: &mut write::StringTable,
122    pending_die_refs: &mut PendingUnitRefs,
123) -> Result<write::UnitEntryId, Error> {
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, reason = "sometimes not used below")]
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, dwarf)?
142        ),
143        WebAssemblyPtrKind::Reference => format!(
144            "WebAssemblyRefWrapper<{}>",
145            get_base_type_name(pointer_type_entry, unit, dwarf)?
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::DebugInfoRef(wasm_ptr_die_ref),
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 = "wasmtime_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!(wasmtime_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 = "wasmtime_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!(wasmtime_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 = "wasmtime_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!(wasmtime_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
248fn is_dead_code(entry: &DebuggingInformationEntry<Reader<'_>>) -> 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
257pub(crate) fn clone_unit(
258    compilation: &mut Compilation<'_>,
259    module: StaticModuleIndex,
260    skeleton_unit: &Unit<Reader<'_>>,
261    split_unit: Option<&Unit<Reader<'_>>>,
262    split_dwarf: Option<&Dwarf<Reader<'_>>>,
263    context: &DebugInputContext,
264    addr_tr: &AddressTransform,
265    out_encoding: gimli::Encoding,
266    out_module_synthetic_unit: &ModuleSyntheticUnit,
267    out_units: &mut write::UnitTable,
268    out_strings: &mut write::StringTable,
269    translated: &mut HashSet<usize>,
270    isa: &dyn TargetIsa,
271) -> Result<Option<(write::UnitId, UnitRefsMap, PendingDebugInfoRefs)>, Error> {
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    let skeleton_dwarf = &compilation.translations[module].debuginfo.dwarf;
278
279    // Iterate over all of this compilation unit's entries.
280    let dwarf = split_dwarf.unwrap_or(skeleton_dwarf);
281    let unit = split_unit.unwrap_or(skeleton_unit);
282    let mut entries = unit.entries();
283    dbi_log!("Cloning CU {:?}", log_get_cu_summary(unit));
284
285    let (mut out_unit, out_unit_id, file_map, file_index_base) = if let Some((depth_delta, entry)) =
286        entries.next_dfs()?
287    {
288        assert_eq!(depth_delta, 0);
289        let (out_line_program, debug_line_offset, file_map, file_index_base) = clone_line_program(
290            skeleton_dwarf,
291            skeleton_unit,
292            unit.name,
293            addr_tr,
294            out_encoding,
295            out_strings,
296        )?;
297
298        if entry.tag() == gimli::DW_TAG_compile_unit {
299            log_begin_input_die(dwarf, unit, entry, 0);
300            let out_unit_id = out_units.add(write::Unit::new(out_encoding, out_line_program));
301            let out_unit = out_units.get_mut(out_unit_id);
302
303            let out_root_id = out_unit.root();
304            die_ref_map.insert(entry.offset(), out_root_id);
305
306            clone_die_attributes(
307                dwarf,
308                &unit,
309                entry,
310                addr_tr,
311                None,
312                out_unit,
313                out_root_id,
314                None,
315                None,
316                out_strings,
317                &mut pending_die_refs,
318                &mut pending_di_refs,
319                EntryAttributesContext::Root(Some(debug_line_offset)),
320                isa,
321            )?;
322            if split_unit.is_some() {
323                if let Some((_, skeleton_entry)) = skeleton_unit.entries().next_dfs()? {
324                    clone_die_attributes(
325                        skeleton_dwarf,
326                        skeleton_unit,
327                        skeleton_entry,
328                        addr_tr,
329                        None,
330                        out_unit,
331                        out_root_id,
332                        None,
333                        None,
334                        out_strings,
335                        &mut pending_die_refs,
336                        &mut pending_di_refs,
337                        EntryAttributesContext::Root(Some(debug_line_offset)),
338                        isa,
339                    )?;
340                }
341            }
342
343            log_end_output_die(entry, unit, out_root_id, out_unit, out_strings, 0);
344            stack.push(out_root_id);
345            (out_unit, out_unit_id, file_map, file_index_base)
346        } else {
347            // Can happen when the DWARF is split and we dont have the package/dwo files.
348            // This is a better user experience than errorring.
349            dbi_log!("... skipped: split DW_TAG_compile_unit entry missing");
350            return Ok(None); // empty:
351        }
352    } else {
353        dbi_log!("... skipped: empty CU (no DW_TAG_compile_unit entry)");
354        return Ok(None); // empty
355    };
356    let mut current_depth = 0;
357    let mut skip_at_depth = None;
358    let mut current_frame_base = InheritedAttr::new();
359    let mut current_value_range = InheritedAttr::new();
360    let mut current_scope_ranges = InheritedAttr::new();
361    let mut current_subprogram = InheritedAttr::new();
362    while let Some((depth_delta, entry)) = entries.next_dfs()? {
363        current_depth += depth_delta;
364        log_begin_input_die(dwarf, unit, entry, current_depth);
365
366        // If `skip_at_depth` is `Some` then we previously decided to skip over
367        // a node and all it's children. Let A be the last node processed, B be
368        // the first node skipped, C be previous node, and D the current node.
369        // Then `cached` is the difference from A to B, `depth` is the difference
370        // from B to C, and `depth_delta` is the differenc from C to D.
371        let depth_delta = if let Some((depth, cached)) = skip_at_depth {
372            // `new_depth` = B to D
373            let new_depth = depth + depth_delta;
374            // if D is below B continue to skip
375            if new_depth > 0 {
376                skip_at_depth = Some((new_depth, cached));
377                log_end_output_die_skipped(entry, unit, "unreachable", current_depth);
378                continue;
379            }
380            // otherwise process D with `depth_delta` being the difference from A to D
381            skip_at_depth = None;
382            new_depth + cached
383        } else {
384            depth_delta
385        };
386
387        if !context
388            .reachable
389            .contains(&entry.offset().to_unit_section_offset(&unit))
390            || is_dead_code(&entry)
391        {
392            // entry is not reachable: discarding all its info.
393            // Here B = C so `depth` is 0. A is the previous node so `cached` =
394            // `depth_delta`.
395            skip_at_depth = Some((0, depth_delta));
396            log_end_output_die_skipped(entry, unit, "unreachable", current_depth);
397            continue;
398        }
399
400        let new_stack_len = stack.len().wrapping_add(depth_delta as usize);
401        current_frame_base.update(new_stack_len);
402        current_scope_ranges.update(new_stack_len);
403        current_value_range.update(new_stack_len);
404        current_subprogram.update(new_stack_len);
405        let range_builder = if entry.tag() == gimli::DW_TAG_subprogram {
406            let range_builder =
407                RangeInfoBuilder::from_subprogram_die(dwarf, &unit, entry, addr_tr)?;
408            if let RangeInfoBuilder::Function(func) = range_builder {
409                let frame_info = compilation.function_frame_info(module, func);
410                current_value_range.push(new_stack_len, frame_info);
411                let (symbol, _) = compilation.function(module, func);
412                translated.insert(symbol);
413                current_scope_ranges.push(new_stack_len, range_builder.get_ranges(addr_tr));
414                Some(range_builder)
415            } else {
416                // FIXME current_scope_ranges.push()
417                None
418            }
419        } else {
420            let high_pc = entry.attr_value(gimli::DW_AT_high_pc)?;
421            let ranges = entry.attr_value(gimli::DW_AT_ranges)?;
422            if high_pc.is_some() || ranges.is_some() {
423                let range_builder = RangeInfoBuilder::from(dwarf, &unit, entry)?;
424                current_scope_ranges.push(new_stack_len, range_builder.get_ranges(addr_tr));
425                Some(range_builder)
426            } else {
427                None
428            }
429        };
430
431        if depth_delta <= 0 {
432            for _ in depth_delta..1 {
433                stack.pop();
434            }
435        } else {
436            assert_eq!(depth_delta, 1);
437        }
438
439        if let Some(AttributeValue::Exprloc(expr)) = entry.attr_value(gimli::DW_AT_frame_base)? {
440            if let Some(expr) = compile_expression(&expr, unit.encoding(), None)? {
441                current_frame_base.push(new_stack_len, expr);
442            }
443        }
444
445        let parent = stack.last().unwrap();
446
447        if entry.tag() == gimli::DW_TAG_pointer_type || entry.tag() == gimli::DW_TAG_reference_type
448        {
449            // Wrap pointer types.
450            let pointer_kind = match entry.tag() {
451                gimli::DW_TAG_pointer_type => WebAssemblyPtrKind::Pointer,
452                gimli::DW_TAG_reference_type => WebAssemblyPtrKind::Reference,
453                _ => panic!(),
454            };
455            let die_id = replace_pointer_type(
456                *parent,
457                pointer_kind,
458                out_unit,
459                out_module_synthetic_unit.wasm_ptr_die_ref(),
460                entry,
461                unit,
462                dwarf,
463                out_strings,
464                &mut pending_die_refs,
465            )?;
466            stack.push(die_id);
467            assert_eq!(stack.len(), new_stack_len);
468            die_ref_map.insert(entry.offset(), die_id);
469            log_end_output_die(entry, unit, die_id, out_unit, out_strings, current_depth);
470            continue;
471        }
472
473        let out_die_id = out_unit.add(*parent, entry.tag());
474
475        stack.push(out_die_id);
476        assert_eq!(stack.len(), new_stack_len);
477        die_ref_map.insert(entry.offset(), out_die_id);
478
479        clone_die_attributes(
480            dwarf,
481            &unit,
482            entry,
483            addr_tr,
484            current_value_range.top(),
485            &mut out_unit,
486            out_die_id,
487            range_builder,
488            current_scope_ranges.top(),
489            out_strings,
490            &mut pending_die_refs,
491            &mut pending_di_refs,
492            EntryAttributesContext::Children {
493                depth: current_depth as usize,
494                subprograms: &mut current_subprogram,
495                file_map: &file_map,
496                file_index_base,
497                frame_base: current_frame_base.top(),
498            },
499            isa,
500        )?;
501
502        // Data in WebAssembly memory always uses little-endian byte order.
503        // If the native architecture is big-endian, we need to mark all
504        // base types used to refer to WebAssembly memory as little-endian
505        // using the DW_AT_endianity attribute, so that the debugger will
506        // be able to correctly access them.
507        if entry.tag() == gimli::DW_TAG_base_type && isa.endianness() == Endianness::Big {
508            let current_scope = out_unit.get_mut(out_die_id);
509            current_scope.set(
510                gimli::DW_AT_endianity,
511                write::AttributeValue::Endianity(gimli::DW_END_little),
512            );
513        }
514
515        if entry.tag() == gimli::DW_TAG_subprogram && !current_scope_ranges.is_empty() {
516            append_vmctx_info(
517                out_unit,
518                out_die_id,
519                out_module_synthetic_unit.vmctx_ptr_die_ref(),
520                addr_tr,
521                current_value_range.top(),
522                current_scope_ranges.top().context("range")?,
523                out_strings,
524                isa,
525            )?;
526        }
527
528        log_end_output_die(
529            entry,
530            unit,
531            out_die_id,
532            out_unit,
533            out_strings,
534            current_depth,
535        );
536    }
537    die_ref_map.patch(pending_die_refs, out_unit);
538    Ok(Some((out_unit_id, die_ref_map, pending_di_refs)))
539}