Path: blob/main/crates/cranelift/src/debug/transform/unit.rs
1693 views
use super::DebugInputContext;1use super::address_transform::AddressTransform;2use super::attr::{EntryAttributesContext, clone_die_attributes};3use super::debug_transform_logging::{4dbi_log, log_begin_input_die, log_end_output_die, log_end_output_die_skipped,5log_get_cu_summary,6};7use super::expression::compile_expression;8use super::line_program::clone_line_program;9use super::range_info_builder::RangeInfoBuilder;10use super::refs::{PendingDebugInfoRefs, PendingUnitRefs, UnitRefsMap};11use super::synthetic::ModuleSyntheticUnit;12use super::utils::{append_vmctx_info, resolve_die_ref};13use crate::debug::{Compilation, Reader};14use anyhow::{Context, Error};15use cranelift_codegen::ir::Endianness;16use cranelift_codegen::isa::TargetIsa;17use gimli::write;18use gimli::{AttributeValue, DebuggingInformationEntry, Dwarf, Unit};19use std::collections::HashSet;20use wasmtime_environ::StaticModuleIndex;21use wasmtime_versioned_export_macros::versioned_stringify_ident;2223#[derive(Debug)]24pub struct InheritedAttr<T> {25stack: Vec<(usize, T)>,26}2728impl<T> InheritedAttr<T> {29fn new() -> Self {30InheritedAttr { stack: Vec::new() }31}3233fn update(&mut self, depth: usize) {34while !self.stack.is_empty() && self.stack.last().unwrap().0 >= depth {35self.stack.pop();36}37}3839pub fn push(&mut self, depth: usize, value: T) {40self.stack.push((depth, value));41}4243pub fn top(&self) -> Option<&T> {44self.stack.last().map(|entry| &entry.1)45}4647pub fn top_with_depth_mut(&mut self, depth: usize) -> Option<&mut T> {48self.stack49.last_mut()50.filter(|entry| entry.0 == depth)51.map(|entry| &mut entry.1)52}5354fn is_empty(&self) -> bool {55self.stack.is_empty()56}57}5859fn get_base_type_name(60type_entry: &DebuggingInformationEntry<Reader<'_>>,61unit: &Unit<Reader<'_>>,62dwarf: &Dwarf<Reader<'_>>,63) -> Result<String, Error> {64// FIXME remove recursion.65if let Some(die_ref) = type_entry.attr_value(gimli::DW_AT_type)? {66if let Some(ref die) = resolve_die_ref(unit, &die_ref)? {67if let Some(value) = die.attr_value(gimli::DW_AT_name)? {68return Ok(String::from(dwarf.attr_string(unit, value)?.to_string()?));69}70match die.tag() {71gimli::DW_TAG_const_type => {72return Ok(format!("const {}", get_base_type_name(die, unit, dwarf)?));73}74gimli::DW_TAG_pointer_type => {75return Ok(format!("{}*", get_base_type_name(die, unit, dwarf)?));76}77gimli::DW_TAG_reference_type => {78return Ok(format!("{}&", get_base_type_name(die, unit, dwarf)?));79}80gimli::DW_TAG_array_type => {81return Ok(format!("{}[]", get_base_type_name(die, unit, dwarf)?));82}83_ => (),84}85}86}87Ok(String::from("??"))88}8990enum WebAssemblyPtrKind {91Reference,92Pointer,93}9495/// Replaces WebAssembly pointer type DIE with the wrapper96/// 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 a100/// DW_TAG_reference_type (e.g. `T&`).101///102/// The generated wrapper is a structure that contains only the103/// `__ptr` field. The utility operators overloads is added to104/// provide better debugging experience.105///106/// Wrappers of pointer and reference types are identical except for107/// their name -- they are formatted and accessed from a debugger108/// the same way.109///110/// Notice that "resolve_vmctx_memory_ptr" is external/builtin111/// subprogram that is not part of Wasm code.112fn replace_pointer_type(113parent_id: write::UnitEntryId,114kind: WebAssemblyPtrKind,115comp_unit: &mut write::Unit,116wasm_ptr_die_ref: write::Reference,117pointer_type_entry: &DebuggingInformationEntry<Reader<'_>>,118unit: &Unit<Reader<'_>, usize>,119dwarf: &Dwarf<Reader<'_>>,120out_strings: &mut write::StringTable,121pending_die_refs: &mut PendingUnitRefs,122) -> Result<write::UnitEntryId, Error> {123const WASM_PTR_LEN: u8 = 4;124125macro_rules! add_tag {126($parent_id:ident, $tag:expr => $die:ident as $die_id:ident { $($a:path = $v:expr),* }) => {127let $die_id = comp_unit.add($parent_id, $tag);128#[allow(unused_variables, reason = "sometimes not used below")]129let $die = comp_unit.get_mut($die_id);130$( $die.set($a, $v); )*131};132}133134// Build DW_TAG_structure_type for the wrapper:135// .. DW_AT_name = "WebAssemblyPtrWrapper<T>",136// .. DW_AT_byte_size = 4,137let name = match kind {138WebAssemblyPtrKind::Pointer => format!(139"WebAssemblyPtrWrapper<{}>",140get_base_type_name(pointer_type_entry, unit, dwarf)?141),142WebAssemblyPtrKind::Reference => format!(143"WebAssemblyRefWrapper<{}>",144get_base_type_name(pointer_type_entry, unit, dwarf)?145),146};147add_tag!(parent_id, gimli::DW_TAG_structure_type => wrapper_die as wrapper_die_id {148gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add(name.as_str())),149gimli::DW_AT_byte_size = write::AttributeValue::Data1(WASM_PTR_LEN)150});151152// Build DW_TAG_pointer_type for `WebAssemblyPtrWrapper<T>*`:153// .. DW_AT_type = <wrapper_die>154add_tag!(parent_id, gimli::DW_TAG_pointer_type => wrapper_ptr_type as wrapper_ptr_type_id {155gimli::DW_AT_type = write::AttributeValue::UnitRef(wrapper_die_id)156});157158let 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>161add_tag!(parent_id, gimli::DW_TAG_reference_type => ref_type as ref_type_id {});162if let Some(AttributeValue::UnitRef(ref offset)) = base_type_id {163pending_die_refs.insert(ref_type_id, gimli::DW_AT_type, *offset);164}165166// Build DW_TAG_pointer_type for `T*`:167// .. DW_AT_type = <base_type>168add_tag!(parent_id, gimli::DW_TAG_pointer_type => ptr_type as ptr_type_id {});169if let Some(AttributeValue::UnitRef(ref offset)) = base_type_id {170pending_die_refs.insert(ptr_type_id, gimli::DW_AT_type, *offset);171}172173// Build wrapper_die's DW_TAG_template_type_parameter:174// .. DW_AT_name = "T"175// .. DW_AT_type = <base_type>176add_tag!(wrapper_die_id, gimli::DW_TAG_template_type_parameter => t_param_die as t_param_die_id {177gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("T"))178});179if let Some(AttributeValue::UnitRef(ref offset)) = base_type_id {180pending_die_refs.insert(t_param_die_id, gimli::DW_AT_type, *offset);181}182183// Build wrapper_die's DW_TAG_member for `__ptr`:184// .. DW_AT_name = "__ptr"185// .. DW_AT_type = <wp_die>186// .. DW_AT_location = 0187add_tag!(wrapper_die_id, gimli::DW_TAG_member => m_die as m_die_id {188gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("__ptr")),189gimli::DW_AT_type = write::AttributeValue::DebugInfoRef(wasm_ptr_die_ref),190gimli::DW_AT_data_member_location = write::AttributeValue::Data1(0)191});192193// Build wrapper_die's DW_TAG_subprogram for `ptr()`:194// .. DW_AT_linkage_name = "wasmtime_resolve_vmctx_memory_ptr"195// .. DW_AT_name = "ptr"196// .. DW_AT_type = <ptr_type>197// .. DW_TAG_formal_parameter198// .. .. DW_AT_type = <wrapper_ptr_type>199// .. .. DW_AT_artificial = 1200add_tag!(wrapper_die_id, gimli::DW_TAG_subprogram => deref_op_die as deref_op_die_id {201gimli::DW_AT_linkage_name = write::AttributeValue::StringRef(out_strings.add(versioned_stringify_ident!(wasmtime_resolve_vmctx_memory_ptr))),202gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("ptr")),203gimli::DW_AT_type = write::AttributeValue::UnitRef(ptr_type_id)204});205add_tag!(deref_op_die_id, gimli::DW_TAG_formal_parameter => deref_op_this_param as deref_op_this_param_id {206gimli::DW_AT_type = write::AttributeValue::UnitRef(wrapper_ptr_type_id),207gimli::DW_AT_artificial = write::AttributeValue::Flag(true)208});209210// Build wrapper_die's DW_TAG_subprogram for `operator*`:211// .. DW_AT_linkage_name = "wasmtime_resolve_vmctx_memory_ptr"212// .. DW_AT_name = "operator*"213// .. DW_AT_type = <ref_type>214// .. DW_TAG_formal_parameter215// .. .. DW_AT_type = <wrapper_ptr_type>216// .. .. DW_AT_artificial = 1217add_tag!(wrapper_die_id, gimli::DW_TAG_subprogram => deref_op_die as deref_op_die_id {218gimli::DW_AT_linkage_name = write::AttributeValue::StringRef(out_strings.add(versioned_stringify_ident!(wasmtime_resolve_vmctx_memory_ptr))),219gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("operator*")),220gimli::DW_AT_type = write::AttributeValue::UnitRef(ref_type_id)221});222add_tag!(deref_op_die_id, gimli::DW_TAG_formal_parameter => deref_op_this_param as deref_op_this_param_id {223gimli::DW_AT_type = write::AttributeValue::UnitRef(wrapper_ptr_type_id),224gimli::DW_AT_artificial = write::AttributeValue::Flag(true)225});226227// Build wrapper_die's DW_TAG_subprogram for `operator->`:228// .. DW_AT_linkage_name = "wasmtime_resolve_vmctx_memory_ptr"229// .. DW_AT_name = "operator->"230// .. DW_AT_type = <ptr_type>231// .. DW_TAG_formal_parameter232// .. .. DW_AT_type = <wrapper_ptr_type>233// .. .. DW_AT_artificial = 1234add_tag!(wrapper_die_id, gimli::DW_TAG_subprogram => deref_op_die as deref_op_die_id {235gimli::DW_AT_linkage_name = write::AttributeValue::StringRef(out_strings.add(versioned_stringify_ident!(wasmtime_resolve_vmctx_memory_ptr))),236gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("operator->")),237gimli::DW_AT_type = write::AttributeValue::UnitRef(ptr_type_id)238});239add_tag!(deref_op_die_id, gimli::DW_TAG_formal_parameter => deref_op_this_param as deref_op_this_param_id {240gimli::DW_AT_type = write::AttributeValue::UnitRef(wrapper_ptr_type_id),241gimli::DW_AT_artificial = write::AttributeValue::Flag(true)242});243244Ok(wrapper_die_id)245}246247pub(crate) fn clone_unit(248compilation: &mut Compilation<'_>,249module: StaticModuleIndex,250skeleton_unit: &Unit<Reader<'_>>,251split_unit: Option<&Unit<Reader<'_>>>,252split_dwarf: Option<&Dwarf<Reader<'_>>>,253context: &DebugInputContext,254addr_tr: &AddressTransform,255out_encoding: gimli::Encoding,256out_module_synthetic_unit: &ModuleSyntheticUnit,257out_units: &mut write::UnitTable,258out_strings: &mut write::StringTable,259translated: &mut HashSet<usize>,260isa: &dyn TargetIsa,261) -> Result<Option<(write::UnitId, UnitRefsMap, PendingDebugInfoRefs)>, Error> {262let mut die_ref_map = UnitRefsMap::new();263let mut pending_die_refs = PendingUnitRefs::new();264let mut pending_di_refs = PendingDebugInfoRefs::new();265let mut stack = Vec::new();266267let skeleton_dwarf = &compilation.translations[module].debuginfo.dwarf;268269// Iterate over all of this compilation unit's entries.270let dwarf = split_dwarf.unwrap_or(skeleton_dwarf);271let unit = split_unit.unwrap_or(skeleton_unit);272let mut entries = unit.entries();273dbi_log!("Cloning CU {:?}", log_get_cu_summary(unit));274275let (mut out_unit, out_unit_id, file_map, file_index_base) = if let Some((depth_delta, entry)) =276entries.next_dfs()?277{278assert_eq!(depth_delta, 0);279let (out_line_program, debug_line_offset, file_map, file_index_base) = clone_line_program(280skeleton_dwarf,281skeleton_unit,282unit.name,283addr_tr,284out_encoding,285out_strings,286)?;287288if entry.tag() == gimli::DW_TAG_compile_unit {289log_begin_input_die(dwarf, unit, entry, 0);290let out_unit_id = out_units.add(write::Unit::new(out_encoding, out_line_program));291let out_unit = out_units.get_mut(out_unit_id);292293let out_root_id = out_unit.root();294die_ref_map.insert(entry.offset(), out_root_id);295296clone_die_attributes(297dwarf,298&unit,299entry,300addr_tr,301None,302out_unit,303out_root_id,304None,305None,306out_strings,307&mut pending_die_refs,308&mut pending_di_refs,309EntryAttributesContext::Root(Some(debug_line_offset)),310isa,311)?;312if split_unit.is_some() {313if let Some((_, skeleton_entry)) = skeleton_unit.entries().next_dfs()? {314clone_die_attributes(315skeleton_dwarf,316skeleton_unit,317skeleton_entry,318addr_tr,319None,320out_unit,321out_root_id,322None,323None,324out_strings,325&mut pending_die_refs,326&mut pending_di_refs,327EntryAttributesContext::Root(Some(debug_line_offset)),328isa,329)?;330}331}332333log_end_output_die(entry, unit, out_root_id, out_unit, out_strings, 0);334stack.push(out_root_id);335(out_unit, out_unit_id, file_map, file_index_base)336} else {337// Can happen when the DWARF is split and we dont have the package/dwo files.338// This is a better user experience than errorring.339dbi_log!("... skipped: split DW_TAG_compile_unit entry missing");340return Ok(None); // empty:341}342} else {343dbi_log!("... skipped: empty CU (no DW_TAG_compile_unit entry)");344return Ok(None); // empty345};346let mut current_depth = 0;347let mut skip_at_depth = None;348let mut current_frame_base = InheritedAttr::new();349let mut current_value_range = InheritedAttr::new();350let mut current_scope_ranges = InheritedAttr::new();351let mut current_subprogram = InheritedAttr::new();352while let Some((depth_delta, entry)) = entries.next_dfs()? {353current_depth += depth_delta;354log_begin_input_die(dwarf, unit, entry, current_depth);355356// If `skip_at_depth` is `Some` then we previously decided to skip over357// a node and all it's children. Let A be the last node processed, B be358// the first node skipped, C be previous node, and D the current node.359// Then `cached` is the difference from A to B, `depth` is the difference360// from B to C, and `depth_delta` is the differenc from C to D.361let depth_delta = if let Some((depth, cached)) = skip_at_depth {362// `new_depth` = B to D363let new_depth = depth + depth_delta;364// if D is below B continue to skip365if new_depth > 0 {366skip_at_depth = Some((new_depth, cached));367log_end_output_die_skipped(entry, unit, "unreachable", current_depth);368continue;369}370// otherwise process D with `depth_delta` being the difference from A to D371skip_at_depth = None;372new_depth + cached373} else {374depth_delta375};376377if !context378.reachable379.contains(&entry.offset().to_unit_section_offset(&unit))380{381// entry is not reachable: discarding all its info.382// Here B = C so `depth` is 0. A is the previous node so `cached` =383// `depth_delta`.384skip_at_depth = Some((0, depth_delta));385log_end_output_die_skipped(entry, unit, "unreachable", current_depth);386continue;387}388389let new_stack_len = stack.len().wrapping_add(depth_delta as usize);390current_frame_base.update(new_stack_len);391current_scope_ranges.update(new_stack_len);392current_value_range.update(new_stack_len);393current_subprogram.update(new_stack_len);394let range_builder = if entry.tag() == gimli::DW_TAG_subprogram {395let range_builder =396RangeInfoBuilder::from_subprogram_die(dwarf, &unit, entry, addr_tr)?;397if let RangeInfoBuilder::Function(func) = range_builder {398let frame_info = compilation.function_frame_info(module, func);399current_value_range.push(new_stack_len, frame_info);400let (symbol, _) = compilation.function(module, func);401translated.insert(symbol);402current_scope_ranges.push(new_stack_len, range_builder.get_ranges(addr_tr));403Some(range_builder)404} else {405// FIXME current_scope_ranges.push()406None407}408} else {409let high_pc = entry.attr_value(gimli::DW_AT_high_pc)?;410let ranges = entry.attr_value(gimli::DW_AT_ranges)?;411if high_pc.is_some() || ranges.is_some() {412let range_builder = RangeInfoBuilder::from(dwarf, &unit, entry)?;413current_scope_ranges.push(new_stack_len, range_builder.get_ranges(addr_tr));414Some(range_builder)415} else {416None417}418};419420if depth_delta <= 0 {421for _ in depth_delta..1 {422stack.pop();423}424} else {425assert_eq!(depth_delta, 1);426}427428if let Some(AttributeValue::Exprloc(expr)) = entry.attr_value(gimli::DW_AT_frame_base)? {429if let Some(expr) = compile_expression(&expr, unit.encoding(), None)? {430current_frame_base.push(new_stack_len, expr);431}432}433434let parent = stack.last().unwrap();435436if entry.tag() == gimli::DW_TAG_pointer_type || entry.tag() == gimli::DW_TAG_reference_type437{438// Wrap pointer types.439let pointer_kind = match entry.tag() {440gimli::DW_TAG_pointer_type => WebAssemblyPtrKind::Pointer,441gimli::DW_TAG_reference_type => WebAssemblyPtrKind::Reference,442_ => panic!(),443};444let die_id = replace_pointer_type(445*parent,446pointer_kind,447out_unit,448out_module_synthetic_unit.wasm_ptr_die_ref(),449entry,450unit,451dwarf,452out_strings,453&mut pending_die_refs,454)?;455stack.push(die_id);456assert_eq!(stack.len(), new_stack_len);457die_ref_map.insert(entry.offset(), die_id);458log_end_output_die(entry, unit, die_id, out_unit, out_strings, current_depth);459continue;460}461462let out_die_id = out_unit.add(*parent, entry.tag());463464stack.push(out_die_id);465assert_eq!(stack.len(), new_stack_len);466die_ref_map.insert(entry.offset(), out_die_id);467468clone_die_attributes(469dwarf,470&unit,471entry,472addr_tr,473current_value_range.top(),474&mut out_unit,475out_die_id,476range_builder,477current_scope_ranges.top(),478out_strings,479&mut pending_die_refs,480&mut pending_di_refs,481EntryAttributesContext::Children {482depth: current_depth as usize,483subprograms: &mut current_subprogram,484file_map: &file_map,485file_index_base,486frame_base: current_frame_base.top(),487},488isa,489)?;490491// Data in WebAssembly memory always uses little-endian byte order.492// If the native architecture is big-endian, we need to mark all493// base types used to refer to WebAssembly memory as little-endian494// using the DW_AT_endianity attribute, so that the debugger will495// be able to correctly access them.496if entry.tag() == gimli::DW_TAG_base_type && isa.endianness() == Endianness::Big {497let current_scope = out_unit.get_mut(out_die_id);498current_scope.set(499gimli::DW_AT_endianity,500write::AttributeValue::Endianity(gimli::DW_END_little),501);502}503504if entry.tag() == gimli::DW_TAG_subprogram && !current_scope_ranges.is_empty() {505append_vmctx_info(506out_unit,507out_die_id,508out_module_synthetic_unit.vmctx_ptr_die_ref(),509addr_tr,510current_value_range.top(),511current_scope_ranges.top().context("range")?,512out_strings,513isa,514)?;515}516517log_end_output_die(518entry,519unit,520out_die_id,521out_unit,522out_strings,523current_depth,524);525}526die_ref_map.patch(pending_die_refs, out_unit);527Ok(Some((out_unit_id, die_ref_map, pending_di_refs)))528}529530531