1 use super::AddressTransform;
2 use super::expression::{CompiledExpression, FunctionFrameInfo};
3 use super::utils::append_vmctx_info;
4 use crate::debug::Compilation;
5 use crate::translate::get_vmctx_value_label;
6 use cranelift_codegen::isa::TargetIsa;
7 use gimli::LineEncoding;
8 use gimli::write;
9 use std::collections::{HashMap, HashSet};
10 use std::path::PathBuf;
11 use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
12 use wasmtime_environ::error::{Context, Error};
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         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         // Always emit a row for offset zero - debuggers expect this.
79         out_program.row().address_offset = 0;
80         out_program.row().file = file_index;
81         out_program.row().line = 0; // Special line number for non-user code.
82         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 
105 fn check_invalid_chars_in_name(s: &str) -> Option<&str> {
106     if s.contains('\x00') { None } else { Some(s) }
107 }
108 
109 fn 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 
121 struct WasmTypesDieRefs {
122     i32: write::UnitEntryId,
123     i64: write::UnitEntryId,
124     f32: write::UnitEntryId,
125     f64: write::UnitEntryId,
126 }
127 
128 fn 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 
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     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     // Normalize order of ValueLabelsRanges keys to have reproducible results.
209     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                     // Skipping if type of local cannot be detected.
231                     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 
281 fn 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 
287 /// Generate "simulated" native DWARF for functions lacking WASM-level DWARF.
288 pub 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 }
435