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