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