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