1 use crate::debug::transform::utils::resolve_die_ref;
2 use crate::debug::Reader;
3 
4 use super::address_transform::AddressTransform;
5 use super::expression::{compile_expression, CompiledExpression, FunctionFrameInfo};
6 use super::range_info_builder::RangeInfoBuilder;
7 use super::refs::{PendingDebugInfoRefs, PendingUnitRefs};
8 use super::unit::InheritedAttr;
9 use super::{dbi_log, TransformError};
10 use anyhow::{bail, Error};
11 use cranelift_codegen::isa::TargetIsa;
12 use gimli::{
13     write, AttributeValue, DebugLineOffset, DebuggingInformationEntry, Dwarf, Unit, UnitOffset,
14 };
15 
16 #[derive(Debug)]
17 pub(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)]
29 pub struct SubprogramContext {
30     pub obj_ptr: UnitOffset,
31     pub param_num: isize,
32 }
33 
34 fn 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 
49 pub(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                             .expressions
245                             .filter(|i| {
246                                 // Ignore empty range
247                                 if let Ok((_, 0, _)) = i {
248                                     false
249                                 } else {
250                                     true
251                                 }
252                             })
253                             .map(|i| {
254                                 i.map(|(start, len, expr)| write::Location::StartLength {
255                                     begin: start,
256                                     length: len,
257                                     data: expr,
258                                 })
259                             })
260                             .collect::<Result<Vec<_>, _>>()?;
261                         match &mut result {
262                             Some(r) => r.extend(chunk),
263                             x @ None => *x = Some(chunk),
264                         }
265                     } else {
266                         // FIXME _expr contains invalid expression
267                         continue; // ignore entry
268                     }
269                 }
270                 if result.is_none() {
271                     continue; // no valid locations
272                 }
273                 let list_id = out_unit.locations.add(write::LocationList(result.unwrap()));
274                 write::AttributeValue::LocationListRef(list_id)
275             }
276             AttributeValue::Exprloc(_) if attr.name() == gimli::DW_AT_frame_base => {
277                 // We do not really "rewrite" the frame base so much as replace it outright.
278                 // References to it through the DW_OP_fbreg opcode will be expanded below.
279                 let mut cfa = write::Expression::new();
280                 cfa.op(gimli::DW_OP_call_frame_cfa);
281                 write::AttributeValue::Exprloc(cfa)
282             }
283             AttributeValue::Exprloc(ref expr) => {
284                 let frame_base =
285                     if let EntryAttributesContext::Children { frame_base, .. } = attr_context {
286                         frame_base
287                     } else {
288                         None
289                     };
290                 if let Some(expr) = compile_expression(expr, unit_encoding, frame_base)? {
291                     if expr.is_simple() {
292                         if let Some(expr) = expr.build() {
293                             write::AttributeValue::Exprloc(expr)
294                         } else {
295                             continue;
296                         }
297                     } else {
298                         // Conversion to loclist is required.
299                         if let Some(scope_ranges) = scope_ranges {
300                             let built_expression =
301                                 expr.build_with_locals(scope_ranges, addr_tr, frame_info, isa);
302                             let exprs = built_expression
303                                 .expressions
304                                 .collect::<Result<Vec<_>, _>>()?;
305                             if exprs.is_empty() {
306                                 continue;
307                             }
308                             // Micro-optimization all expressions alike, use one exprloc.
309                             let mut single_expr: Option<write::Expression> = None;
310                             if built_expression.covers_entire_scope {
311                                 for (_, _, expr) in &exprs {
312                                     if let Some(ref prev_expr) = single_expr {
313                                         if expr == prev_expr {
314                                             continue; // the same expression
315                                         }
316                                         single_expr = None;
317                                         break;
318                                     }
319                                     single_expr = Some(expr.clone())
320                                 }
321                             }
322                             if let Some(expr) = single_expr {
323                                 write::AttributeValue::Exprloc(expr)
324                             } else if is_exprloc_to_loclist_allowed(attr.name()) {
325                                 // Converting exprloc to loclist.
326                                 let mut locs = Vec::new();
327                                 for (begin, length, data) in exprs {
328                                     if length == 0 {
329                                         // Ignore empty range
330                                         continue;
331                                     }
332                                     locs.push(write::Location::StartLength {
333                                         begin,
334                                         length,
335                                         data,
336                                     });
337                                 }
338                                 let list_id = out_unit.locations.add(write::LocationList(locs));
339                                 write::AttributeValue::LocationListRef(list_id)
340                             } else {
341                                 continue;
342                             }
343                         } else {
344                             continue;
345                         }
346                     }
347                 } else {
348                     // FIXME _expr contains invalid expression
349                     continue; // ignore attribute
350                 }
351             }
352             AttributeValue::Encoding(e) => write::AttributeValue::Encoding(e),
353             AttributeValue::DecimalSign(e) => write::AttributeValue::DecimalSign(e),
354             AttributeValue::Endianity(e) => write::AttributeValue::Endianity(e),
355             AttributeValue::Accessibility(e) => write::AttributeValue::Accessibility(e),
356             AttributeValue::Visibility(e) => write::AttributeValue::Visibility(e),
357             AttributeValue::Virtuality(e) => write::AttributeValue::Virtuality(e),
358             AttributeValue::Language(e) => write::AttributeValue::Language(e),
359             AttributeValue::AddressClass(e) => write::AttributeValue::AddressClass(e),
360             AttributeValue::IdentifierCase(e) => write::AttributeValue::IdentifierCase(e),
361             AttributeValue::CallingConvention(e) => write::AttributeValue::CallingConvention(e),
362             AttributeValue::Inline(e) => write::AttributeValue::Inline(e),
363             AttributeValue::Ordering(e) => write::AttributeValue::Ordering(e),
364             AttributeValue::UnitRef(offset) => {
365                 pending_die_refs.insert(out_entry_id, attr.name(), offset);
366                 continue;
367             }
368             AttributeValue::DebugInfoRef(offset) => {
369                 pending_di_refs.insert(out_entry_id, attr.name(), offset);
370                 continue;
371             }
372             a => bail!("Unexpected attribute: {:?}", a),
373         };
374         let out_entry: &mut write::DebuggingInformationEntry = out_unit.get_mut(out_entry_id);
375         out_entry.set(attr.name(), out_attr_value);
376     }
377     Ok(())
378 }
379 
380 fn prepare_die_context(
381     dwarf: &Dwarf<Reader<'_>>,
382     unit: &Unit<Reader<'_>>,
383     entry: &DebuggingInformationEntry<Reader<'_>>,
384     attr_context: &mut EntryAttributesContext<'_>,
385     is_obj_ptr: &mut bool,
386 ) -> Result<(), Error> {
387     let EntryAttributesContext::Children {
388         depth, subprograms, ..
389     } = attr_context
390     else {
391         return Ok(());
392     };
393 
394     // Update the current context based on what kind of entry this is.
395     match entry.tag() {
396         gimli::DW_TAG_subprogram | gimli::DW_TAG_inlined_subroutine | gimli::DW_TAG_entry_point => {
397             // Push the 'context' of there being no parameters (yet).
398             subprograms.push(
399                 *depth,
400                 SubprogramContext {
401                     obj_ptr: UnitOffset { 0: 0 },
402                     param_num: -1,
403                 },
404             );
405         }
406         gimli::DW_TAG_formal_parameter => {
407             // Formal parameter tags can be parented by catch blocks
408             // and such - not just subprogram DIEs. So we need to check
409             // that this DIE is indeed a direct child of a subprogram.
410             if let Some(subprogram) = subprograms.top_with_depth_mut(*depth - 1) {
411                 subprogram.param_num += 1;
412 
413                 if subprogram.obj_ptr == entry.offset()
414                     || is_obj_ptr_param(dwarf, unit, entry, subprogram.param_num)?
415                 {
416                     *is_obj_ptr = true;
417                 }
418             }
419         }
420         _ => {}
421     }
422     Ok(())
423 }
424 
425 fn is_obj_ptr_param(
426     dwarf: &Dwarf<Reader<'_>>,
427     unit: &Unit<Reader<'_>>,
428     entry: &DebuggingInformationEntry<Reader<'_>>,
429     param_num: isize,
430 ) -> Result<bool, Error> {
431     debug_assert!(entry.tag() == gimli::DW_TAG_formal_parameter);
432 
433     // This logic was taken loosely from LLDB. It is known
434     // that it is not fully correct (doesn't handle 'deduced
435     // this', for example).
436     // Q: DWARF includes DW_AT_object_pointer as we use it,
437     // why do we need this heuristic as well?
438     // A: Declarations do not include DW_AT_object_pointer.
439     if param_num == 0
440         && entry.attr_value(gimli::DW_AT_artificial)? == Some(AttributeValue::Flag(true))
441     {
442         // Either this has no name (declarations omit them), or its explicitly "this".
443         let name = entry.attr_value(gimli::DW_AT_name)?;
444         if name.is_none() || dwarf.attr_string(unit, name.unwrap())?.slice().eq(b"this") {
445             // Finally, a type check. We expect a pointer.
446             if let Some(type_attr) = entry.attr_value(gimli::DW_AT_type)? {
447                 if let Some(type_die) = resolve_die_ref(unit, &type_attr)? {
448                     return Ok(type_die.tag() == gimli::DW_TAG_pointer_type);
449                 }
450             }
451         }
452     };
453 
454     return Ok(false);
455 }
456