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