use crate::debug::Reader; use crate::debug::transform::utils::resolve_die_ref; use super::address_transform::AddressTransform; use super::dbi_log; use super::expression::{CompiledExpression, FunctionFrameInfo, compile_expression}; use super::range_info_builder::RangeInfoBuilder; use super::unit::InheritedAttr; use cranelift_codegen::isa::TargetIsa; use gimli::{AttributeValue, UnitOffset, write}; use wasmtime_environ::error::Error; #[derive(Debug)] pub(crate) struct EntryAttributesContext<'a> { pub subprograms: &'a mut InheritedAttr, pub frame_base: Option<&'a CompiledExpression>, } #[derive(Debug)] pub struct SubprogramContext { pub obj_ptr: UnitOffset, pub param_num: isize, } fn is_exprloc_to_loclist_allowed(attr_name: gimli::constants::DwAt) -> bool { match attr_name { gimli::DW_AT_location | gimli::DW_AT_string_length | gimli::DW_AT_return_addr | gimli::DW_AT_data_member_location | gimli::DW_AT_frame_base | gimli::DW_AT_segment | gimli::DW_AT_static_link | gimli::DW_AT_use_location | gimli::DW_AT_vtable_elem_location => true, _ => false, } } pub(crate) fn clone_die_attributes<'a>( convert_unit: &mut gimli::write::ConvertUnit>, entry: &gimli::write::ConvertUnitEntry>, addr_tr: &AddressTransform, frame_info: Option<&FunctionFrameInfo>, out_entry_id: write::UnitEntryId, subprogram_range_builder: Option, scope_ranges: Option<&Vec<(u64, u64)>>, mut attr_context: EntryAttributesContext, isa: &dyn TargetIsa, ) -> Result<(), Error> { let unit = entry.read_unit; let unit_encoding = unit.encoding(); let range_info = if let Some(subprogram_range_builder) = subprogram_range_builder { subprogram_range_builder } else { // FIXME for CU: currently address_transform operate on a single // function range, and when CU spans multiple ranges the // transformation may be incomplete. RangeInfoBuilder::from(entry)? }; range_info.build(addr_tr, convert_unit.unit, out_entry_id); let mut is_obj_ptr = false; prepare_die_context(entry, &mut attr_context, &mut is_obj_ptr)?; for attr in &entry.attrs { match attr.name() { gimli::DW_AT_low_pc | gimli::DW_AT_high_pc | gimli::DW_AT_ranges => { // Handled by RangeInfoBuilder. continue; } gimli::DW_AT_object_pointer => { // Our consumers cannot handle 'this' typed as a non-pointer (recall // we translate all pointers to wrapper types), making it unusable. // To remedy this, we 'strip' instance-ness off of methods by removing // DW_AT_object_pointer and renaming 'this' to '__this'. if let Some(ref mut subprogram) = attr_context.subprograms.top_with_depth_mut(entry.depth) { // We expect this to reference a child entry in the same unit. if let Some(unit_offs) = match attr.value() { AttributeValue::DebugInfoRef(di_ref) => di_ref.to_unit_offset(&unit.header), AttributeValue::UnitRef(unit_ref) => Some(unit_ref), _ => None, } { subprogram.obj_ptr = unit_offs; dbi_log!("Stripped DW_AT_object_pointer"); continue; } } } _ => {} } if is_obj_ptr { match attr.name() { gimli::DW_AT_artificial => { dbi_log!("Object pointer: stripped DW_AT_artificial"); continue; } gimli::DW_AT_name => { let old_name: &str = &unit.attr_string(attr.value())?.to_string_lossy(); let new_name = format!("__{old_name}"); dbi_log!( "Object pointer: renamed '{}' -> '{}'", old_name, new_name.as_str() ); let attr_value = write::AttributeValue::StringRef(convert_unit.strings.add(new_name)); convert_unit .unit .get_mut(out_entry_id) .set(gimli::DW_AT_name, attr_value); continue; } _ => {} } } let attr_value = attr.value(); let out_attr_value = match attr_value { AttributeValue::Addr(u) => { let addr = addr_tr.translate(u).unwrap_or(write::Address::Constant(0)); write::AttributeValue::Address(addr) } AttributeValue::DebugAddrIndex(i) => { let u = unit.address(i)?; let addr = addr_tr.translate(u).unwrap_or(write::Address::Constant(0)); write::AttributeValue::Address(addr) } AttributeValue::RangeListsRef(_) | AttributeValue::DebugRngListsIndex(_) => { let r = unit.attr_ranges_offset(attr_value)?.unwrap(); let range_info = RangeInfoBuilder::from_ranges_ref(unit, r)?; let range_list_id = range_info.build_ranges(addr_tr, &mut convert_unit.unit.ranges); write::AttributeValue::RangeListRef(range_list_id) } AttributeValue::LocationListsRef(_) | AttributeValue::DebugLocListsIndex(_) => { let mut locs = unit.attr_locations(attr_value)?.unwrap(); let frame_base = attr_context.frame_base; let mut result: Option> = None; while let Some(loc) = locs.next()? { if let Some(expr) = compile_expression(&loc.data, unit_encoding, frame_base)? { let chunk = expr .build_with_locals( &[(loc.range.begin, loc.range.end)], addr_tr, frame_info, isa, ) .expressions .filter(|i| { // Ignore empty range if let Ok((_, 0, _)) = i { false } else { true } }) .map(|i| { i.map(|(start, len, expr)| write::Location::StartLength { begin: start, length: len, data: expr, }) }) .collect::, _>>()?; match &mut result { Some(r) => r.extend(chunk), x @ None => *x = Some(chunk), } } else { // FIXME _expr contains invalid expression continue; // ignore entry } } if result.is_none() { continue; // no valid locations } let list_id = convert_unit .unit .locations .add(write::LocationList(result.unwrap())); write::AttributeValue::LocationListRef(list_id) } AttributeValue::Exprloc(_) if attr.name() == gimli::DW_AT_frame_base => { // We do not really "rewrite" the frame base so much as replace it outright. // References to it through the DW_OP_fbreg opcode will be expanded below. let mut cfa = write::Expression::new(); cfa.op(gimli::DW_OP_call_frame_cfa); write::AttributeValue::Exprloc(cfa) } AttributeValue::Exprloc(ref expr) => { let frame_base = attr_context.frame_base; if let Some(expr) = compile_expression(expr, unit_encoding, frame_base)? { if expr.is_simple() { if let Some(expr) = expr.build() { write::AttributeValue::Exprloc(expr) } else { continue; } } else { // Conversion to loclist is required. if let Some(scope_ranges) = scope_ranges { let built_expression = expr.build_with_locals(scope_ranges, addr_tr, frame_info, isa); let exprs = built_expression .expressions .collect::, _>>()?; if exprs.is_empty() { continue; } // Micro-optimization all expressions alike, use one exprloc. let mut single_expr: Option = None; if built_expression.covers_entire_scope { for (_, _, expr) in &exprs { if let Some(ref prev_expr) = single_expr { if expr == prev_expr { continue; // the same expression } single_expr = None; break; } single_expr = Some(expr.clone()) } } if let Some(expr) = single_expr { write::AttributeValue::Exprloc(expr) } else if is_exprloc_to_loclist_allowed(attr.name()) { // Converting exprloc to loclist. let mut locs = Vec::new(); for (begin, length, data) in exprs { if length == 0 { // Ignore empty range continue; } locs.push(write::Location::StartLength { begin, length, data, }); } let list_id = convert_unit.unit.locations.add(write::LocationList(locs)); write::AttributeValue::LocationListRef(list_id) } else { continue; } } else { continue; } } } else { // FIXME _expr contains invalid expression continue; // ignore attribute } } // No other attributes contain addresses or address offsets. _ => match convert_unit.convert_attribute_value(unit, attr, &|_| None) { Ok(value) => value, Err(e) => { // Invalid `FileIndex` was seen in #8884 and #8904. In general it's // better to ignore invalid or unknown DWARF rather then failing outright. dbi_log!( "Ignoring entry {:x?} attribute {} = {:x?}: {e}", entry.offset.to_unit_section_offset(&convert_unit.read_unit), attr.name(), attr_value, ); continue; } }, }; let out_entry = convert_unit.unit.get_mut(out_entry_id); out_entry.set(attr.name(), out_attr_value); } Ok(()) } fn prepare_die_context<'a>( entry: &gimli::write::ConvertUnitEntry>, attr_context: &mut EntryAttributesContext, is_obj_ptr: &mut bool, ) -> Result<(), Error> { let subprograms = &mut attr_context.subprograms; // Update the current context based on what kind of entry this is. match entry.tag { gimli::DW_TAG_subprogram | gimli::DW_TAG_inlined_subroutine | gimli::DW_TAG_entry_point => { // Push the 'context' of there being no parameters (yet). subprograms.push( entry.depth, SubprogramContext { obj_ptr: UnitOffset { 0: 0 }, param_num: -1, }, ); } gimli::DW_TAG_formal_parameter => { // Formal parameter tags can be parented by catch blocks // and such - not just subprogram DIEs. So we need to check // that this DIE is indeed a direct child of a subprogram. if let Some(subprogram) = subprograms.top_with_depth_mut(entry.depth - 1) { subprogram.param_num += 1; if subprogram.obj_ptr == entry.offset || is_obj_ptr_param(entry, subprogram.param_num)? { *is_obj_ptr = true; } } } _ => {} } Ok(()) } fn is_obj_ptr_param( entry: &gimli::write::ConvertUnitEntry>, param_num: isize, ) -> Result { debug_assert_eq!(entry.tag, gimli::DW_TAG_formal_parameter); let unit = entry.read_unit; // This logic was taken loosely from LLDB. It is known // that it is not fully correct (doesn't handle 'deduced // this', for example). // Q: DWARF includes DW_AT_object_pointer as we use it, // why do we need this heuristic as well? // A: Declarations do not include DW_AT_object_pointer. if param_num == 0 && entry.attr_value(gimli::DW_AT_artificial) == Some(AttributeValue::Flag(true)) { // Either this has no name (declarations omit them), or its explicitly "this". let name = entry.attr_value(gimli::DW_AT_name); if name.is_none() || unit.attr_string(name.unwrap())?.slice().eq(b"this") { // Finally, a type check. We expect a pointer. if let Some(type_attr) = entry.attr_value(gimli::DW_AT_type) { if let Some(type_die) = resolve_die_ref(unit, &type_attr)? { return Ok(type_die.tag == gimli::DW_TAG_pointer_type); } } } }; return Ok(false); }