1 use super::expression::{CompiledExpression, FunctionFrameInfo};
2 use super::utils::{add_internal_types, append_vmctx_info};
3 use super::AddressTransform;
4 use crate::debug::{Compilation, ModuleMemoryOffset};
5 use anyhow::{Context, Error};
6 use cranelift_codegen::isa::TargetIsa;
7 use cranelift_wasm::get_vmctx_value_label;
8 use gimli::write;
9 use gimli::LineEncoding;
10 use std::collections::{HashMap, HashSet};
11 use std::path::PathBuf;
12 use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
13 use wasmtime_environ::{
14     DebugInfoData, EntityRef, FunctionMetadata, PrimaryMap, StaticModuleIndex, WasmFileInfo,
15     WasmValType,
16 };
17 
18 const PRODUCER_NAME: &str = "wasmtime";
19 
20 macro_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 
32 fn 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, 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)
94 }
95 
96 fn check_invalid_chars_in_name(s: &str) -> Option<&str> {
97     if s.contains('\x00') {
98         None
99     } else {
100         Some(s)
101     }
102 }
103 
104 fn 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-{}>", NEXT_ID.fetch_add(1, SeqCst)));
112     let path = format!("/<wasm-module>/{module_name}.wasm");
113     PathBuf::from(path)
114 }
115 
116 struct WasmTypesDieRefs {
117     vmctx: write::UnitEntryId,
118     i32: write::UnitEntryId,
119     i64: write::UnitEntryId,
120     f32: write::UnitEntryId,
121     f64: write::UnitEntryId,
122 }
123 
124 fn add_wasm_types(
125     unit: &mut write::Unit,
126     root_id: write::UnitEntryId,
127     out_strings: &mut write::StringTable,
128     memory_offset: &ModuleMemoryOffset,
129 ) -> WasmTypesDieRefs {
130     let (_wp_die_id, vmctx_die_id) = add_internal_types(unit, root_id, out_strings, memory_offset);
131 
132     macro_rules! def_type {
133         ($id:literal, $size:literal, $enc:path) => {{
134             let die_id = unit.add(root_id, gimli::DW_TAG_base_type);
135             let die = unit.get_mut(die_id);
136             die.set(
137                 gimli::DW_AT_name,
138                 write::AttributeValue::StringRef(out_strings.add($id)),
139             );
140             die.set(gimli::DW_AT_byte_size, write::AttributeValue::Data1($size));
141             die.set(gimli::DW_AT_encoding, write::AttributeValue::Encoding($enc));
142             die_id
143         }};
144     }
145 
146     let i32_die_id = def_type!("i32", 4, gimli::DW_ATE_signed);
147     let i64_die_id = def_type!("i64", 8, gimli::DW_ATE_signed);
148     let f32_die_id = def_type!("f32", 4, gimli::DW_ATE_float);
149     let f64_die_id = def_type!("f64", 8, gimli::DW_ATE_float);
150 
151     WasmTypesDieRefs {
152         vmctx: vmctx_die_id,
153         i32: i32_die_id,
154         i64: i64_die_id,
155         f32: f32_die_id,
156         f64: f64_die_id,
157     }
158 }
159 
160 fn 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 
193 fn 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     wasm_types: &WasmTypesDieRefs,
200     func_meta: &FunctionMetadata,
201     locals_names: Option<&HashMap<u32, &str>>,
202     out_strings: &mut write::StringTable,
203     isa: &dyn TargetIsa,
204 ) -> Result<(), Error> {
205     let vmctx_label = get_vmctx_value_label();
206 
207     // Normalize order of ValueLabelsRanges keys to have reproducible results.
208     let mut vars = frame_info.value_ranges.keys().collect::<Vec<_>>();
209     vars.sort_by(|a, b| a.index().cmp(&b.index()));
210 
211     for label in vars {
212         if label.index() == vmctx_label.index() {
213             append_vmctx_info(
214                 unit,
215                 die_id,
216                 wasm_types.vmctx,
217                 addr_tr,
218                 Some(frame_info),
219                 scope_ranges,
220                 out_strings,
221                 isa,
222             )?;
223         } else {
224             let var_index = label.index();
225             let (type_die_id, is_param) =
226                 if let Some(result) = resolve_var_type(var_index, wasm_types, func_meta) {
227                     result
228                 } else {
229                     // Skipping if type of local cannot be detected.
230                     continue;
231                 };
232 
233             let loc_list_id = {
234                 let locs = CompiledExpression::from_label(*label)
235                     .build_with_locals(scope_ranges, addr_tr, Some(frame_info), isa)
236                     .map(|i| {
237                         i.map(|(begin, length, data)| write::Location::StartLength {
238                             begin,
239                             length,
240                             data,
241                         })
242                     })
243                     .collect::<Result<Vec<_>, _>>()?;
244                 unit.locations.add(write::LocationList(locs))
245             };
246 
247             let var_id = unit.add(
248                 die_id,
249                 if is_param {
250                     gimli::DW_TAG_formal_parameter
251                 } else {
252                     gimli::DW_TAG_variable
253                 },
254             );
255             let var = unit.get_mut(var_id);
256 
257             let name_id = match locals_names
258                 .and_then(|m| m.get(&(var_index as u32)))
259                 .and_then(|s| check_invalid_chars_in_name(s))
260             {
261                 Some(n) => out_strings.add(assert_dwarf_str!(n)),
262                 None => out_strings.add(format!("var{var_index}")),
263             };
264 
265             var.set(gimli::DW_AT_name, write::AttributeValue::StringRef(name_id));
266             var.set(
267                 gimli::DW_AT_type,
268                 write::AttributeValue::UnitRef(type_die_id),
269             );
270             var.set(
271                 gimli::DW_AT_location,
272                 write::AttributeValue::LocationListRef(loc_list_id),
273             );
274         }
275     }
276     Ok(())
277 }
278 
279 fn check_invalid_chars_in_path(path: PathBuf) -> Option<PathBuf> {
280     path.clone()
281         .to_str()
282         .and_then(move |s| if s.contains('\x00') { None } else { Some(path) })
283 }
284 
285 pub fn generate_simulated_dwarf(
286     compilation: &mut Compilation<'_>,
287     addr_tr: &PrimaryMap<StaticModuleIndex, AddressTransform>,
288     translated: &HashSet<usize>,
289     out_encoding: gimli::Encoding,
290     out_units: &mut write::UnitTable,
291     out_strings: &mut write::StringTable,
292     isa: &dyn TargetIsa,
293 ) -> Result<(), Error> {
294     let (wasm_file, path) = {
295         let di = &compilation.translations.iter().next().unwrap().1.debuginfo;
296         let path = di
297             .wasm_file
298             .path
299             .to_owned()
300             .and_then(check_invalid_chars_in_path)
301             .unwrap_or_else(|| autogenerate_dwarf_wasm_path(di));
302         (&di.wasm_file, path)
303     };
304 
305     let (unit, root_id, name_id) = {
306         let comp_dir_id = out_strings.add(assert_dwarf_str!(path
307             .parent()
308             .context("path dir")?
309             .to_str()
310             .context("path dir encoding")?));
311         let name = path
312             .file_name()
313             .context("path name")?
314             .to_str()
315             .context("path name encoding")?;
316         let name_id = out_strings.add(assert_dwarf_str!(name));
317 
318         let out_program = generate_line_info(
319             addr_tr,
320             translated,
321             out_encoding,
322             wasm_file,
323             comp_dir_id,
324             name_id,
325             name,
326         )?;
327 
328         let unit_id = out_units.add(write::Unit::new(out_encoding, out_program));
329         let unit = out_units.get_mut(unit_id);
330 
331         let root_id = unit.root();
332         let root = unit.get_mut(root_id);
333 
334         let id = out_strings.add(PRODUCER_NAME);
335         root.set(gimli::DW_AT_producer, write::AttributeValue::StringRef(id));
336         root.set(gimli::DW_AT_name, write::AttributeValue::StringRef(name_id));
337         root.set(
338             gimli::DW_AT_stmt_list,
339             write::AttributeValue::LineProgramRef,
340         );
341         root.set(
342             gimli::DW_AT_comp_dir,
343             write::AttributeValue::StringRef(comp_dir_id),
344         );
345         (unit, root_id, name_id)
346     };
347 
348     let mut module_wasm_types = PrimaryMap::new();
349     for (module, memory_offset) in compilation.module_memory_offsets.iter() {
350         let wasm_types = add_wasm_types(unit, root_id, out_strings, memory_offset);
351         let i = module_wasm_types.push(wasm_types);
352         assert_eq!(i, module);
353     }
354 
355     for (module, index) in compilation.indexes().collect::<Vec<_>>() {
356         let (symbol, _) = compilation.function(module, index);
357         if translated.contains(&symbol) {
358             continue;
359         }
360 
361         let addr_tr = &addr_tr[module];
362         let map = &addr_tr.map()[index];
363         let start = map.offset as u64;
364         let end = start + map.len as u64;
365         let die_id = unit.add(root_id, gimli::DW_TAG_subprogram);
366         let die = unit.get_mut(die_id);
367         die.set(
368             gimli::DW_AT_low_pc,
369             write::AttributeValue::Address(write::Address::Symbol {
370                 symbol,
371                 addend: start as i64,
372             }),
373         );
374         die.set(
375             gimli::DW_AT_high_pc,
376             write::AttributeValue::Udata(end - start),
377         );
378 
379         let translation = &compilation.translations[module];
380         let func_index = translation.module.func_index(index);
381         let di = &translation.debuginfo;
382         let id = match di
383             .name_section
384             .func_names
385             .get(&func_index)
386             .and_then(|s| check_invalid_chars_in_name(s))
387         {
388             Some(n) => out_strings.add(assert_dwarf_str!(n)),
389             None => out_strings.add(format!("wasm-function[{}]", func_index.as_u32())),
390         };
391 
392         die.set(gimli::DW_AT_name, write::AttributeValue::StringRef(id));
393 
394         die.set(
395             gimli::DW_AT_decl_file,
396             write::AttributeValue::StringRef(name_id),
397         );
398 
399         let f_start = map.addresses[0].wasm;
400         let wasm_offset = di.wasm_file.code_section_offset + f_start;
401         die.set(
402             gimli::DW_AT_decl_file,
403             write::AttributeValue::Udata(wasm_offset),
404         );
405 
406         let frame_info = compilation.function_frame_info(module, index);
407         let source_range = addr_tr.func_source_range(index);
408         generate_vars(
409             unit,
410             die_id,
411             addr_tr,
412             &frame_info,
413             &[(source_range.0, source_range.1)],
414             &module_wasm_types[module],
415             &di.wasm_file.funcs[index.as_u32() as usize],
416             di.name_section.locals_names.get(&func_index),
417             out_strings,
418             isa,
419         )?;
420     }
421 
422     Ok(())
423 }
424