1 use super::expression::{CompiledExpression, FunctionFrameInfo};
2 use super::utils::{add_internal_types, append_vmctx_info, get_function_frame_info};
3 use super::AddressTransform;
4 use crate::debug::ModuleMemoryOffset;
5 use crate::CompiledFunctions;
6 use anyhow::{Context, Error};
7 use cranelift_codegen::isa::TargetIsa;
8 use cranelift_wasm::get_vmctx_value_label;
9 use gimli::write;
10 use gimli::{self, LineEncoding};
11 use std::collections::{HashMap, HashSet};
12 use std::path::PathBuf;
13 use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
14 use wasmparser::ValType as WasmType;
15 use wasmtime_environ::{
16     DebugInfoData, DefinedFuncIndex, EntityRef, FuncIndex, FunctionMetadata, WasmFileInfo,
17 };
18 
19 const PRODUCER_NAME: &str = "wasmtime";
20 
21 macro_rules! assert_dwarf_str {
22     ($s:expr) => {{
23         let s = $s;
24         if cfg!(debug_assertions) {
25             // Perform check the same way as gimli does it.
26             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 
33 fn 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 
92 fn check_invalid_chars_in_name(s: &str) -> Option<&str> {
93     if s.contains('\x00') {
94         None
95     } else {
96         Some(s)
97     }
98 }
99 
100 fn 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 
112 struct WasmTypesDieRefs {
113     vmctx: write::UnitEntryId,
114     i32: write::UnitEntryId,
115     i64: write::UnitEntryId,
116     f32: write::UnitEntryId,
117     f64: write::UnitEntryId,
118 }
119 
120 fn 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 
156 fn 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             // Ignore the var index out of bound.
172             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             // Ignore unsupported types.
183             return None;
184         }
185     };
186     Some((type_die_id, is_param))
187 }
188 
189 fn 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     // Normalize order of ValueLabelsRanges keys to have reproducable results.
204     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                     // Skipping if type of local cannot be detected.
226                     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 
275 fn 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 
281 pub 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 }
412