wasmtime_internal_cranelift/debug/transform/
simulate.rs

1use super::AddressTransform;
2use super::expression::{CompiledExpression, FunctionFrameInfo};
3use super::utils::append_vmctx_info;
4use crate::debug::Compilation;
5use crate::translate::get_vmctx_value_label;
6use anyhow::{Context, Error};
7use cranelift_codegen::isa::TargetIsa;
8use gimli::LineEncoding;
9use gimli::write;
10use std::collections::{HashMap, HashSet};
11use std::path::PathBuf;
12use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
13use wasmtime_environ::{
14    DebugInfoData, EntityRef, FunctionMetadata, PrimaryMap, StaticModuleIndex, WasmFileInfo,
15    WasmValType,
16};
17
18const PRODUCER_NAME: &str = "wasmtime";
19
20macro_rules! assert_dwarf_str {
21    ($s:expr) => {{
22        let s = $s;
23        if cfg!(debug_assertions) {
24            // Perform check the same way as gimli does it.
25            let bytes: Vec<u8> = s.clone().into();
26            debug_assert!(!bytes.contains(&0), "DWARF string shall not have NULL byte");
27        }
28        s
29    }};
30}
31
32fn generate_line_info(
33    addr_tr: &PrimaryMap<StaticModuleIndex, AddressTransform>,
34    translated: &HashSet<usize>,
35    out_encoding: gimli::Encoding,
36    w: &WasmFileInfo,
37    comp_dir_id: write::StringId,
38    name_id: write::StringId,
39    name: &str,
40) -> Result<(write::LineProgram, write::FileId), Error> {
41    let out_comp_dir = write::LineString::StringRef(comp_dir_id);
42    let out_comp_name = write::LineString::StringRef(name_id);
43
44    let line_encoding = LineEncoding::default();
45
46    let mut out_program = write::LineProgram::new(
47        out_encoding,
48        line_encoding,
49        out_comp_dir,
50        None,
51        out_comp_name,
52        None,
53    );
54
55    let file_index = out_program.add_file(
56        write::LineString::String(name.as_bytes().to_vec()),
57        out_program.default_directory(),
58        None,
59    );
60
61    let maps = addr_tr.iter().flat_map(|(_, transform)| {
62        transform.map().iter().filter_map(|(_, map)| {
63            if translated.contains(&map.symbol) {
64                None
65            } else {
66                Some((map.symbol, map))
67            }
68        })
69    });
70
71    for (symbol, map) in maps {
72        let base_addr = map.offset;
73        out_program.begin_sequence(Some(write::Address::Symbol {
74            symbol,
75            addend: base_addr as i64,
76        }));
77
78        // Always emit a row for offset zero - debuggers expect this.
79        out_program.row().address_offset = 0;
80        out_program.row().file = file_index;
81        out_program.row().line = 0; // Special line number for non-user code.
82        out_program.row().discriminator = 1;
83        out_program.row().is_statement = true;
84        out_program.generate_row();
85
86        let mut is_prologue_end = true;
87        for addr_map in map.addresses.iter() {
88            let address_offset = (addr_map.generated - base_addr) as u64;
89            out_program.row().address_offset = address_offset;
90            let wasm_offset = w.code_section_offset + addr_map.wasm;
91            out_program.row().line = wasm_offset;
92            out_program.row().discriminator = 1;
93            out_program.row().prologue_end = is_prologue_end;
94            out_program.generate_row();
95
96            is_prologue_end = false;
97        }
98        let end_addr = (base_addr + map.len - 1) as u64;
99        out_program.end_sequence(end_addr);
100    }
101
102    Ok((out_program, file_index))
103}
104
105fn check_invalid_chars_in_name(s: &str) -> Option<&str> {
106    if s.contains('\x00') { None } else { Some(s) }
107}
108
109fn autogenerate_dwarf_wasm_path(di: &DebugInfoData) -> PathBuf {
110    static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
111    let module_name = di
112        .name_section
113        .module_name
114        .and_then(check_invalid_chars_in_name)
115        .map(|s| s.to_string())
116        .unwrap_or_else(|| format!("<gen-{}>.wasm", NEXT_ID.fetch_add(1, SeqCst)));
117    let path = format!("/<wasm-module>/{module_name}");
118    PathBuf::from(path)
119}
120
121struct WasmTypesDieRefs {
122    i32: write::UnitEntryId,
123    i64: write::UnitEntryId,
124    f32: write::UnitEntryId,
125    f64: write::UnitEntryId,
126}
127
128fn add_wasm_types(
129    unit: &mut write::Unit,
130    root_id: write::UnitEntryId,
131    out_strings: &mut write::StringTable,
132) -> WasmTypesDieRefs {
133    macro_rules! def_type {
134        ($id:literal, $size:literal, $enc:path) => {{
135            let die_id = unit.add(root_id, gimli::DW_TAG_base_type);
136            let die = unit.get_mut(die_id);
137            die.set(
138                gimli::DW_AT_name,
139                write::AttributeValue::StringRef(out_strings.add($id)),
140            );
141            die.set(gimli::DW_AT_byte_size, write::AttributeValue::Data1($size));
142            die.set(gimli::DW_AT_encoding, write::AttributeValue::Encoding($enc));
143            die_id
144        }};
145    }
146
147    let i32_die_id = def_type!("i32", 4, gimli::DW_ATE_signed);
148    let i64_die_id = def_type!("i64", 8, gimli::DW_ATE_signed);
149    let f32_die_id = def_type!("f32", 4, gimli::DW_ATE_float);
150    let f64_die_id = def_type!("f64", 8, gimli::DW_ATE_float);
151
152    WasmTypesDieRefs {
153        i32: i32_die_id,
154        i64: i64_die_id,
155        f32: f32_die_id,
156        f64: f64_die_id,
157    }
158}
159
160fn resolve_var_type(
161    index: usize,
162    wasm_types: &WasmTypesDieRefs,
163    func_meta: &FunctionMetadata,
164) -> Option<(write::UnitEntryId, bool)> {
165    let (ty, is_param) = if index < func_meta.params.len() {
166        (func_meta.params[index], true)
167    } else {
168        let mut i = (index - func_meta.params.len()) as u32;
169        let mut j = 0;
170        while j < func_meta.locals.len() && i >= func_meta.locals[j].0 {
171            i -= func_meta.locals[j].0;
172            j += 1;
173        }
174        if j >= func_meta.locals.len() {
175            // Ignore the var index out of bound.
176            return None;
177        }
178        (func_meta.locals[j].1, false)
179    };
180    let type_die_id = match ty {
181        WasmValType::I32 => wasm_types.i32,
182        WasmValType::I64 => wasm_types.i64,
183        WasmValType::F32 => wasm_types.f32,
184        WasmValType::F64 => wasm_types.f64,
185        _ => {
186            // Ignore unsupported types.
187            return None;
188        }
189    };
190    Some((type_die_id, is_param))
191}
192
193fn generate_vars(
194    unit: &mut write::Unit,
195    die_id: write::UnitEntryId,
196    addr_tr: &AddressTransform,
197    frame_info: &FunctionFrameInfo,
198    scope_ranges: &[(u64, u64)],
199    vmctx_ptr_die_ref: write::Reference,
200    wasm_types: &WasmTypesDieRefs,
201    func_meta: &FunctionMetadata,
202    locals_names: Option<&HashMap<u32, &str>>,
203    out_strings: &mut write::StringTable,
204    isa: &dyn TargetIsa,
205) -> Result<(), Error> {
206    let vmctx_label = get_vmctx_value_label();
207
208    // Normalize order of ValueLabelsRanges keys to have reproducible results.
209    let mut vars = frame_info.value_ranges.keys().collect::<Vec<_>>();
210    vars.sort_by(|a, b| a.index().cmp(&b.index()));
211
212    for label in vars {
213        if label.index() == vmctx_label.index() {
214            append_vmctx_info(
215                unit,
216                die_id,
217                vmctx_ptr_die_ref,
218                addr_tr,
219                Some(frame_info),
220                scope_ranges,
221                out_strings,
222                isa,
223            )?;
224        } else {
225            let var_index = label.index();
226            let (type_die_id, is_param) =
227                if let Some(result) = resolve_var_type(var_index, wasm_types, func_meta) {
228                    result
229                } else {
230                    // Skipping if type of local cannot be detected.
231                    continue;
232                };
233
234            let loc_list_id = {
235                let locs = CompiledExpression::from_label(*label)
236                    .build_with_locals(scope_ranges, addr_tr, Some(frame_info), isa)
237                    .expressions
238                    .map(|i| {
239                        i.map(|(begin, length, data)| write::Location::StartLength {
240                            begin,
241                            length,
242                            data,
243                        })
244                    })
245                    .collect::<Result<Vec<_>, _>>()?;
246                unit.locations.add(write::LocationList(locs))
247            };
248
249            let var_id = unit.add(
250                die_id,
251                if is_param {
252                    gimli::DW_TAG_formal_parameter
253                } else {
254                    gimli::DW_TAG_variable
255                },
256            );
257            let var = unit.get_mut(var_id);
258
259            let name_id = match locals_names
260                .and_then(|m| m.get(&(var_index as u32)))
261                .and_then(|s| check_invalid_chars_in_name(s))
262            {
263                Some(n) => out_strings.add(assert_dwarf_str!(n)),
264                None => out_strings.add(format!("var{var_index}")),
265            };
266
267            var.set(gimli::DW_AT_name, write::AttributeValue::StringRef(name_id));
268            var.set(
269                gimli::DW_AT_type,
270                write::AttributeValue::UnitRef(type_die_id),
271            );
272            var.set(
273                gimli::DW_AT_location,
274                write::AttributeValue::LocationListRef(loc_list_id),
275            );
276        }
277    }
278    Ok(())
279}
280
281fn check_invalid_chars_in_path(path: PathBuf) -> Option<PathBuf> {
282    path.clone()
283        .to_str()
284        .and_then(move |s| if s.contains('\x00') { None } else { Some(path) })
285}
286
287/// Generate "simulated" native DWARF for functions lacking WASM-level DWARF.
288pub fn generate_simulated_dwarf(
289    compilation: &mut Compilation<'_>,
290    addr_tr: &PrimaryMap<StaticModuleIndex, AddressTransform>,
291    translated: &HashSet<usize>,
292    out_encoding: gimli::Encoding,
293    vmctx_ptr_die_refs: &PrimaryMap<StaticModuleIndex, write::Reference>,
294    out_units: &mut write::UnitTable,
295    out_strings: &mut write::StringTable,
296    isa: &dyn TargetIsa,
297) -> Result<(), Error> {
298    let (wasm_file, path) = {
299        let di = &compilation.translations.iter().next().unwrap().1.debuginfo;
300        let path = di
301            .wasm_file
302            .path
303            .to_owned()
304            .and_then(check_invalid_chars_in_path)
305            .unwrap_or_else(|| autogenerate_dwarf_wasm_path(di));
306        (&di.wasm_file, path)
307    };
308
309    let (unit, root_id, file_id) = {
310        let comp_dir_id = out_strings.add(assert_dwarf_str!(
311            path.parent()
312                .context("path dir")?
313                .to_str()
314                .context("path dir encoding")?
315        ));
316        let name = path
317            .file_name()
318            .context("path name")?
319            .to_str()
320            .context("path name encoding")?;
321        let name_id = out_strings.add(assert_dwarf_str!(name));
322
323        let (out_program, file_id) = generate_line_info(
324            addr_tr,
325            translated,
326            out_encoding,
327            wasm_file,
328            comp_dir_id,
329            name_id,
330            name,
331        )?;
332
333        let unit_id = out_units.add(write::Unit::new(out_encoding, out_program));
334        let unit = out_units.get_mut(unit_id);
335
336        let root_id = unit.root();
337        let root = unit.get_mut(root_id);
338
339        let id = out_strings.add(PRODUCER_NAME);
340        root.set(gimli::DW_AT_producer, write::AttributeValue::StringRef(id));
341        root.set(
342            gimli::DW_AT_language,
343            write::AttributeValue::Language(gimli::DW_LANG_C11),
344        );
345        root.set(gimli::DW_AT_name, write::AttributeValue::StringRef(name_id));
346        root.set(
347            gimli::DW_AT_stmt_list,
348            write::AttributeValue::LineProgramRef,
349        );
350        root.set(
351            gimli::DW_AT_comp_dir,
352            write::AttributeValue::StringRef(comp_dir_id),
353        );
354        (unit, root_id, file_id)
355    };
356
357    let wasm_types = add_wasm_types(unit, root_id, out_strings);
358    let mut unit_ranges = vec![];
359    for (module, index) in compilation.indexes().collect::<Vec<_>>() {
360        let (symbol, _) = compilation.function(module, index);
361        if translated.contains(&symbol) {
362            continue;
363        }
364
365        let addr_tr = &addr_tr[module];
366        let map = &addr_tr.map()[index];
367        let die_id = unit.add(root_id, gimli::DW_TAG_subprogram);
368        let die = unit.get_mut(die_id);
369        let low_pc = write::Address::Symbol {
370            symbol,
371            addend: map.offset as i64,
372        };
373        let code_length = map.len as u64;
374        die.set(gimli::DW_AT_low_pc, write::AttributeValue::Address(low_pc));
375        die.set(
376            gimli::DW_AT_high_pc,
377            write::AttributeValue::Udata(code_length),
378        );
379        unit_ranges.push(write::Range::StartLength {
380            begin: low_pc,
381            length: code_length,
382        });
383
384        let translation = &compilation.translations[module];
385        let func_index = translation.module.func_index(index);
386        let di = &translation.debuginfo;
387        let id = match di
388            .name_section
389            .func_names
390            .get(&func_index)
391            .and_then(|s| check_invalid_chars_in_name(s))
392        {
393            Some(n) => out_strings.add(assert_dwarf_str!(n)),
394            None => out_strings.add(format!("wasm-function[{}]", func_index.as_u32())),
395        };
396
397        die.set(gimli::DW_AT_name, write::AttributeValue::StringRef(id));
398
399        die.set(
400            gimli::DW_AT_decl_file,
401            write::AttributeValue::FileIndex(Some(file_id)),
402        );
403
404        let f_start = map.addresses[0].wasm;
405        let wasm_offset = di.wasm_file.code_section_offset + f_start;
406        die.set(
407            gimli::DW_AT_decl_line,
408            write::AttributeValue::Udata(wasm_offset),
409        );
410
411        let frame_info = compilation.function_frame_info(module, index);
412        let source_range = addr_tr.func_source_range(index);
413        generate_vars(
414            unit,
415            die_id,
416            addr_tr,
417            &frame_info,
418            &[(source_range.0, source_range.1)],
419            vmctx_ptr_die_refs[module],
420            &wasm_types,
421            &di.wasm_file.funcs[index.as_u32() as usize],
422            di.name_section.locals_names.get(&func_index),
423            out_strings,
424            isa,
425        )?;
426    }
427    let unit_ranges_id = unit.ranges.add(write::RangeList(unit_ranges));
428    unit.get_mut(root_id).set(
429        gimli::DW_AT_ranges,
430        write::AttributeValue::RangeListRef(unit_ranges_id),
431    );
432
433    Ok(())
434}