wasm_debug/transform/
simulate.rs

1use super::expression::{CompiledExpression, FunctionFrameInfo};
2use super::utils::{add_internal_types, append_vmctx_info, get_function_frame_info};
3use super::AddressTransform;
4use crate::read_debuginfo::WasmFileInfo;
5use crate::types::{get_vmctx_value_label, ModuleVmctxInfo, ValueLabelsRanges};
6use anyhow::Error;
7use cranelift_entity::EntityRef;
8use gimli::write;
9use gimli::{self, LineEncoding};
10use std::collections::{HashMap, HashSet};
11use std::path::PathBuf;
12
13pub use crate::read_debuginfo::{DebugInfoData, FunctionMetadata, WasmType};
14
15const PRODUCER_NAME: &str = "wasmtime";
16
17fn generate_line_info(
18    addr_tr: &AddressTransform,
19    translated: &HashSet<u32>,
20    out_encoding: gimli::Encoding,
21    w: &WasmFileInfo,
22    comp_dir_id: write::StringId,
23    name_id: write::StringId,
24    name: &str,
25) -> Result<write::LineProgram, Error> {
26    let out_comp_dir = write::LineString::StringRef(comp_dir_id);
27    let out_comp_name = write::LineString::StringRef(name_id);
28
29    let line_encoding = LineEncoding::default();
30
31    let mut out_program = write::LineProgram::new(
32        out_encoding,
33        line_encoding,
34        out_comp_dir,
35        out_comp_name,
36        None,
37    );
38
39    let file_index = out_program.add_file(
40        write::LineString::String(name.as_bytes().to_vec()),
41        out_program.default_directory(),
42        None,
43    );
44
45    for (i, map) in addr_tr.map() {
46        let symbol = i.index();
47        if translated.contains(&(symbol as u32)) {
48            continue;
49        }
50
51        let base_addr = map.offset;
52        out_program.begin_sequence(Some(write::Address::Symbol { symbol, addend: 0 }));
53        for addr_map in map.addresses.iter() {
54            let address_offset = (addr_map.generated - base_addr) as u64;
55            out_program.row().address_offset = address_offset;
56            out_program.row().op_index = 0;
57            out_program.row().file = file_index;
58            let wasm_offset = w.code_section_offset + addr_map.wasm as u64;
59            out_program.row().line = wasm_offset;
60            out_program.row().column = 0;
61            out_program.row().discriminator = 1;
62            out_program.row().is_statement = true;
63            out_program.row().basic_block = false;
64            out_program.row().prologue_end = false;
65            out_program.row().epilogue_begin = false;
66            out_program.row().isa = 0;
67            out_program.generate_row();
68        }
69        let end_addr = (map.offset + map.len - 1) as u64;
70        out_program.end_sequence(end_addr);
71    }
72
73    Ok(out_program)
74}
75
76fn autogenerate_dwarf_wasm_path(di: &DebugInfoData) -> PathBuf {
77    let module_name = di
78        .name_section
79        .as_ref()
80        .and_then(|ns| ns.module_name.to_owned())
81        .unwrap_or_else(|| unsafe {
82            static mut GEN_ID: u32 = 0;
83            GEN_ID += 1;
84            format!("<gen-{}>", GEN_ID)
85        });
86    let path = format!("/<wasm-module>/{}.wasm", module_name);
87    PathBuf::from(path)
88}
89
90struct WasmTypesDieRefs {
91    vmctx: write::UnitEntryId,
92    i32: write::UnitEntryId,
93    i64: write::UnitEntryId,
94    f32: write::UnitEntryId,
95    f64: write::UnitEntryId,
96}
97
98fn add_wasm_types(
99    unit: &mut write::Unit,
100    root_id: write::UnitEntryId,
101    out_strings: &mut write::StringTable,
102    vmctx_info: &ModuleVmctxInfo,
103) -> WasmTypesDieRefs {
104    let (_wp_die_id, vmctx_die_id) = add_internal_types(unit, root_id, out_strings, vmctx_info);
105
106    macro_rules! def_type {
107        ($id:literal, $size:literal, $enc:path) => {{
108            let die_id = unit.add(root_id, gimli::DW_TAG_base_type);
109            let die = unit.get_mut(die_id);
110            die.set(
111                gimli::DW_AT_name,
112                write::AttributeValue::StringRef(out_strings.add($id)),
113            );
114            die.set(gimli::DW_AT_byte_size, write::AttributeValue::Data1($size));
115            die.set(gimli::DW_AT_encoding, write::AttributeValue::Encoding($enc));
116            die_id
117        }};
118    }
119
120    let i32_die_id = def_type!("i32", 4, gimli::DW_ATE_signed);
121    let i64_die_id = def_type!("i64", 8, gimli::DW_ATE_signed);
122    let f32_die_id = def_type!("f32", 4, gimli::DW_ATE_float);
123    let f64_die_id = def_type!("f64", 8, gimli::DW_ATE_float);
124
125    WasmTypesDieRefs {
126        vmctx: vmctx_die_id,
127        i32: i32_die_id,
128        i64: i64_die_id,
129        f32: f32_die_id,
130        f64: f64_die_id,
131    }
132}
133
134fn resolve_var_type(
135    index: usize,
136    wasm_types: &WasmTypesDieRefs,
137    func_meta: &FunctionMetadata,
138) -> Option<(write::UnitEntryId, bool)> {
139    let (ty, is_param) = if index < func_meta.params.len() {
140        (func_meta.params[index], true)
141    } else {
142        let mut i = (index - func_meta.params.len()) as u32;
143        let mut j = 0;
144        while j < func_meta.locals.len() && i >= func_meta.locals[j].0 {
145            i -= func_meta.locals[j].0;
146            j += 1;
147        }
148        if j >= func_meta.locals.len() {
149            // Ignore the var index out of bound.
150            return None;
151        }
152        (func_meta.locals[j].1, false)
153    };
154    let type_die_id = match ty {
155        WasmType::I32 => wasm_types.i32,
156        WasmType::I64 => wasm_types.i64,
157        WasmType::F32 => wasm_types.f32,
158        WasmType::F64 => wasm_types.f64,
159        _ => {
160            // Ignore unsupported types.
161            return None;
162        }
163    };
164    Some((type_die_id, is_param))
165}
166
167fn generate_vars(
168    unit: &mut write::Unit,
169    die_id: write::UnitEntryId,
170    addr_tr: &AddressTransform,
171    frame_info: &FunctionFrameInfo,
172    scope_ranges: &[(u64, u64)],
173    wasm_types: &WasmTypesDieRefs,
174    func_meta: &FunctionMetadata,
175    locals_names: Option<&HashMap<u32, String>>,
176    out_strings: &mut write::StringTable,
177) {
178    let vmctx_label = get_vmctx_value_label();
179
180    for label in frame_info.value_ranges.keys() {
181        if label.index() == vmctx_label.index() {
182            append_vmctx_info(
183                unit,
184                die_id,
185                wasm_types.vmctx,
186                addr_tr,
187                Some(frame_info),
188                scope_ranges,
189                out_strings,
190            )
191            .expect("append_vmctx_info success");
192        } else {
193            let var_index = label.index();
194            let (type_die_id, is_param) =
195                if let Some(result) = resolve_var_type(var_index, wasm_types, func_meta) {
196                    result
197                } else {
198                    // Skipping if type of local cannot be detected.
199                    continue;
200                };
201
202            let loc_list_id = {
203                let endian = gimli::RunTimeEndian::Little;
204
205                let expr = CompiledExpression::from_label(*label);
206                let mut locs = Vec::new();
207                for (begin, length, data) in
208                    expr.build_with_locals(scope_ranges, addr_tr, Some(frame_info), endian)
209                {
210                    locs.push(write::Location::StartLength {
211                        begin,
212                        length,
213                        data,
214                    });
215                }
216                unit.locations.add(write::LocationList(locs))
217            };
218
219            let var_id = unit.add(
220                die_id,
221                if is_param {
222                    gimli::DW_TAG_formal_parameter
223                } else {
224                    gimli::DW_TAG_variable
225                },
226            );
227            let var = unit.get_mut(var_id);
228
229            let name_id = match locals_names.and_then(|m| m.get(&(var_index as u32))) {
230                Some(n) => out_strings.add(n.to_owned()),
231                None => out_strings.add(format!("var{}", var_index)),
232            };
233
234            var.set(gimli::DW_AT_name, write::AttributeValue::StringRef(name_id));
235            var.set(
236                gimli::DW_AT_type,
237                write::AttributeValue::ThisUnitEntryRef(type_die_id),
238            );
239            var.set(
240                gimli::DW_AT_location,
241                write::AttributeValue::LocationListRef(loc_list_id),
242            );
243        }
244    }
245}
246
247pub fn generate_simulated_dwarf(
248    addr_tr: &AddressTransform,
249    di: &DebugInfoData,
250    vmctx_info: &ModuleVmctxInfo,
251    ranges: &ValueLabelsRanges,
252    translated: &HashSet<u32>,
253    out_encoding: gimli::Encoding,
254    out_units: &mut write::UnitTable,
255    out_strings: &mut write::StringTable,
256) -> Result<(), Error> {
257    let path = di
258        .wasm_file
259        .path
260        .to_owned()
261        .unwrap_or_else(|| autogenerate_dwarf_wasm_path(di));
262
263    let (func_names, locals_names) = if let Some(ref name_section) = di.name_section {
264        (
265            Some(&name_section.func_names),
266            Some(&name_section.locals_names),
267        )
268    } else {
269        (None, None)
270    };
271
272    let (unit, root_id, name_id) = {
273        let comp_dir_id = out_strings.add(path.parent().expect("path dir").to_str().unwrap());
274        let name = path.file_name().expect("path name").to_str().unwrap();
275        let name_id = out_strings.add(name);
276
277        let out_program = generate_line_info(
278            addr_tr,
279            translated,
280            out_encoding,
281            &di.wasm_file,
282            comp_dir_id,
283            name_id,
284            name,
285        )?;
286
287        let unit_id = out_units.add(write::Unit::new(out_encoding, out_program));
288        let unit = out_units.get_mut(unit_id);
289
290        let root_id = unit.root();
291        let root = unit.get_mut(root_id);
292
293        let id = out_strings.add(PRODUCER_NAME);
294        root.set(gimli::DW_AT_producer, write::AttributeValue::StringRef(id));
295        root.set(gimli::DW_AT_name, write::AttributeValue::StringRef(name_id));
296        root.set(
297            gimli::DW_AT_stmt_list,
298            write::AttributeValue::LineProgramRef,
299        );
300        root.set(
301            gimli::DW_AT_comp_dir,
302            write::AttributeValue::StringRef(comp_dir_id),
303        );
304        (unit, root_id, name_id)
305    };
306
307    let wasm_types = add_wasm_types(unit, root_id, out_strings, vmctx_info);
308
309    for (i, map) in addr_tr.map().iter() {
310        let index = i.index();
311        if translated.contains(&(index as u32)) {
312            continue;
313        }
314
315        let start = map.offset as u64;
316        let end = start + map.len as u64;
317        let die_id = unit.add(root_id, gimli::DW_TAG_subprogram);
318        let die = unit.get_mut(die_id);
319        die.set(
320            gimli::DW_AT_low_pc,
321            write::AttributeValue::Address(write::Address::Symbol {
322                symbol: index,
323                addend: start as i64,
324            }),
325        );
326        die.set(
327            gimli::DW_AT_high_pc,
328            write::AttributeValue::Udata((end - start) as u64),
329        );
330
331        let id = match func_names.and_then(|m| m.get(&(index as u32))) {
332            Some(n) => out_strings.add(n.to_owned()),
333            None => out_strings.add(format!("wasm-function[{}]", index)),
334        };
335
336        die.set(gimli::DW_AT_name, write::AttributeValue::StringRef(id));
337
338        die.set(
339            gimli::DW_AT_decl_file,
340            write::AttributeValue::StringRef(name_id),
341        );
342
343        let f = addr_tr.map().get(i).unwrap();
344        let f_start = f.addresses[0].wasm;
345        let wasm_offset = di.wasm_file.code_section_offset + f_start as u64;
346        die.set(
347            gimli::DW_AT_decl_file,
348            write::AttributeValue::Udata(wasm_offset),
349        );
350
351        if let Some(frame_info) = get_function_frame_info(vmctx_info, i, ranges) {
352            let source_range = addr_tr.func_source_range(i);
353            generate_vars(
354                unit,
355                die_id,
356                addr_tr,
357                &frame_info,
358                &[(source_range.0, source_range.1)],
359                &wasm_types,
360                &di.wasm_file.funcs[index],
361                locals_names.and_then(|m| m.get(&(index as u32))),
362                out_strings,
363            );
364        }
365    }
366
367    Ok(())
368}