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