1 use super::address_transform::AddressTransform; 2 use super::attr::{clone_die_attributes, FileAttributeContext}; 3 use super::expression::compile_expression; 4 use super::line_program::clone_line_program; 5 use super::range_info_builder::RangeInfoBuilder; 6 use super::refs::{PendingDebugInfoRefs, PendingUnitRefs, UnitRefsMap}; 7 use super::utils::{add_internal_types, append_vmctx_info, get_function_frame_info}; 8 use super::{DebugInputContext, Reader}; 9 use crate::debug::ModuleMemoryOffset; 10 use crate::CompiledFunctionsMetadata; 11 use anyhow::{Context, Error}; 12 use cranelift_codegen::ir::Endianness; 13 use cranelift_codegen::isa::TargetIsa; 14 use gimli::write; 15 use gimli::{AttributeValue, DebuggingInformationEntry, Unit}; 16 use std::collections::HashSet; 17 use wasmtime_environ::DefinedFuncIndex; 18 use wasmtime_versioned_export_macros::versioned_stringify_ident; 19 20 struct InheritedAttr<T> { 21 stack: Vec<(usize, T)>, 22 } 23 24 impl<T> InheritedAttr<T> { 25 fn new() -> Self { 26 InheritedAttr { stack: Vec::new() } 27 } 28 29 fn update(&mut self, depth: usize) { 30 while !self.stack.is_empty() && self.stack.last().unwrap().0 >= depth { 31 self.stack.pop(); 32 } 33 } 34 35 fn push(&mut self, depth: usize, value: T) { 36 self.stack.push((depth, value)); 37 } 38 39 fn top(&self) -> Option<&T> { 40 self.stack.last().map(|entry| &entry.1) 41 } 42 43 fn is_empty(&self) -> bool { 44 self.stack.is_empty() 45 } 46 } 47 48 fn get_base_type_name<R>( 49 type_entry: &DebuggingInformationEntry<R>, 50 unit: &Unit<R, R::Offset>, 51 context: &DebugInputContext<R>, 52 ) -> Result<String, Error> 53 where 54 R: Reader, 55 { 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(AttributeValue::DebugStrRef(str_offset)) = 62 die.attr_value(gimli::DW_AT_name)? 63 { 64 return Ok(String::from( 65 context.debug_str.get_str(str_offset)?.to_string()?, 66 )); 67 } 68 match die.tag() { 69 gimli::DW_TAG_const_type => { 70 return Ok(format!("const {}", get_base_type_name(die, unit, context)?)); 71 } 72 gimli::DW_TAG_pointer_type => { 73 return Ok(format!("{}*", get_base_type_name(die, unit, context)?)); 74 } 75 gimli::DW_TAG_reference_type => { 76 return Ok(format!("{}&", get_base_type_name(die, unit, context)?)); 77 } 78 gimli::DW_TAG_array_type => { 79 return Ok(format!("{}[]", get_base_type_name(die, unit, context)?)); 80 } 81 _ => (), 82 } 83 } 84 } 85 Ok(String::from("??")) 86 } 87 88 enum WebAssemblyPtrKind { 89 Reference, 90 Pointer, 91 } 92 93 /// Replaces WebAssembly pointer type DIE with the wrapper 94 /// which natively represented by offset in a Wasm memory. 95 /// 96 /// `pointer_type_entry` is a DW_TAG_pointer_type entry (e.g. `T*`), 97 /// which refers its base type (e.g. `T`), or is a 98 /// DW_TAG_reference_type (e.g. `T&`). 99 /// 100 /// The generated wrapper is a structure that contains only the 101 /// `__ptr` field. The utility operators overloads is added to 102 /// provide better debugging experience. 103 /// 104 /// Wrappers of pointer and reference types are identical except for 105 /// their name -- they are formatted and accessed from a debugger 106 /// the same way. 107 /// 108 /// Notice that "resolve_vmctx_memory_ptr" is external/builtin 109 /// subprogram that is not part of Wasm code. 110 fn replace_pointer_type<R>( 111 parent_id: write::UnitEntryId, 112 kind: WebAssemblyPtrKind, 113 comp_unit: &mut write::Unit, 114 wp_die_id: write::UnitEntryId, 115 pointer_type_entry: &DebuggingInformationEntry<R>, 116 unit: &Unit<R, R::Offset>, 117 context: &DebugInputContext<R>, 118 out_strings: &mut write::StringTable, 119 pending_die_refs: &mut PendingUnitRefs, 120 ) -> Result<write::UnitEntryId, Error> 121 where 122 R: Reader, 123 { 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)] 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, context)? 142 ), 143 WebAssemblyPtrKind::Reference => format!( 144 "WebAssemblyRefWrapper<{}>", 145 get_base_type_name(pointer_type_entry, unit, context)? 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::UnitRef(wp_die_id), 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 = "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!(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 = "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!(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 = "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!(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<R: Reader>(entry: &DebuggingInformationEntry<R>) -> 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<'a, R>( 258 dwarf: &gimli::Dwarf<R>, 259 unit: &Unit<R, R::Offset>, 260 split_unit: Option<&Unit<R, R::Offset>>, 261 split_dwarf: Option<&gimli::Dwarf<R>>, 262 context: &DebugInputContext<R>, 263 addr_tr: &'a AddressTransform, 264 funcs: &'a CompiledFunctionsMetadata, 265 memory_offset: &ModuleMemoryOffset, 266 out_encoding: gimli::Encoding, 267 out_units: &mut write::UnitTable, 268 out_strings: &mut write::StringTable, 269 translated: &mut HashSet<DefinedFuncIndex>, 270 isa: &dyn TargetIsa, 271 ) -> Result<Option<(write::UnitId, UnitRefsMap, PendingDebugInfoRefs)>, Error> 272 where 273 R: Reader, 274 { 275 let mut die_ref_map = UnitRefsMap::new(); 276 let mut pending_die_refs = PendingUnitRefs::new(); 277 let mut pending_di_refs = PendingDebugInfoRefs::new(); 278 let mut stack = Vec::new(); 279 280 let mut program_unit = unit; 281 let mut skeleton_die = None; 282 283 // Get entries in outer scope to avoid borrowing on short lived temporary. 284 let mut skeleton_entries = unit.entries(); 285 if let Some(unit) = split_unit { 286 program_unit = unit; 287 288 // From the spec, a skeleton unit has no children so we can assume the first, and only, entry is the DW_TAG_skeleton_unit (https://dwarfstd.org/doc/DWARF5.pdf). 289 if let Some(die_tuple) = skeleton_entries.next_dfs()? { 290 skeleton_die = Some(die_tuple.1); 291 } 292 } 293 294 // Iterate over all of this compilation unit's entries. 295 let mut entries = program_unit.entries(); 296 let (mut comp_unit, unit_id, file_map, file_index_base, cu_low_pc, wp_die_id, vmctx_die_id) = 297 if let Some((depth_delta, entry)) = entries.next_dfs()? { 298 assert_eq!(depth_delta, 0); 299 let (out_line_program, debug_line_offset, file_map, file_index_base) = 300 clone_line_program( 301 split_dwarf.unwrap_or(dwarf), 302 dwarf, 303 program_unit, 304 entry, 305 skeleton_die, 306 addr_tr, 307 out_encoding, 308 context.debug_str, 309 context.debug_line, 310 out_strings, 311 )?; 312 313 if entry.tag() == gimli::DW_TAG_compile_unit { 314 let unit_id = out_units.add(write::Unit::new(out_encoding, out_line_program)); 315 let comp_unit = out_units.get_mut(unit_id); 316 317 let root_id = comp_unit.root(); 318 die_ref_map.insert(entry.offset(), root_id); 319 let cu_low_pc = unit.low_pc; 320 321 clone_die_attributes( 322 split_dwarf.unwrap_or(dwarf), 323 &program_unit, 324 entry, 325 context, 326 addr_tr, 327 None, 328 comp_unit, 329 root_id, 330 None, 331 None, 332 cu_low_pc, 333 out_strings, 334 &mut pending_die_refs, 335 &mut pending_di_refs, 336 FileAttributeContext::Root(Some(debug_line_offset)), 337 isa, 338 )?; 339 340 let (wp_die_id, vmctx_die_id) = 341 add_internal_types(comp_unit, root_id, out_strings, memory_offset); 342 343 stack.push(root_id); 344 ( 345 comp_unit, 346 unit_id, 347 file_map, 348 file_index_base, 349 cu_low_pc, 350 wp_die_id, 351 vmctx_die_id, 352 ) 353 } else { 354 // Can happen when the DWARF is split and we dont have the package/dwo files. 355 // This is a better user experience than errorring. 356 return Ok(None); // empty: 357 } 358 } else { 359 return Ok(None); // empty 360 }; 361 let mut skip_at_depth = None; 362 let mut current_frame_base = InheritedAttr::new(); 363 let mut current_value_range = InheritedAttr::new(); 364 let mut current_scope_ranges = InheritedAttr::new(); 365 while let Some((depth_delta, entry)) = entries.next_dfs()? { 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 diffence 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 continue; 378 } 379 // otherwise process D with `depth_delta` being the difference from A to D 380 skip_at_depth = None; 381 new_depth + cached 382 } else { 383 depth_delta 384 }; 385 386 if !context 387 .reachable 388 .contains(&entry.offset().to_unit_section_offset(&unit)) 389 || is_dead_code(&entry) 390 { 391 // entry is not reachable: discarding all its info. 392 // Here B = C so `depth` is 0. A is the previous node so `cached` = 393 // `depth_delta`. 394 skip_at_depth = Some((0, depth_delta)); 395 continue; 396 } 397 398 let new_stack_len = stack.len().wrapping_add(depth_delta as usize); 399 current_frame_base.update(new_stack_len); 400 current_scope_ranges.update(new_stack_len); 401 current_value_range.update(new_stack_len); 402 let range_builder = if entry.tag() == gimli::DW_TAG_subprogram { 403 let range_builder = RangeInfoBuilder::from_subprogram_die( 404 dwarf, &unit, entry, context, addr_tr, cu_low_pc, 405 )?; 406 if let RangeInfoBuilder::Function(func_index) = range_builder { 407 if let Some(frame_info) = get_function_frame_info(memory_offset, funcs, func_index) 408 { 409 current_value_range.push(new_stack_len, frame_info); 410 } 411 translated.insert(func_index); 412 current_scope_ranges.push(new_stack_len, range_builder.get_ranges(addr_tr)); 413 Some(range_builder) 414 } else { 415 // FIXME current_scope_ranges.push() 416 None 417 } 418 } else { 419 let high_pc = entry.attr_value(gimli::DW_AT_high_pc)?; 420 let ranges = entry.attr_value(gimli::DW_AT_ranges)?; 421 if high_pc.is_some() || ranges.is_some() { 422 let range_builder = 423 RangeInfoBuilder::from(dwarf, &unit, entry, context, cu_low_pc)?; 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 comp_unit, 459 wp_die_id, 460 entry, 461 &unit, 462 context, 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 continue; 470 } 471 472 let die_id = comp_unit.add(*parent, entry.tag()); 473 474 stack.push(die_id); 475 assert_eq!(stack.len(), new_stack_len); 476 die_ref_map.insert(entry.offset(), die_id); 477 478 clone_die_attributes( 479 split_dwarf.unwrap_or(dwarf), 480 &unit, 481 entry, 482 context, 483 addr_tr, 484 current_value_range.top(), 485 &mut comp_unit, 486 die_id, 487 range_builder, 488 current_scope_ranges.top(), 489 cu_low_pc, 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 = comp_unit.get_mut(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 comp_unit, 517 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 die_ref_map.patch(pending_die_refs, comp_unit); 528 Ok(Some((unit_id, die_ref_map, pending_di_refs))) 529 } 530