1use super::expression::{CompiledExpression, FunctionFrameInfo};
2use super::utils::{add_internal_types, append_vmctx_info, get_function_frame_info};
3use super::AddressTransform;
4use crate::debug::ModuleMemoryOffset;
5use crate::CompiledFunctions;
6use anyhow::{Context, Error};
7use cranelift_codegen::isa::TargetIsa;
8use cranelift_wasm::get_vmctx_value_label;
9use gimli::write;
10use gimli::{self, LineEncoding};
11use std::collections::{HashMap, HashSet};
12use std::path::PathBuf;
13use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
14use wasmparser::ValType as WasmType;
15use wasmtime_environ::{
16 DebugInfoData, DefinedFuncIndex, EntityRef, FuncIndex, FunctionMetadata, WasmFileInfo,
17};
18
19const PRODUCER_NAME: &str = "wasmtime";
20
21macro_rules! assert_dwarf_str {
22 ($s:expr) => {{
23 let s = $s;
24 if cfg!(debug_assertions) {
25 let bytes: Vec<u8> = s.clone().into();
27 debug_assert!(!bytes.contains(&0), "DWARF string shall not have NULL byte");
28 }
29 s
30 }};
31}
32
33fn generate_line_info(
34 addr_tr: &AddressTransform,
35 translated: &HashSet<DefinedFuncIndex>,
36 out_encoding: gimli::Encoding,
37 w: &WasmFileInfo,
38 comp_dir_id: write::StringId,
39 name_id: write::StringId,
40 name: &str,
41) -> Result<write::LineProgram, Error> {
42 let out_comp_dir = write::LineString::StringRef(comp_dir_id);
43 let out_comp_name = write::LineString::StringRef(name_id);
44
45 let line_encoding = LineEncoding::default();
46
47 let mut out_program = write::LineProgram::new(
48 out_encoding,
49 line_encoding,
50 out_comp_dir,
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 for (i, map) in addr_tr.map() {
62 let symbol = i.index();
63 if translated.contains(&i) {
64 continue;
65 }
66
67 let base_addr = map.offset;
68 out_program.begin_sequence(Some(write::Address::Symbol { symbol, addend: 0 }));
69 for addr_map in map.addresses.iter() {
70 let address_offset = (addr_map.generated - base_addr) as u64;
71 out_program.row().address_offset = address_offset;
72 out_program.row().op_index = 0;
73 out_program.row().file = file_index;
74 let wasm_offset = w.code_section_offset + addr_map.wasm as u64;
75 out_program.row().line = wasm_offset;
76 out_program.row().column = 0;
77 out_program.row().discriminator = 1;
78 out_program.row().is_statement = true;
79 out_program.row().basic_block = false;
80 out_program.row().prologue_end = false;
81 out_program.row().epilogue_begin = false;
82 out_program.row().isa = 0;
83 out_program.generate_row();
84 }
85 let end_addr = (map.offset + map.len - 1) as u64;
86 out_program.end_sequence(end_addr);
87 }
88
89 Ok(out_program)
90}
91
92fn check_invalid_chars_in_name(s: &str) -> Option<&str> {
93 if s.contains('\x00') {
94 None
95 } else {
96 Some(s)
97 }
98}
99
100fn autogenerate_dwarf_wasm_path(di: &DebugInfoData) -> PathBuf {
101 static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
102 let module_name = di
103 .name_section
104 .module_name
105 .and_then(check_invalid_chars_in_name)
106 .map(|s| s.to_string())
107 .unwrap_or_else(|| format!("<gen-{}>", NEXT_ID.fetch_add(1, SeqCst)));
108 let path = format!("/<wasm-module>/{}.wasm", module_name);
109 PathBuf::from(path)
110}
111
112struct WasmTypesDieRefs {
113 vmctx: write::UnitEntryId,
114 i32: write::UnitEntryId,
115 i64: write::UnitEntryId,
116 f32: write::UnitEntryId,
117 f64: write::UnitEntryId,
118}
119
120fn add_wasm_types(
121 unit: &mut write::Unit,
122 root_id: write::UnitEntryId,
123 out_strings: &mut write::StringTable,
124 memory_offset: &ModuleMemoryOffset,
125) -> WasmTypesDieRefs {
126 let (_wp_die_id, vmctx_die_id) = add_internal_types(unit, root_id, out_strings, memory_offset);
127
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 vmctx: vmctx_die_id,
149 i32: i32_die_id,
150 i64: i64_die_id,
151 f32: f32_die_id,
152 f64: f64_die_id,
153 }
154}
155
156fn resolve_var_type(
157 index: usize,
158 wasm_types: &WasmTypesDieRefs,
159 func_meta: &FunctionMetadata,
160) -> Option<(write::UnitEntryId, bool)> {
161 let (ty, is_param) = if index < func_meta.params.len() {
162 (func_meta.params[index], true)
163 } else {
164 let mut i = (index - func_meta.params.len()) as u32;
165 let mut j = 0;
166 while j < func_meta.locals.len() && i >= func_meta.locals[j].0 {
167 i -= func_meta.locals[j].0;
168 j += 1;
169 }
170 if j >= func_meta.locals.len() {
171 return None;
173 }
174 (func_meta.locals[j].1, false)
175 };
176 let type_die_id = match ty {
177 WasmType::I32 => wasm_types.i32,
178 WasmType::I64 => wasm_types.i64,
179 WasmType::F32 => wasm_types.f32,
180 WasmType::F64 => wasm_types.f64,
181 _ => {
182 return None;
184 }
185 };
186 Some((type_die_id, is_param))
187}
188
189fn generate_vars(
190 unit: &mut write::Unit,
191 die_id: write::UnitEntryId,
192 addr_tr: &AddressTransform,
193 frame_info: &FunctionFrameInfo,
194 scope_ranges: &[(u64, u64)],
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 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 wasm_types.vmctx,
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 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
281pub fn generate_simulated_dwarf(
282 addr_tr: &AddressTransform,
283 di: &DebugInfoData,
284 memory_offset: &ModuleMemoryOffset,
285 funcs: &CompiledFunctions,
286 translated: &HashSet<DefinedFuncIndex>,
287 out_encoding: gimli::Encoding,
288 out_units: &mut write::UnitTable,
289 out_strings: &mut write::StringTable,
290 isa: &dyn TargetIsa,
291) -> Result<(), Error> {
292 let path = di
293 .wasm_file
294 .path
295 .to_owned()
296 .and_then(check_invalid_chars_in_path)
297 .unwrap_or_else(|| autogenerate_dwarf_wasm_path(di));
298
299 let func_names = &di.name_section.func_names;
300 let locals_names = &di.name_section.locals_names;
301 let imported_func_count = di.wasm_file.imported_func_count;
302
303 let (unit, root_id, name_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 = generate_line_info(
317 addr_tr,
318 translated,
319 out_encoding,
320 &di.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, name_id)
344 };
345
346 let wasm_types = add_wasm_types(unit, root_id, out_strings, memory_offset);
347
348 for (i, map) in addr_tr.map().iter() {
349 let index = i.index();
350 if translated.contains(&i) {
351 continue;
352 }
353
354 let start = map.offset as u64;
355 let end = start + map.len as u64;
356 let die_id = unit.add(root_id, gimli::DW_TAG_subprogram);
357 let die = unit.get_mut(die_id);
358 die.set(
359 gimli::DW_AT_low_pc,
360 write::AttributeValue::Address(write::Address::Symbol {
361 symbol: index,
362 addend: start as i64,
363 }),
364 );
365 die.set(
366 gimli::DW_AT_high_pc,
367 write::AttributeValue::Udata((end - start) as u64),
368 );
369
370 let func_index = imported_func_count + (index as u32);
371 let id = match func_names
372 .get(&FuncIndex::from_u32(func_index))
373 .and_then(|s| check_invalid_chars_in_name(s))
374 {
375 Some(n) => out_strings.add(assert_dwarf_str!(n)),
376 None => out_strings.add(format!("wasm-function[{}]", func_index)),
377 };
378
379 die.set(gimli::DW_AT_name, write::AttributeValue::StringRef(id));
380
381 die.set(
382 gimli::DW_AT_decl_file,
383 write::AttributeValue::StringRef(name_id),
384 );
385
386 let f_start = map.addresses[0].wasm;
387 let wasm_offset = di.wasm_file.code_section_offset + f_start as u64;
388 die.set(
389 gimli::DW_AT_decl_file,
390 write::AttributeValue::Udata(wasm_offset),
391 );
392
393 if let Some(frame_info) = get_function_frame_info(memory_offset, funcs, i) {
394 let source_range = addr_tr.func_source_range(i);
395 generate_vars(
396 unit,
397 die_id,
398 addr_tr,
399 &frame_info,
400 &[(source_range.0, source_range.1)],
401 &wasm_types,
402 &di.wasm_file.funcs[index],
403 locals_names.get(&FuncIndex::from_u32(index as u32)),
404 out_strings,
405 isa,
406 )?;
407 }
408 }
409
410 Ok(())
411}