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