Path: blob/main/crates/cranelift/src/debug/transform/attr.rs
3071 views
use crate::debug::Reader;1use crate::debug::transform::utils::resolve_die_ref;23use super::address_transform::AddressTransform;4use super::dbi_log;5use super::expression::{CompiledExpression, FunctionFrameInfo, compile_expression};6use super::range_info_builder::RangeInfoBuilder;7use super::unit::InheritedAttr;8use cranelift_codegen::isa::TargetIsa;9use gimli::{AttributeValue, UnitOffset, write};10use wasmtime_environ::error::Error;1112#[derive(Debug)]13pub(crate) struct EntryAttributesContext<'a> {14pub subprograms: &'a mut InheritedAttr<SubprogramContext>,15pub frame_base: Option<&'a CompiledExpression>,16}1718#[derive(Debug)]19pub struct SubprogramContext {20pub obj_ptr: UnitOffset,21pub param_num: isize,22}2324fn is_exprloc_to_loclist_allowed(attr_name: gimli::constants::DwAt) -> bool {25match attr_name {26gimli::DW_AT_location27| gimli::DW_AT_string_length28| gimli::DW_AT_return_addr29| gimli::DW_AT_data_member_location30| gimli::DW_AT_frame_base31| gimli::DW_AT_segment32| gimli::DW_AT_static_link33| gimli::DW_AT_use_location34| gimli::DW_AT_vtable_elem_location => true,35_ => false,36}37}3839pub(crate) fn clone_die_attributes<'a>(40convert_unit: &mut gimli::write::ConvertUnit<Reader<'a>>,41entry: &gimli::write::ConvertUnitEntry<Reader<'a>>,42addr_tr: &AddressTransform,43frame_info: Option<&FunctionFrameInfo>,44out_entry_id: write::UnitEntryId,45subprogram_range_builder: Option<RangeInfoBuilder>,46scope_ranges: Option<&Vec<(u64, u64)>>,47mut attr_context: EntryAttributesContext,48isa: &dyn TargetIsa,49) -> Result<(), Error> {50let unit = entry.read_unit;51let unit_encoding = unit.encoding();5253let range_info = if let Some(subprogram_range_builder) = subprogram_range_builder {54subprogram_range_builder55} else {56// FIXME for CU: currently address_transform operate on a single57// function range, and when CU spans multiple ranges the58// transformation may be incomplete.59RangeInfoBuilder::from(entry)?60};61range_info.build(addr_tr, convert_unit.unit, out_entry_id);6263let mut is_obj_ptr = false;64prepare_die_context(entry, &mut attr_context, &mut is_obj_ptr)?;6566for attr in &entry.attrs {67match attr.name() {68gimli::DW_AT_low_pc | gimli::DW_AT_high_pc | gimli::DW_AT_ranges => {69// Handled by RangeInfoBuilder.70continue;71}72gimli::DW_AT_object_pointer => {73// Our consumers cannot handle 'this' typed as a non-pointer (recall74// we translate all pointers to wrapper types), making it unusable.75// To remedy this, we 'strip' instance-ness off of methods by removing76// DW_AT_object_pointer and renaming 'this' to '__this'.77if let Some(ref mut subprogram) =78attr_context.subprograms.top_with_depth_mut(entry.depth)79{80// We expect this to reference a child entry in the same unit.81if let Some(unit_offs) = match attr.value() {82AttributeValue::DebugInfoRef(di_ref) => di_ref.to_unit_offset(&unit.header),83AttributeValue::UnitRef(unit_ref) => Some(unit_ref),84_ => None,85} {86subprogram.obj_ptr = unit_offs;87dbi_log!("Stripped DW_AT_object_pointer");88continue;89}90}91}92_ => {}93}9495if is_obj_ptr {96match attr.name() {97gimli::DW_AT_artificial => {98dbi_log!("Object pointer: stripped DW_AT_artificial");99continue;100}101gimli::DW_AT_name => {102let old_name: &str = &unit.attr_string(attr.value())?.to_string_lossy();103let new_name = format!("__{old_name}");104dbi_log!(105"Object pointer: renamed '{}' -> '{}'",106old_name,107new_name.as_str()108);109110let attr_value =111write::AttributeValue::StringRef(convert_unit.strings.add(new_name));112convert_unit113.unit114.get_mut(out_entry_id)115.set(gimli::DW_AT_name, attr_value);116continue;117}118_ => {}119}120}121122let attr_value = attr.value();123let out_attr_value = match attr_value {124AttributeValue::Addr(u) => {125let addr = addr_tr.translate(u).unwrap_or(write::Address::Constant(0));126write::AttributeValue::Address(addr)127}128AttributeValue::DebugAddrIndex(i) => {129let u = unit.address(i)?;130let addr = addr_tr.translate(u).unwrap_or(write::Address::Constant(0));131write::AttributeValue::Address(addr)132}133AttributeValue::RangeListsRef(_) | AttributeValue::DebugRngListsIndex(_) => {134let r = unit.attr_ranges_offset(attr_value)?.unwrap();135let range_info = RangeInfoBuilder::from_ranges_ref(unit, r)?;136let range_list_id = range_info.build_ranges(addr_tr, &mut convert_unit.unit.ranges);137write::AttributeValue::RangeListRef(range_list_id)138}139AttributeValue::LocationListsRef(_) | AttributeValue::DebugLocListsIndex(_) => {140let mut locs = unit.attr_locations(attr_value)?.unwrap();141let frame_base = attr_context.frame_base;142143let mut result: Option<Vec<_>> = None;144while let Some(loc) = locs.next()? {145if let Some(expr) = compile_expression(&loc.data, unit_encoding, frame_base)? {146let chunk = expr147.build_with_locals(148&[(loc.range.begin, loc.range.end)],149addr_tr,150frame_info,151isa,152)153.expressions154.filter(|i| {155// Ignore empty range156if let Ok((_, 0, _)) = i { false } else { true }157})158.map(|i| {159i.map(|(start, len, expr)| write::Location::StartLength {160begin: start,161length: len,162data: expr,163})164})165.collect::<Result<Vec<_>, _>>()?;166match &mut result {167Some(r) => r.extend(chunk),168x @ None => *x = Some(chunk),169}170} else {171// FIXME _expr contains invalid expression172continue; // ignore entry173}174}175if result.is_none() {176continue; // no valid locations177}178let list_id = convert_unit179.unit180.locations181.add(write::LocationList(result.unwrap()));182write::AttributeValue::LocationListRef(list_id)183}184AttributeValue::Exprloc(_) if attr.name() == gimli::DW_AT_frame_base => {185// We do not really "rewrite" the frame base so much as replace it outright.186// References to it through the DW_OP_fbreg opcode will be expanded below.187let mut cfa = write::Expression::new();188cfa.op(gimli::DW_OP_call_frame_cfa);189write::AttributeValue::Exprloc(cfa)190}191AttributeValue::Exprloc(ref expr) => {192let frame_base = attr_context.frame_base;193if let Some(expr) = compile_expression(expr, unit_encoding, frame_base)? {194if expr.is_simple() {195if let Some(expr) = expr.build() {196write::AttributeValue::Exprloc(expr)197} else {198continue;199}200} else {201// Conversion to loclist is required.202if let Some(scope_ranges) = scope_ranges {203let built_expression =204expr.build_with_locals(scope_ranges, addr_tr, frame_info, isa);205let exprs = built_expression206.expressions207.collect::<Result<Vec<_>, _>>()?;208if exprs.is_empty() {209continue;210}211// Micro-optimization all expressions alike, use one exprloc.212let mut single_expr: Option<write::Expression> = None;213if built_expression.covers_entire_scope {214for (_, _, expr) in &exprs {215if let Some(ref prev_expr) = single_expr {216if expr == prev_expr {217continue; // the same expression218}219single_expr = None;220break;221}222single_expr = Some(expr.clone())223}224}225if let Some(expr) = single_expr {226write::AttributeValue::Exprloc(expr)227} else if is_exprloc_to_loclist_allowed(attr.name()) {228// Converting exprloc to loclist.229let mut locs = Vec::new();230for (begin, length, data) in exprs {231if length == 0 {232// Ignore empty range233continue;234}235locs.push(write::Location::StartLength {236begin,237length,238data,239});240}241let list_id =242convert_unit.unit.locations.add(write::LocationList(locs));243write::AttributeValue::LocationListRef(list_id)244} else {245continue;246}247} else {248continue;249}250}251} else {252// FIXME _expr contains invalid expression253continue; // ignore attribute254}255}256// No other attributes contain addresses or address offsets.257_ => match convert_unit.convert_attribute_value(unit, attr, &|_| None) {258Ok(value) => value,259Err(e) => {260// Invalid `FileIndex` was seen in #8884 and #8904. In general it's261// better to ignore invalid or unknown DWARF rather then failing outright.262dbi_log!(263"Ignoring entry {:x?} attribute {} = {:x?}: {e}",264entry.offset.to_unit_section_offset(&convert_unit.read_unit),265attr.name(),266attr_value,267);268continue;269}270},271};272let out_entry = convert_unit.unit.get_mut(out_entry_id);273out_entry.set(attr.name(), out_attr_value);274}275Ok(())276}277278fn prepare_die_context<'a>(279entry: &gimli::write::ConvertUnitEntry<Reader<'a>>,280attr_context: &mut EntryAttributesContext,281is_obj_ptr: &mut bool,282) -> Result<(), Error> {283let subprograms = &mut attr_context.subprograms;284285// Update the current context based on what kind of entry this is.286match entry.tag {287gimli::DW_TAG_subprogram | gimli::DW_TAG_inlined_subroutine | gimli::DW_TAG_entry_point => {288// Push the 'context' of there being no parameters (yet).289subprograms.push(290entry.depth,291SubprogramContext {292obj_ptr: UnitOffset { 0: 0 },293param_num: -1,294},295);296}297gimli::DW_TAG_formal_parameter => {298// Formal parameter tags can be parented by catch blocks299// and such - not just subprogram DIEs. So we need to check300// that this DIE is indeed a direct child of a subprogram.301if let Some(subprogram) = subprograms.top_with_depth_mut(entry.depth - 1) {302subprogram.param_num += 1;303304if subprogram.obj_ptr == entry.offset305|| is_obj_ptr_param(entry, subprogram.param_num)?306{307*is_obj_ptr = true;308}309}310}311_ => {}312}313Ok(())314}315316fn is_obj_ptr_param(317entry: &gimli::write::ConvertUnitEntry<Reader<'_>>,318param_num: isize,319) -> Result<bool, Error> {320debug_assert_eq!(entry.tag, gimli::DW_TAG_formal_parameter);321let unit = entry.read_unit;322323// This logic was taken loosely from LLDB. It is known324// that it is not fully correct (doesn't handle 'deduced325// this', for example).326// Q: DWARF includes DW_AT_object_pointer as we use it,327// why do we need this heuristic as well?328// A: Declarations do not include DW_AT_object_pointer.329if param_num == 0330&& entry.attr_value(gimli::DW_AT_artificial) == Some(AttributeValue::Flag(true))331{332// Either this has no name (declarations omit them), or its explicitly "this".333let name = entry.attr_value(gimli::DW_AT_name);334if name.is_none() || unit.attr_string(name.unwrap())?.slice().eq(b"this") {335// Finally, a type check. We expect a pointer.336if let Some(type_attr) = entry.attr_value(gimli::DW_AT_type) {337if let Some(type_die) = resolve_die_ref(unit, &type_attr)? {338return Ok(type_die.tag == gimli::DW_TAG_pointer_type);339}340}341}342};343344return Ok(false);345}346347348