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 as u64; 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) as u64), 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 as u64; 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