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