Path: blob/main/crates/cranelift/src/debug/transform/attr.rs
1693 views
use crate::debug::Reader;1use crate::debug::transform::utils::resolve_die_ref;23use super::address_transform::AddressTransform;4use super::expression::{CompiledExpression, FunctionFrameInfo, compile_expression};5use super::range_info_builder::RangeInfoBuilder;6use super::refs::{PendingDebugInfoRefs, PendingUnitRefs};7use super::unit::InheritedAttr;8use super::{TransformError, dbi_log};9use anyhow::{Error, bail};10use cranelift_codegen::isa::TargetIsa;11use gimli::{12AttributeValue, DebugLineOffset, DebuggingInformationEntry, Dwarf, Unit, UnitOffset, write,13};1415#[derive(Debug)]16pub(crate) enum EntryAttributesContext<'a> {17Root(Option<DebugLineOffset>),18Children {19depth: usize,20subprograms: &'a mut InheritedAttr<SubprogramContext>,21file_map: &'a [write::FileId],22file_index_base: u64,23frame_base: Option<&'a CompiledExpression>,24},25}2627#[derive(Debug)]28pub struct SubprogramContext {29pub obj_ptr: UnitOffset,30pub param_num: isize,31}3233fn is_exprloc_to_loclist_allowed(attr_name: gimli::constants::DwAt) -> bool {34match attr_name {35gimli::DW_AT_location36| gimli::DW_AT_string_length37| gimli::DW_AT_return_addr38| gimli::DW_AT_data_member_location39| gimli::DW_AT_frame_base40| gimli::DW_AT_segment41| gimli::DW_AT_static_link42| gimli::DW_AT_use_location43| gimli::DW_AT_vtable_elem_location => true,44_ => false,45}46}4748pub(crate) fn clone_die_attributes<'a>(49dwarf: &gimli::Dwarf<Reader<'a>>,50unit: &Unit<Reader<'a>>,51entry: &DebuggingInformationEntry<Reader<'a>>,52addr_tr: &'a AddressTransform,53frame_info: Option<&FunctionFrameInfo>,54out_unit: &mut write::Unit,55out_entry_id: write::UnitEntryId,56subprogram_range_builder: Option<RangeInfoBuilder>,57scope_ranges: Option<&Vec<(u64, u64)>>,58out_strings: &mut write::StringTable,59pending_die_refs: &mut PendingUnitRefs,60pending_di_refs: &mut PendingDebugInfoRefs,61mut attr_context: EntryAttributesContext<'a>,62isa: &dyn TargetIsa,63) -> Result<(), Error> {64let unit_encoding = unit.encoding();6566let range_info = if let Some(subprogram_range_builder) = subprogram_range_builder {67subprogram_range_builder68} else {69// FIXME for CU: currently address_transform operate on a single70// function range, and when CU spans multiple ranges the71// transformation may be incomplete.72RangeInfoBuilder::from(dwarf, unit, entry)?73};74range_info.build(addr_tr, out_unit, out_entry_id);7576let mut is_obj_ptr = false;77prepare_die_context(dwarf, unit, entry, &mut attr_context, &mut is_obj_ptr)?;7879let mut attrs = entry.attrs();80while let Some(attr) = attrs.next()? {81match attr.name() {82gimli::DW_AT_low_pc | gimli::DW_AT_high_pc | gimli::DW_AT_ranges => {83// Handled by RangeInfoBuilder.84continue;85}86gimli::DW_AT_object_pointer => {87// Our consumers cannot handle 'this' typed as a non-pointer (recall88// we translate all pointers to wrapper types), making it unusable.89// To remedy this, we 'strip' instance-ness off of methods by removing90// DW_AT_object_pointer and renaming 'this' to '__this'.91if let EntryAttributesContext::Children {92depth,93ref mut subprograms,94..95} = attr_context96{97if let Some(ref mut subprogram) = subprograms.top_with_depth_mut(depth) {98// We expect this to reference a child entry in the same unit.99if let Some(unit_offs) = match attr.value() {100AttributeValue::DebugInfoRef(di_ref) => {101di_ref.to_unit_offset(&unit.header)102}103AttributeValue::UnitRef(unit_ref) => Some(unit_ref),104_ => None,105} {106subprogram.obj_ptr = unit_offs;107dbi_log!("Stripped DW_AT_object_pointer");108continue;109}110}111}112}113gimli::DW_AT_str_offsets_base114| gimli::DW_AT_addr_base115| gimli::DW_AT_rnglists_base116| gimli::DW_AT_loclists_base117| gimli::DW_AT_dwo_name118| gimli::DW_AT_GNU_addr_base119| gimli::DW_AT_GNU_ranges_base120| gimli::DW_AT_GNU_dwo_name121| gimli::DW_AT_GNU_dwo_id => {122// DWARF encoding details that we don't need to copy.123continue;124}125_ => {}126}127128if is_obj_ptr {129match attr.name() {130gimli::DW_AT_artificial => {131dbi_log!("Object pointer: stripped DW_AT_artificial");132continue;133}134gimli::DW_AT_name => {135let old_name: &str = &dwarf.attr_string(unit, attr.value())?.to_string_lossy();136let new_name = format!("__{old_name}");137dbi_log!(138"Object pointer: renamed '{}' -> '{}'",139old_name,140new_name.as_str()141);142143let attr_value = write::AttributeValue::StringRef(out_strings.add(new_name));144out_unit145.get_mut(out_entry_id)146.set(gimli::DW_AT_name, attr_value);147continue;148}149_ => {}150}151}152153let attr_value = attr.value();154let out_attr_value = match attr_value {155AttributeValue::Addr(u) => {156let addr = addr_tr.translate(u).unwrap_or(write::Address::Constant(0));157write::AttributeValue::Address(addr)158}159AttributeValue::DebugAddrIndex(i) => {160let u = dwarf.address(unit, i)?;161let addr = addr_tr.translate(u).unwrap_or(write::Address::Constant(0));162write::AttributeValue::Address(addr)163}164AttributeValue::Block(d) => write::AttributeValue::Block(d.to_vec()),165AttributeValue::Udata(u) => write::AttributeValue::Udata(u),166AttributeValue::Data1(d) => write::AttributeValue::Data1(d),167AttributeValue::Data2(d) => write::AttributeValue::Data2(d),168AttributeValue::Data4(d) => write::AttributeValue::Data4(d),169AttributeValue::Data8(d) => write::AttributeValue::Data8(d),170AttributeValue::Sdata(d) => write::AttributeValue::Sdata(d),171AttributeValue::Flag(f) => write::AttributeValue::Flag(f),172AttributeValue::DebugLineRef(line_program_offset) => {173if let EntryAttributesContext::Root(o) = attr_context {174if o != Some(line_program_offset) {175return Err(TransformError("invalid debug_line offset").into());176}177write::AttributeValue::LineProgramRef178} else {179return Err(TransformError("unexpected debug_line index attribute").into());180}181}182AttributeValue::FileIndex(i) => {183if let EntryAttributesContext::Children {184file_map,185file_index_base,186..187} = attr_context188{189let index = usize::try_from(i - file_index_base)190.ok()191.and_then(|i| file_map.get(i).copied());192match index {193Some(index) => write::AttributeValue::FileIndex(Some(index)),194// This was seen to be invalid in #8884 and #8904 so195// ignore this seemingly invalid DWARF from LLVM196None => continue,197}198} else {199return Err(TransformError("unexpected file index attribute").into());200}201}202AttributeValue::String(d) => write::AttributeValue::String(d.to_vec()),203AttributeValue::DebugStrRef(_) | AttributeValue::DebugStrOffsetsIndex(_) => {204let s = dwarf205.attr_string(unit, attr_value)?206.to_string_lossy()207.into_owned();208write::AttributeValue::StringRef(out_strings.add(s))209}210AttributeValue::RangeListsRef(_) | AttributeValue::DebugRngListsIndex(_) => {211let r = dwarf.attr_ranges_offset(unit, attr_value)?.unwrap();212let range_info = RangeInfoBuilder::from_ranges_ref(dwarf, unit, r)?;213let range_list_id = range_info.build_ranges(addr_tr, &mut out_unit.ranges);214write::AttributeValue::RangeListRef(range_list_id)215}216AttributeValue::LocationListsRef(_) | AttributeValue::DebugLocListsIndex(_) => {217let r = dwarf.attr_locations_offset(unit, attr_value)?.unwrap();218let low_pc = 0;219let mut locs = dwarf.locations.locations(220r,221unit_encoding,222low_pc,223&dwarf.debug_addr,224unit.addr_base,225)?;226let frame_base =227if let EntryAttributesContext::Children { frame_base, .. } = attr_context {228frame_base229} else {230None231};232233let mut result: Option<Vec<_>> = None;234while let Some(loc) = locs.next()? {235if let Some(expr) = compile_expression(&loc.data, unit_encoding, frame_base)? {236let chunk = expr237.build_with_locals(238&[(loc.range.begin, loc.range.end)],239addr_tr,240frame_info,241isa,242)243.expressions244.filter(|i| {245// Ignore empty range246if let Ok((_, 0, _)) = i { false } else { true }247})248.map(|i| {249i.map(|(start, len, expr)| write::Location::StartLength {250begin: start,251length: len,252data: expr,253})254})255.collect::<Result<Vec<_>, _>>()?;256match &mut result {257Some(r) => r.extend(chunk),258x @ None => *x = Some(chunk),259}260} else {261// FIXME _expr contains invalid expression262continue; // ignore entry263}264}265if result.is_none() {266continue; // no valid locations267}268let list_id = out_unit.locations.add(write::LocationList(result.unwrap()));269write::AttributeValue::LocationListRef(list_id)270}271AttributeValue::Exprloc(_) if attr.name() == gimli::DW_AT_frame_base => {272// We do not really "rewrite" the frame base so much as replace it outright.273// References to it through the DW_OP_fbreg opcode will be expanded below.274let mut cfa = write::Expression::new();275cfa.op(gimli::DW_OP_call_frame_cfa);276write::AttributeValue::Exprloc(cfa)277}278AttributeValue::Exprloc(ref expr) => {279let frame_base =280if let EntryAttributesContext::Children { frame_base, .. } = attr_context {281frame_base282} else {283None284};285if let Some(expr) = compile_expression(expr, unit_encoding, frame_base)? {286if expr.is_simple() {287if let Some(expr) = expr.build() {288write::AttributeValue::Exprloc(expr)289} else {290continue;291}292} else {293// Conversion to loclist is required.294if let Some(scope_ranges) = scope_ranges {295let built_expression =296expr.build_with_locals(scope_ranges, addr_tr, frame_info, isa);297let exprs = built_expression298.expressions299.collect::<Result<Vec<_>, _>>()?;300if exprs.is_empty() {301continue;302}303// Micro-optimization all expressions alike, use one exprloc.304let mut single_expr: Option<write::Expression> = None;305if built_expression.covers_entire_scope {306for (_, _, expr) in &exprs {307if let Some(ref prev_expr) = single_expr {308if expr == prev_expr {309continue; // the same expression310}311single_expr = None;312break;313}314single_expr = Some(expr.clone())315}316}317if let Some(expr) = single_expr {318write::AttributeValue::Exprloc(expr)319} else if is_exprloc_to_loclist_allowed(attr.name()) {320// Converting exprloc to loclist.321let mut locs = Vec::new();322for (begin, length, data) in exprs {323if length == 0 {324// Ignore empty range325continue;326}327locs.push(write::Location::StartLength {328begin,329length,330data,331});332}333let list_id = out_unit.locations.add(write::LocationList(locs));334write::AttributeValue::LocationListRef(list_id)335} else {336continue;337}338} else {339continue;340}341}342} else {343// FIXME _expr contains invalid expression344continue; // ignore attribute345}346}347AttributeValue::Encoding(e) => write::AttributeValue::Encoding(e),348AttributeValue::DecimalSign(e) => write::AttributeValue::DecimalSign(e),349AttributeValue::Endianity(e) => write::AttributeValue::Endianity(e),350AttributeValue::Accessibility(e) => write::AttributeValue::Accessibility(e),351AttributeValue::Visibility(e) => write::AttributeValue::Visibility(e),352AttributeValue::Virtuality(e) => write::AttributeValue::Virtuality(e),353AttributeValue::Language(e) => write::AttributeValue::Language(e),354AttributeValue::AddressClass(e) => write::AttributeValue::AddressClass(e),355AttributeValue::IdentifierCase(e) => write::AttributeValue::IdentifierCase(e),356AttributeValue::CallingConvention(e) => write::AttributeValue::CallingConvention(e),357AttributeValue::Inline(e) => write::AttributeValue::Inline(e),358AttributeValue::Ordering(e) => write::AttributeValue::Ordering(e),359AttributeValue::UnitRef(offset) => {360pending_die_refs.insert(out_entry_id, attr.name(), offset);361continue;362}363AttributeValue::DebugInfoRef(offset) => {364pending_di_refs.insert(out_entry_id, attr.name(), offset);365continue;366}367a => bail!("Unexpected attribute: {:?}", a),368};369let out_entry: &mut write::DebuggingInformationEntry = out_unit.get_mut(out_entry_id);370out_entry.set(attr.name(), out_attr_value);371}372Ok(())373}374375fn prepare_die_context(376dwarf: &Dwarf<Reader<'_>>,377unit: &Unit<Reader<'_>>,378entry: &DebuggingInformationEntry<Reader<'_>>,379attr_context: &mut EntryAttributesContext<'_>,380is_obj_ptr: &mut bool,381) -> Result<(), Error> {382let EntryAttributesContext::Children {383depth, subprograms, ..384} = attr_context385else {386return Ok(());387};388389// Update the current context based on what kind of entry this is.390match entry.tag() {391gimli::DW_TAG_subprogram | gimli::DW_TAG_inlined_subroutine | gimli::DW_TAG_entry_point => {392// Push the 'context' of there being no parameters (yet).393subprograms.push(394*depth,395SubprogramContext {396obj_ptr: UnitOffset { 0: 0 },397param_num: -1,398},399);400}401gimli::DW_TAG_formal_parameter => {402// Formal parameter tags can be parented by catch blocks403// and such - not just subprogram DIEs. So we need to check404// that this DIE is indeed a direct child of a subprogram.405if let Some(subprogram) = subprograms.top_with_depth_mut(*depth - 1) {406subprogram.param_num += 1;407408if subprogram.obj_ptr == entry.offset()409|| is_obj_ptr_param(dwarf, unit, entry, subprogram.param_num)?410{411*is_obj_ptr = true;412}413}414}415_ => {}416}417Ok(())418}419420fn is_obj_ptr_param(421dwarf: &Dwarf<Reader<'_>>,422unit: &Unit<Reader<'_>>,423entry: &DebuggingInformationEntry<Reader<'_>>,424param_num: isize,425) -> Result<bool, Error> {426debug_assert!(entry.tag() == gimli::DW_TAG_formal_parameter);427428// This logic was taken loosely from LLDB. It is known429// that it is not fully correct (doesn't handle 'deduced430// this', for example).431// Q: DWARF includes DW_AT_object_pointer as we use it,432// why do we need this heuristic as well?433// A: Declarations do not include DW_AT_object_pointer.434if param_num == 0435&& entry.attr_value(gimli::DW_AT_artificial)? == Some(AttributeValue::Flag(true))436{437// Either this has no name (declarations omit them), or its explicitly "this".438let name = entry.attr_value(gimli::DW_AT_name)?;439if name.is_none() || dwarf.attr_string(unit, name.unwrap())?.slice().eq(b"this") {440// Finally, a type check. We expect a pointer.441if let Some(type_attr) = entry.attr_value(gimli::DW_AT_type)? {442if let Some(type_die) = resolve_die_ref(unit, &type_attr)? {443return Ok(type_die.tag() == gimli::DW_TAG_pointer_type);444}445}446}447};448449return Ok(false);450}451452453