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