wasmtime_cranelift/debug/transform/
simulate.rs

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