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 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 out_program.row().address_offset = 0;
80 out_program.row().file = file_index;
81 out_program.row().line = 0; 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 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 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 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 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
287pub 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}