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