wasmtime_cranelift/debug/transform/
attr.rs

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