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