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 { symbol, addend: 0 })); 73 for addr_map in map.addresses.iter() { 74 let address_offset = (addr_map.generated - base_addr) as u64; 75 out_program.row().address_offset = address_offset; 76 out_program.row().op_index = 0; 77 out_program.row().file = file_index; 78 let wasm_offset = w.code_section_offset + addr_map.wasm; 79 out_program.row().line = wasm_offset; 80 out_program.row().column = 0; 81 out_program.row().discriminator = 1; 82 out_program.row().is_statement = true; 83 out_program.row().basic_block = false; 84 out_program.row().prologue_end = false; 85 out_program.row().epilogue_begin = false; 86 out_program.row().isa = 0; 87 out_program.generate_row(); 88 } 89 let end_addr = (map.offset + map.len - 1) as u64; 90 out_program.end_sequence(end_addr); 91 } 92 93 Ok((out_program, file_index)) 94 } 95 96 fn check_invalid_chars_in_name(s: &str) -> Option<&str> { 97 if s.contains('\x00') { 98 None 99 } else { 100 Some(s) 101 } 102 } 103 104 fn autogenerate_dwarf_wasm_path(di: &DebugInfoData) -> PathBuf { 105 static NEXT_ID: AtomicUsize = AtomicUsize::new(0); 106 let module_name = di 107 .name_section 108 .module_name 109 .and_then(check_invalid_chars_in_name) 110 .map(|s| s.to_string()) 111 .unwrap_or_else(|| format!("<gen-{}>.wasm", NEXT_ID.fetch_add(1, SeqCst))); 112 let path = format!("/<wasm-module>/{module_name}"); 113 PathBuf::from(path) 114 } 115 116 struct WasmTypesDieRefs { 117 i32: write::UnitEntryId, 118 i64: write::UnitEntryId, 119 f32: write::UnitEntryId, 120 f64: write::UnitEntryId, 121 } 122 123 fn add_wasm_types( 124 unit: &mut write::Unit, 125 root_id: write::UnitEntryId, 126 out_strings: &mut write::StringTable, 127 ) -> WasmTypesDieRefs { 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 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 WasmValType::I32 => wasm_types.i32, 177 WasmValType::I64 => wasm_types.i64, 178 WasmValType::F32 => wasm_types.f32, 179 WasmValType::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 vmctx_ptr_die_ref: write::Reference, 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 reproducible 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 vmctx_ptr_die_ref, 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 .expressions 233 .map(|i| { 234 i.map(|(begin, length, data)| write::Location::StartLength { 235 begin, 236 length, 237 data, 238 }) 239 }) 240 .collect::<Result<Vec<_>, _>>()?; 241 unit.locations.add(write::LocationList(locs)) 242 }; 243 244 let var_id = unit.add( 245 die_id, 246 if is_param { 247 gimli::DW_TAG_formal_parameter 248 } else { 249 gimli::DW_TAG_variable 250 }, 251 ); 252 let var = unit.get_mut(var_id); 253 254 let name_id = match locals_names 255 .and_then(|m| m.get(&(var_index as u32))) 256 .and_then(|s| check_invalid_chars_in_name(s)) 257 { 258 Some(n) => out_strings.add(assert_dwarf_str!(n)), 259 None => out_strings.add(format!("var{var_index}")), 260 }; 261 262 var.set(gimli::DW_AT_name, write::AttributeValue::StringRef(name_id)); 263 var.set( 264 gimli::DW_AT_type, 265 write::AttributeValue::UnitRef(type_die_id), 266 ); 267 var.set( 268 gimli::DW_AT_location, 269 write::AttributeValue::LocationListRef(loc_list_id), 270 ); 271 } 272 } 273 Ok(()) 274 } 275 276 fn check_invalid_chars_in_path(path: PathBuf) -> Option<PathBuf> { 277 path.clone() 278 .to_str() 279 .and_then(move |s| if s.contains('\x00') { None } else { Some(path) }) 280 } 281 282 /// Generate "simulated" native DWARF for functions lacking WASM-level DWARF. 283 pub fn generate_simulated_dwarf( 284 compilation: &mut Compilation<'_>, 285 addr_tr: &PrimaryMap<StaticModuleIndex, AddressTransform>, 286 translated: &HashSet<usize>, 287 out_encoding: gimli::Encoding, 288 vmctx_ptr_die_refs: &PrimaryMap<StaticModuleIndex, write::Reference>, 289 out_units: &mut write::UnitTable, 290 out_strings: &mut write::StringTable, 291 isa: &dyn TargetIsa, 292 ) -> Result<(), Error> { 293 let (wasm_file, path) = { 294 let di = &compilation.translations.iter().next().unwrap().1.debuginfo; 295 let path = di 296 .wasm_file 297 .path 298 .to_owned() 299 .and_then(check_invalid_chars_in_path) 300 .unwrap_or_else(|| autogenerate_dwarf_wasm_path(di)); 301 (&di.wasm_file, path) 302 }; 303 304 let (unit, root_id, file_id) = { 305 let comp_dir_id = out_strings.add(assert_dwarf_str!(path 306 .parent() 307 .context("path dir")? 308 .to_str() 309 .context("path dir encoding")?)); 310 let name = path 311 .file_name() 312 .context("path name")? 313 .to_str() 314 .context("path name encoding")?; 315 let name_id = out_strings.add(assert_dwarf_str!(name)); 316 317 let (out_program, file_id) = generate_line_info( 318 addr_tr, 319 translated, 320 out_encoding, 321 wasm_file, 322 comp_dir_id, 323 name_id, 324 name, 325 )?; 326 327 let unit_id = out_units.add(write::Unit::new(out_encoding, out_program)); 328 let unit = out_units.get_mut(unit_id); 329 330 let root_id = unit.root(); 331 let root = unit.get_mut(root_id); 332 333 let id = out_strings.add(PRODUCER_NAME); 334 root.set(gimli::DW_AT_producer, write::AttributeValue::StringRef(id)); 335 root.set(gimli::DW_AT_name, write::AttributeValue::StringRef(name_id)); 336 root.set( 337 gimli::DW_AT_stmt_list, 338 write::AttributeValue::LineProgramRef, 339 ); 340 root.set( 341 gimli::DW_AT_comp_dir, 342 write::AttributeValue::StringRef(comp_dir_id), 343 ); 344 (unit, root_id, file_id) 345 }; 346 347 let wasm_types = add_wasm_types(unit, root_id, out_strings); 348 for (module, index) in compilation.indexes().collect::<Vec<_>>() { 349 let (symbol, _) = compilation.function(module, index); 350 if translated.contains(&symbol) { 351 continue; 352 } 353 354 let addr_tr = &addr_tr[module]; 355 let map = &addr_tr.map()[index]; 356 let start = map.offset as u64; 357 let end = start + map.len as u64; 358 let die_id = unit.add(root_id, gimli::DW_TAG_subprogram); 359 let die = unit.get_mut(die_id); 360 die.set( 361 gimli::DW_AT_low_pc, 362 write::AttributeValue::Address(write::Address::Symbol { 363 symbol, 364 addend: start as i64, 365 }), 366 ); 367 die.set( 368 gimli::DW_AT_high_pc, 369 write::AttributeValue::Udata(end - start), 370 ); 371 372 let translation = &compilation.translations[module]; 373 let func_index = translation.module.func_index(index); 374 let di = &translation.debuginfo; 375 let id = match di 376 .name_section 377 .func_names 378 .get(&func_index) 379 .and_then(|s| check_invalid_chars_in_name(s)) 380 { 381 Some(n) => out_strings.add(assert_dwarf_str!(n)), 382 None => out_strings.add(format!("wasm-function[{}]", func_index.as_u32())), 383 }; 384 385 die.set(gimli::DW_AT_name, write::AttributeValue::StringRef(id)); 386 387 die.set( 388 gimli::DW_AT_decl_file, 389 write::AttributeValue::FileIndex(Some(file_id)), 390 ); 391 392 let f_start = map.addresses[0].wasm; 393 let wasm_offset = di.wasm_file.code_section_offset + f_start; 394 die.set( 395 gimli::DW_AT_decl_line, 396 write::AttributeValue::Udata(wasm_offset), 397 ); 398 399 let frame_info = compilation.function_frame_info(module, index); 400 let source_range = addr_tr.func_source_range(index); 401 generate_vars( 402 unit, 403 die_id, 404 addr_tr, 405 &frame_info, 406 &[(source_range.0, source_range.1)], 407 vmctx_ptr_die_refs[module], 408 &wasm_types, 409 &di.wasm_file.funcs[index.as_u32() as usize], 410 di.name_section.locals_names.get(&func_index), 411 out_strings, 412 isa, 413 )?; 414 } 415 416 Ok(()) 417 } 418