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