1 use super::address_transform::AddressTransform; 2 use super::attr::{clone_die_attributes, FileAttributeContext}; 3 use super::debug_transform_logging::{ 4 dbi_log, log_begin_input_die, log_end_output_die, log_end_output_die_skipped, 5 log_get_cu_summary, 6 }; 7 use super::expression::compile_expression; 8 use super::line_program::clone_line_program; 9 use super::range_info_builder::RangeInfoBuilder; 10 use super::refs::{PendingDebugInfoRefs, PendingUnitRefs, UnitRefsMap}; 11 use super::synthetic::ModuleSyntheticUnit; 12 use super::utils::append_vmctx_info; 13 use super::DebugInputContext; 14 use crate::debug::{Compilation, Reader}; 15 use anyhow::{Context, Error}; 16 use cranelift_codegen::ir::Endianness; 17 use cranelift_codegen::isa::TargetIsa; 18 use gimli::write; 19 use gimli::{AttributeValue, DebuggingInformationEntry, Dwarf, Unit}; 20 use std::collections::HashSet; 21 use wasmtime_environ::StaticModuleIndex; 22 use wasmtime_versioned_export_macros::versioned_stringify_ident; 23 24 struct InheritedAttr<T> { 25 stack: Vec<(usize, T)>, 26 } 27 28 impl<T> InheritedAttr<T> { 29 fn new() -> Self { 30 InheritedAttr { stack: Vec::new() } 31 } 32 33 fn update(&mut self, depth: usize) { 34 while !self.stack.is_empty() && self.stack.last().unwrap().0 >= depth { 35 self.stack.pop(); 36 } 37 } 38 39 fn push(&mut self, depth: usize, value: T) { 40 self.stack.push((depth, value)); 41 } 42 43 fn top(&self) -> Option<&T> { 44 self.stack.last().map(|entry| &entry.1) 45 } 46 47 fn is_empty(&self) -> bool { 48 self.stack.is_empty() 49 } 50 } 51 52 fn get_base_type_name( 53 type_entry: &DebuggingInformationEntry<Reader<'_>>, 54 unit: &Unit<Reader<'_>, usize>, 55 dwarf: &Dwarf<Reader<'_>>, 56 ) -> Result<String, Error> { 57 // FIXME remove recursion. 58 if let Some(AttributeValue::UnitRef(ref offset)) = type_entry.attr_value(gimli::DW_AT_type)? { 59 let mut entries = unit.entries_at_offset(*offset)?; 60 entries.next_entry()?; 61 if let Some(die) = entries.current() { 62 if let Some(value) = die.attr_value(gimli::DW_AT_name)? { 63 return Ok(String::from(dwarf.attr_string(unit, value)?.to_string()?)); 64 } 65 match die.tag() { 66 gimli::DW_TAG_const_type => { 67 return Ok(format!("const {}", get_base_type_name(die, unit, dwarf)?)); 68 } 69 gimli::DW_TAG_pointer_type => { 70 return Ok(format!("{}*", get_base_type_name(die, unit, dwarf)?)); 71 } 72 gimli::DW_TAG_reference_type => { 73 return Ok(format!("{}&", get_base_type_name(die, unit, dwarf)?)); 74 } 75 gimli::DW_TAG_array_type => { 76 return Ok(format!("{}[]", get_base_type_name(die, unit, dwarf)?)); 77 } 78 _ => (), 79 } 80 } 81 } 82 Ok(String::from("??")) 83 } 84 85 enum WebAssemblyPtrKind { 86 Reference, 87 Pointer, 88 } 89 90 /// Replaces WebAssembly pointer type DIE with the wrapper 91 /// which natively represented by offset in a Wasm memory. 92 /// 93 /// `pointer_type_entry` is a DW_TAG_pointer_type entry (e.g. `T*`), 94 /// which refers its base type (e.g. `T`), or is a 95 /// DW_TAG_reference_type (e.g. `T&`). 96 /// 97 /// The generated wrapper is a structure that contains only the 98 /// `__ptr` field. The utility operators overloads is added to 99 /// provide better debugging experience. 100 /// 101 /// Wrappers of pointer and reference types are identical except for 102 /// their name -- they are formatted and accessed from a debugger 103 /// the same way. 104 /// 105 /// Notice that "resolve_vmctx_memory_ptr" is external/builtin 106 /// subprogram that is not part of Wasm code. 107 fn replace_pointer_type( 108 parent_id: write::UnitEntryId, 109 kind: WebAssemblyPtrKind, 110 comp_unit: &mut write::Unit, 111 wasm_ptr_die_ref: write::Reference, 112 pointer_type_entry: &DebuggingInformationEntry<Reader<'_>>, 113 unit: &Unit<Reader<'_>, usize>, 114 dwarf: &Dwarf<Reader<'_>>, 115 out_strings: &mut write::StringTable, 116 pending_die_refs: &mut PendingUnitRefs, 117 ) -> Result<write::UnitEntryId, Error> { 118 const WASM_PTR_LEN: u8 = 4; 119 120 macro_rules! add_tag { 121 ($parent_id:ident, $tag:expr => $die:ident as $die_id:ident { $($a:path = $v:expr),* }) => { 122 let $die_id = comp_unit.add($parent_id, $tag); 123 #[allow(unused_variables, reason = "sometimes not used below")] 124 let $die = comp_unit.get_mut($die_id); 125 $( $die.set($a, $v); )* 126 }; 127 } 128 129 // Build DW_TAG_structure_type for the wrapper: 130 // .. DW_AT_name = "WebAssemblyPtrWrapper<T>", 131 // .. DW_AT_byte_size = 4, 132 let name = match kind { 133 WebAssemblyPtrKind::Pointer => format!( 134 "WebAssemblyPtrWrapper<{}>", 135 get_base_type_name(pointer_type_entry, unit, dwarf)? 136 ), 137 WebAssemblyPtrKind::Reference => format!( 138 "WebAssemblyRefWrapper<{}>", 139 get_base_type_name(pointer_type_entry, unit, dwarf)? 140 ), 141 }; 142 add_tag!(parent_id, gimli::DW_TAG_structure_type => wrapper_die as wrapper_die_id { 143 gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add(name.as_str())), 144 gimli::DW_AT_byte_size = write::AttributeValue::Data1(WASM_PTR_LEN) 145 }); 146 147 // Build DW_TAG_pointer_type for `WebAssemblyPtrWrapper<T>*`: 148 // .. DW_AT_type = <wrapper_die> 149 add_tag!(parent_id, gimli::DW_TAG_pointer_type => wrapper_ptr_type as wrapper_ptr_type_id { 150 gimli::DW_AT_type = write::AttributeValue::UnitRef(wrapper_die_id) 151 }); 152 153 let base_type_id = pointer_type_entry.attr_value(gimli::DW_AT_type)?; 154 // Build DW_TAG_reference_type for `T&`: 155 // .. DW_AT_type = <base_type> 156 add_tag!(parent_id, gimli::DW_TAG_reference_type => ref_type as ref_type_id {}); 157 if let Some(AttributeValue::UnitRef(ref offset)) = base_type_id { 158 pending_die_refs.insert(ref_type_id, gimli::DW_AT_type, *offset); 159 } 160 161 // Build DW_TAG_pointer_type for `T*`: 162 // .. DW_AT_type = <base_type> 163 add_tag!(parent_id, gimli::DW_TAG_pointer_type => ptr_type as ptr_type_id {}); 164 if let Some(AttributeValue::UnitRef(ref offset)) = base_type_id { 165 pending_die_refs.insert(ptr_type_id, gimli::DW_AT_type, *offset); 166 } 167 168 // Build wrapper_die's DW_TAG_template_type_parameter: 169 // .. DW_AT_name = "T" 170 // .. DW_AT_type = <base_type> 171 add_tag!(wrapper_die_id, gimli::DW_TAG_template_type_parameter => t_param_die as t_param_die_id { 172 gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("T")) 173 }); 174 if let Some(AttributeValue::UnitRef(ref offset)) = base_type_id { 175 pending_die_refs.insert(t_param_die_id, gimli::DW_AT_type, *offset); 176 } 177 178 // Build wrapper_die's DW_TAG_member for `__ptr`: 179 // .. DW_AT_name = "__ptr" 180 // .. DW_AT_type = <wp_die> 181 // .. DW_AT_location = 0 182 add_tag!(wrapper_die_id, gimli::DW_TAG_member => m_die as m_die_id { 183 gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("__ptr")), 184 gimli::DW_AT_type = write::AttributeValue::DebugInfoRef(wasm_ptr_die_ref), 185 gimli::DW_AT_data_member_location = write::AttributeValue::Data1(0) 186 }); 187 188 // Build wrapper_die's DW_TAG_subprogram for `ptr()`: 189 // .. DW_AT_linkage_name = "resolve_vmctx_memory_ptr" 190 // .. DW_AT_name = "ptr" 191 // .. DW_AT_type = <ptr_type> 192 // .. DW_TAG_formal_parameter 193 // .. .. DW_AT_type = <wrapper_ptr_type> 194 // .. .. DW_AT_artificial = 1 195 add_tag!(wrapper_die_id, gimli::DW_TAG_subprogram => deref_op_die as deref_op_die_id { 196 gimli::DW_AT_linkage_name = write::AttributeValue::StringRef(out_strings.add(versioned_stringify_ident!(resolve_vmctx_memory_ptr))), 197 gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("ptr")), 198 gimli::DW_AT_type = write::AttributeValue::UnitRef(ptr_type_id) 199 }); 200 add_tag!(deref_op_die_id, gimli::DW_TAG_formal_parameter => deref_op_this_param as deref_op_this_param_id { 201 gimli::DW_AT_type = write::AttributeValue::UnitRef(wrapper_ptr_type_id), 202 gimli::DW_AT_artificial = write::AttributeValue::Flag(true) 203 }); 204 205 // Build wrapper_die's DW_TAG_subprogram for `operator*`: 206 // .. DW_AT_linkage_name = "resolve_vmctx_memory_ptr" 207 // .. DW_AT_name = "operator*" 208 // .. DW_AT_type = <ref_type> 209 // .. DW_TAG_formal_parameter 210 // .. .. DW_AT_type = <wrapper_ptr_type> 211 // .. .. DW_AT_artificial = 1 212 add_tag!(wrapper_die_id, gimli::DW_TAG_subprogram => deref_op_die as deref_op_die_id { 213 gimli::DW_AT_linkage_name = write::AttributeValue::StringRef(out_strings.add(versioned_stringify_ident!(resolve_vmctx_memory_ptr))), 214 gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("operator*")), 215 gimli::DW_AT_type = write::AttributeValue::UnitRef(ref_type_id) 216 }); 217 add_tag!(deref_op_die_id, gimli::DW_TAG_formal_parameter => deref_op_this_param as deref_op_this_param_id { 218 gimli::DW_AT_type = write::AttributeValue::UnitRef(wrapper_ptr_type_id), 219 gimli::DW_AT_artificial = write::AttributeValue::Flag(true) 220 }); 221 222 // Build wrapper_die's DW_TAG_subprogram for `operator->`: 223 // .. DW_AT_linkage_name = "resolve_vmctx_memory_ptr" 224 // .. DW_AT_name = "operator->" 225 // .. DW_AT_type = <ptr_type> 226 // .. DW_TAG_formal_parameter 227 // .. .. DW_AT_type = <wrapper_ptr_type> 228 // .. .. DW_AT_artificial = 1 229 add_tag!(wrapper_die_id, gimli::DW_TAG_subprogram => deref_op_die as deref_op_die_id { 230 gimli::DW_AT_linkage_name = write::AttributeValue::StringRef(out_strings.add(versioned_stringify_ident!(resolve_vmctx_memory_ptr))), 231 gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("operator->")), 232 gimli::DW_AT_type = write::AttributeValue::UnitRef(ptr_type_id) 233 }); 234 add_tag!(deref_op_die_id, gimli::DW_TAG_formal_parameter => deref_op_this_param as deref_op_this_param_id { 235 gimli::DW_AT_type = write::AttributeValue::UnitRef(wrapper_ptr_type_id), 236 gimli::DW_AT_artificial = write::AttributeValue::Flag(true) 237 }); 238 239 Ok(wrapper_die_id) 240 } 241 242 fn is_dead_code(entry: &DebuggingInformationEntry<Reader<'_>>) -> bool { 243 const TOMBSTONE: u64 = u32::MAX as u64; 244 245 match entry.attr_value(gimli::DW_AT_low_pc) { 246 Ok(Some(AttributeValue::Addr(addr))) => addr == TOMBSTONE, 247 _ => false, 248 } 249 } 250 251 pub(crate) fn clone_unit( 252 compilation: &mut Compilation<'_>, 253 module: StaticModuleIndex, 254 skeleton_unit: &Unit<Reader<'_>, usize>, 255 split_unit: Option<&Unit<Reader<'_>, usize>>, 256 split_dwarf: Option<&Dwarf<Reader<'_>>>, 257 context: &DebugInputContext, 258 addr_tr: &AddressTransform, 259 out_encoding: gimli::Encoding, 260 out_module_synthetic_unit: &ModuleSyntheticUnit, 261 out_units: &mut write::UnitTable, 262 out_strings: &mut write::StringTable, 263 translated: &mut HashSet<usize>, 264 isa: &dyn TargetIsa, 265 ) -> Result<Option<(write::UnitId, UnitRefsMap, PendingDebugInfoRefs)>, Error> { 266 let mut die_ref_map = UnitRefsMap::new(); 267 let mut pending_die_refs = PendingUnitRefs::new(); 268 let mut pending_di_refs = PendingDebugInfoRefs::new(); 269 let mut stack = Vec::new(); 270 271 let skeleton_dwarf = &compilation.translations[module].debuginfo.dwarf; 272 273 // Iterate over all of this compilation unit's entries. 274 let dwarf = split_dwarf.unwrap_or(skeleton_dwarf); 275 let unit = split_unit.unwrap_or(skeleton_unit); 276 let mut entries = unit.entries(); 277 dbi_log!("Cloning CU {:?}", log_get_cu_summary(unit)); 278 279 let (mut out_unit, out_unit_id, file_map, file_index_base) = if let Some((depth_delta, entry)) = 280 entries.next_dfs()? 281 { 282 assert_eq!(depth_delta, 0); 283 let (out_line_program, debug_line_offset, file_map, file_index_base) = clone_line_program( 284 skeleton_dwarf, 285 skeleton_unit, 286 unit.name, 287 addr_tr, 288 out_encoding, 289 out_strings, 290 )?; 291 292 if entry.tag() == gimli::DW_TAG_compile_unit { 293 log_begin_input_die(dwarf, unit, entry, 0); 294 let out_unit_id = out_units.add(write::Unit::new(out_encoding, out_line_program)); 295 let out_unit = out_units.get_mut(out_unit_id); 296 297 let out_root_id = out_unit.root(); 298 die_ref_map.insert(entry.offset(), out_root_id); 299 300 clone_die_attributes( 301 dwarf, 302 &unit, 303 entry, 304 addr_tr, 305 None, 306 out_unit, 307 out_root_id, 308 None, 309 None, 310 out_strings, 311 &mut pending_die_refs, 312 &mut pending_di_refs, 313 FileAttributeContext::Root(Some(debug_line_offset)), 314 isa, 315 )?; 316 if split_unit.is_some() { 317 if let Some((_, skeleton_entry)) = skeleton_unit.entries().next_dfs()? { 318 clone_die_attributes( 319 skeleton_dwarf, 320 skeleton_unit, 321 skeleton_entry, 322 addr_tr, 323 None, 324 out_unit, 325 out_root_id, 326 None, 327 None, 328 out_strings, 329 &mut pending_die_refs, 330 &mut pending_di_refs, 331 FileAttributeContext::Root(Some(debug_line_offset)), 332 isa, 333 )?; 334 } 335 } 336 337 log_end_output_die(entry, unit, out_root_id, out_unit, out_strings, 0); 338 stack.push(out_root_id); 339 (out_unit, out_unit_id, file_map, file_index_base) 340 } else { 341 // Can happen when the DWARF is split and we dont have the package/dwo files. 342 // This is a better user experience than errorring. 343 dbi_log!("... skipped: split DW_TAG_compile_unit entry missing"); 344 return Ok(None); // empty: 345 } 346 } else { 347 dbi_log!("... skipped: empty CU (no DW_TAG_compile_unit entry)"); 348 return Ok(None); // empty 349 }; 350 let mut current_depth = 0; 351 let mut skip_at_depth = None; 352 let mut current_frame_base = InheritedAttr::new(); 353 let mut current_value_range = InheritedAttr::new(); 354 let mut current_scope_ranges = InheritedAttr::new(); 355 while let Some((depth_delta, entry)) = entries.next_dfs()? { 356 current_depth += depth_delta; 357 log_begin_input_die(dwarf, unit, entry, current_depth); 358 359 // If `skip_at_depth` is `Some` then we previously decided to skip over 360 // a node and all it's children. Let A be the last node processed, B be 361 // the first node skipped, C be previous node, and D the current node. 362 // Then `cached` is the difference from A to B, `depth` is the difference 363 // from B to C, and `depth_delta` is the differenc from C to D. 364 let depth_delta = if let Some((depth, cached)) = skip_at_depth { 365 // `new_depth` = B to D 366 let new_depth = depth + depth_delta; 367 // if D is below B continue to skip 368 if new_depth > 0 { 369 skip_at_depth = Some((new_depth, cached)); 370 log_end_output_die_skipped(entry, unit, "unreachable", current_depth); 371 continue; 372 } 373 // otherwise process D with `depth_delta` being the difference from A to D 374 skip_at_depth = None; 375 new_depth + cached 376 } else { 377 depth_delta 378 }; 379 380 if !context 381 .reachable 382 .contains(&entry.offset().to_unit_section_offset(&unit)) 383 || is_dead_code(&entry) 384 { 385 // entry is not reachable: discarding all its info. 386 // Here B = C so `depth` is 0. A is the previous node so `cached` = 387 // `depth_delta`. 388 skip_at_depth = Some((0, depth_delta)); 389 log_end_output_die_skipped(entry, unit, "unreachable", current_depth); 390 continue; 391 } 392 393 let new_stack_len = stack.len().wrapping_add(depth_delta as usize); 394 current_frame_base.update(new_stack_len); 395 current_scope_ranges.update(new_stack_len); 396 current_value_range.update(new_stack_len); 397 let range_builder = if entry.tag() == gimli::DW_TAG_subprogram { 398 let range_builder = 399 RangeInfoBuilder::from_subprogram_die(dwarf, &unit, entry, addr_tr)?; 400 if let RangeInfoBuilder::Function(func) = range_builder { 401 let frame_info = compilation.function_frame_info(module, func); 402 current_value_range.push(new_stack_len, frame_info); 403 let (symbol, _) = compilation.function(module, func); 404 translated.insert(symbol); 405 current_scope_ranges.push(new_stack_len, range_builder.get_ranges(addr_tr)); 406 Some(range_builder) 407 } else { 408 // FIXME current_scope_ranges.push() 409 None 410 } 411 } else { 412 let high_pc = entry.attr_value(gimli::DW_AT_high_pc)?; 413 let ranges = entry.attr_value(gimli::DW_AT_ranges)?; 414 if high_pc.is_some() || ranges.is_some() { 415 let range_builder = RangeInfoBuilder::from(dwarf, &unit, entry)?; 416 current_scope_ranges.push(new_stack_len, range_builder.get_ranges(addr_tr)); 417 Some(range_builder) 418 } else { 419 None 420 } 421 }; 422 423 if depth_delta <= 0 { 424 for _ in depth_delta..1 { 425 stack.pop(); 426 } 427 } else { 428 assert_eq!(depth_delta, 1); 429 } 430 431 if let Some(AttributeValue::Exprloc(expr)) = entry.attr_value(gimli::DW_AT_frame_base)? { 432 if let Some(expr) = compile_expression(&expr, unit.encoding(), None)? { 433 current_frame_base.push(new_stack_len, expr); 434 } 435 } 436 437 let parent = stack.last().unwrap(); 438 439 if entry.tag() == gimli::DW_TAG_pointer_type || entry.tag() == gimli::DW_TAG_reference_type 440 { 441 // Wrap pointer types. 442 let pointer_kind = match entry.tag() { 443 gimli::DW_TAG_pointer_type => WebAssemblyPtrKind::Pointer, 444 gimli::DW_TAG_reference_type => WebAssemblyPtrKind::Reference, 445 _ => panic!(), 446 }; 447 let die_id = replace_pointer_type( 448 *parent, 449 pointer_kind, 450 out_unit, 451 out_module_synthetic_unit.wasm_ptr_die_ref(), 452 entry, 453 unit, 454 dwarf, 455 out_strings, 456 &mut pending_die_refs, 457 )?; 458 stack.push(die_id); 459 assert_eq!(stack.len(), new_stack_len); 460 die_ref_map.insert(entry.offset(), die_id); 461 log_end_output_die(entry, unit, die_id, out_unit, out_strings, current_depth); 462 continue; 463 } 464 465 let out_die_id = out_unit.add(*parent, entry.tag()); 466 467 stack.push(out_die_id); 468 assert_eq!(stack.len(), new_stack_len); 469 die_ref_map.insert(entry.offset(), out_die_id); 470 471 clone_die_attributes( 472 dwarf, 473 &unit, 474 entry, 475 addr_tr, 476 current_value_range.top(), 477 &mut out_unit, 478 out_die_id, 479 range_builder, 480 current_scope_ranges.top(), 481 out_strings, 482 &mut pending_die_refs, 483 &mut pending_di_refs, 484 FileAttributeContext::Children { 485 file_map: &file_map, 486 file_index_base, 487 frame_base: current_frame_base.top(), 488 }, 489 isa, 490 )?; 491 492 // Data in WebAssembly memory always uses little-endian byte order. 493 // If the native architecture is big-endian, we need to mark all 494 // base types used to refer to WebAssembly memory as little-endian 495 // using the DW_AT_endianity attribute, so that the debugger will 496 // be able to correctly access them. 497 if entry.tag() == gimli::DW_TAG_base_type && isa.endianness() == Endianness::Big { 498 let current_scope = out_unit.get_mut(out_die_id); 499 current_scope.set( 500 gimli::DW_AT_endianity, 501 write::AttributeValue::Endianity(gimli::DW_END_little), 502 ); 503 } 504 505 if entry.tag() == gimli::DW_TAG_subprogram && !current_scope_ranges.is_empty() { 506 append_vmctx_info( 507 out_unit, 508 out_die_id, 509 out_module_synthetic_unit.vmctx_ptr_die_ref(), 510 addr_tr, 511 current_value_range.top(), 512 current_scope_ranges.top().context("range")?, 513 out_strings, 514 isa, 515 )?; 516 } 517 518 log_end_output_die( 519 entry, 520 unit, 521 out_die_id, 522 out_unit, 523 out_strings, 524 current_depth, 525 ); 526 } 527 die_ref_map.patch(pending_die_refs, out_unit); 528 Ok(Some((out_unit_id, die_ref_map, pending_di_refs))) 529 } 530