Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/cranelift/src/debug/transform/unit.rs
1693 views
1
use super::DebugInputContext;
2
use super::address_transform::AddressTransform;
3
use super::attr::{EntryAttributesContext, clone_die_attributes};
4
use super::debug_transform_logging::{
5
dbi_log, log_begin_input_die, log_end_output_die, log_end_output_die_skipped,
6
log_get_cu_summary,
7
};
8
use super::expression::compile_expression;
9
use super::line_program::clone_line_program;
10
use super::range_info_builder::RangeInfoBuilder;
11
use super::refs::{PendingDebugInfoRefs, PendingUnitRefs, UnitRefsMap};
12
use super::synthetic::ModuleSyntheticUnit;
13
use super::utils::{append_vmctx_info, resolve_die_ref};
14
use crate::debug::{Compilation, Reader};
15
use anyhow::{Context, Error};
16
use cranelift_codegen::ir::Endianness;
17
use cranelift_codegen::isa::TargetIsa;
18
use gimli::write;
19
use gimli::{AttributeValue, DebuggingInformationEntry, Dwarf, Unit};
20
use std::collections::HashSet;
21
use wasmtime_environ::StaticModuleIndex;
22
use wasmtime_versioned_export_macros::versioned_stringify_ident;
23
24
#[derive(Debug)]
25
pub struct InheritedAttr<T> {
26
stack: Vec<(usize, T)>,
27
}
28
29
impl<T> InheritedAttr<T> {
30
fn new() -> Self {
31
InheritedAttr { stack: Vec::new() }
32
}
33
34
fn update(&mut self, depth: usize) {
35
while !self.stack.is_empty() && self.stack.last().unwrap().0 >= depth {
36
self.stack.pop();
37
}
38
}
39
40
pub fn push(&mut self, depth: usize, value: T) {
41
self.stack.push((depth, value));
42
}
43
44
pub fn top(&self) -> Option<&T> {
45
self.stack.last().map(|entry| &entry.1)
46
}
47
48
pub fn top_with_depth_mut(&mut self, depth: usize) -> Option<&mut T> {
49
self.stack
50
.last_mut()
51
.filter(|entry| entry.0 == depth)
52
.map(|entry| &mut entry.1)
53
}
54
55
fn is_empty(&self) -> bool {
56
self.stack.is_empty()
57
}
58
}
59
60
fn get_base_type_name(
61
type_entry: &DebuggingInformationEntry<Reader<'_>>,
62
unit: &Unit<Reader<'_>>,
63
dwarf: &Dwarf<Reader<'_>>,
64
) -> Result<String, Error> {
65
// FIXME remove recursion.
66
if let Some(die_ref) = type_entry.attr_value(gimli::DW_AT_type)? {
67
if let Some(ref die) = resolve_die_ref(unit, &die_ref)? {
68
if let Some(value) = die.attr_value(gimli::DW_AT_name)? {
69
return Ok(String::from(dwarf.attr_string(unit, value)?.to_string()?));
70
}
71
match die.tag() {
72
gimli::DW_TAG_const_type => {
73
return Ok(format!("const {}", get_base_type_name(die, unit, dwarf)?));
74
}
75
gimli::DW_TAG_pointer_type => {
76
return Ok(format!("{}*", get_base_type_name(die, unit, dwarf)?));
77
}
78
gimli::DW_TAG_reference_type => {
79
return Ok(format!("{}&", get_base_type_name(die, unit, dwarf)?));
80
}
81
gimli::DW_TAG_array_type => {
82
return Ok(format!("{}[]", get_base_type_name(die, unit, dwarf)?));
83
}
84
_ => (),
85
}
86
}
87
}
88
Ok(String::from("??"))
89
}
90
91
enum WebAssemblyPtrKind {
92
Reference,
93
Pointer,
94
}
95
96
/// Replaces WebAssembly pointer type DIE with the wrapper
97
/// which natively represented by offset in a Wasm memory.
98
///
99
/// `pointer_type_entry` is a DW_TAG_pointer_type entry (e.g. `T*`),
100
/// which refers its base type (e.g. `T`), or is a
101
/// DW_TAG_reference_type (e.g. `T&`).
102
///
103
/// The generated wrapper is a structure that contains only the
104
/// `__ptr` field. The utility operators overloads is added to
105
/// provide better debugging experience.
106
///
107
/// Wrappers of pointer and reference types are identical except for
108
/// their name -- they are formatted and accessed from a debugger
109
/// the same way.
110
///
111
/// Notice that "resolve_vmctx_memory_ptr" is external/builtin
112
/// subprogram that is not part of Wasm code.
113
fn replace_pointer_type(
114
parent_id: write::UnitEntryId,
115
kind: WebAssemblyPtrKind,
116
comp_unit: &mut write::Unit,
117
wasm_ptr_die_ref: write::Reference,
118
pointer_type_entry: &DebuggingInformationEntry<Reader<'_>>,
119
unit: &Unit<Reader<'_>, usize>,
120
dwarf: &Dwarf<Reader<'_>>,
121
out_strings: &mut write::StringTable,
122
pending_die_refs: &mut PendingUnitRefs,
123
) -> Result<write::UnitEntryId, Error> {
124
const WASM_PTR_LEN: u8 = 4;
125
126
macro_rules! add_tag {
127
($parent_id:ident, $tag:expr => $die:ident as $die_id:ident { $($a:path = $v:expr),* }) => {
128
let $die_id = comp_unit.add($parent_id, $tag);
129
#[allow(unused_variables, reason = "sometimes not used below")]
130
let $die = comp_unit.get_mut($die_id);
131
$( $die.set($a, $v); )*
132
};
133
}
134
135
// Build DW_TAG_structure_type for the wrapper:
136
// .. DW_AT_name = "WebAssemblyPtrWrapper<T>",
137
// .. DW_AT_byte_size = 4,
138
let name = match kind {
139
WebAssemblyPtrKind::Pointer => format!(
140
"WebAssemblyPtrWrapper<{}>",
141
get_base_type_name(pointer_type_entry, unit, dwarf)?
142
),
143
WebAssemblyPtrKind::Reference => format!(
144
"WebAssemblyRefWrapper<{}>",
145
get_base_type_name(pointer_type_entry, unit, dwarf)?
146
),
147
};
148
add_tag!(parent_id, gimli::DW_TAG_structure_type => wrapper_die as wrapper_die_id {
149
gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add(name.as_str())),
150
gimli::DW_AT_byte_size = write::AttributeValue::Data1(WASM_PTR_LEN)
151
});
152
153
// Build DW_TAG_pointer_type for `WebAssemblyPtrWrapper<T>*`:
154
// .. DW_AT_type = <wrapper_die>
155
add_tag!(parent_id, gimli::DW_TAG_pointer_type => wrapper_ptr_type as wrapper_ptr_type_id {
156
gimli::DW_AT_type = write::AttributeValue::UnitRef(wrapper_die_id)
157
});
158
159
let base_type_id = pointer_type_entry.attr_value(gimli::DW_AT_type)?;
160
// Build DW_TAG_reference_type for `T&`:
161
// .. DW_AT_type = <base_type>
162
add_tag!(parent_id, gimli::DW_TAG_reference_type => ref_type as ref_type_id {});
163
if let Some(AttributeValue::UnitRef(ref offset)) = base_type_id {
164
pending_die_refs.insert(ref_type_id, gimli::DW_AT_type, *offset);
165
}
166
167
// Build DW_TAG_pointer_type for `T*`:
168
// .. DW_AT_type = <base_type>
169
add_tag!(parent_id, gimli::DW_TAG_pointer_type => ptr_type as ptr_type_id {});
170
if let Some(AttributeValue::UnitRef(ref offset)) = base_type_id {
171
pending_die_refs.insert(ptr_type_id, gimli::DW_AT_type, *offset);
172
}
173
174
// Build wrapper_die's DW_TAG_template_type_parameter:
175
// .. DW_AT_name = "T"
176
// .. DW_AT_type = <base_type>
177
add_tag!(wrapper_die_id, gimli::DW_TAG_template_type_parameter => t_param_die as t_param_die_id {
178
gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("T"))
179
});
180
if let Some(AttributeValue::UnitRef(ref offset)) = base_type_id {
181
pending_die_refs.insert(t_param_die_id, gimli::DW_AT_type, *offset);
182
}
183
184
// Build wrapper_die's DW_TAG_member for `__ptr`:
185
// .. DW_AT_name = "__ptr"
186
// .. DW_AT_type = <wp_die>
187
// .. DW_AT_location = 0
188
add_tag!(wrapper_die_id, gimli::DW_TAG_member => m_die as m_die_id {
189
gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("__ptr")),
190
gimli::DW_AT_type = write::AttributeValue::DebugInfoRef(wasm_ptr_die_ref),
191
gimli::DW_AT_data_member_location = write::AttributeValue::Data1(0)
192
});
193
194
// Build wrapper_die's DW_TAG_subprogram for `ptr()`:
195
// .. DW_AT_linkage_name = "wasmtime_resolve_vmctx_memory_ptr"
196
// .. DW_AT_name = "ptr"
197
// .. DW_AT_type = <ptr_type>
198
// .. DW_TAG_formal_parameter
199
// .. .. DW_AT_type = <wrapper_ptr_type>
200
// .. .. DW_AT_artificial = 1
201
add_tag!(wrapper_die_id, gimli::DW_TAG_subprogram => deref_op_die as deref_op_die_id {
202
gimli::DW_AT_linkage_name = write::AttributeValue::StringRef(out_strings.add(versioned_stringify_ident!(wasmtime_resolve_vmctx_memory_ptr))),
203
gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("ptr")),
204
gimli::DW_AT_type = write::AttributeValue::UnitRef(ptr_type_id)
205
});
206
add_tag!(deref_op_die_id, gimli::DW_TAG_formal_parameter => deref_op_this_param as deref_op_this_param_id {
207
gimli::DW_AT_type = write::AttributeValue::UnitRef(wrapper_ptr_type_id),
208
gimli::DW_AT_artificial = write::AttributeValue::Flag(true)
209
});
210
211
// Build wrapper_die's DW_TAG_subprogram for `operator*`:
212
// .. DW_AT_linkage_name = "wasmtime_resolve_vmctx_memory_ptr"
213
// .. DW_AT_name = "operator*"
214
// .. DW_AT_type = <ref_type>
215
// .. DW_TAG_formal_parameter
216
// .. .. DW_AT_type = <wrapper_ptr_type>
217
// .. .. DW_AT_artificial = 1
218
add_tag!(wrapper_die_id, gimli::DW_TAG_subprogram => deref_op_die as deref_op_die_id {
219
gimli::DW_AT_linkage_name = write::AttributeValue::StringRef(out_strings.add(versioned_stringify_ident!(wasmtime_resolve_vmctx_memory_ptr))),
220
gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("operator*")),
221
gimli::DW_AT_type = write::AttributeValue::UnitRef(ref_type_id)
222
});
223
add_tag!(deref_op_die_id, gimli::DW_TAG_formal_parameter => deref_op_this_param as deref_op_this_param_id {
224
gimli::DW_AT_type = write::AttributeValue::UnitRef(wrapper_ptr_type_id),
225
gimli::DW_AT_artificial = write::AttributeValue::Flag(true)
226
});
227
228
// Build wrapper_die's DW_TAG_subprogram for `operator->`:
229
// .. DW_AT_linkage_name = "wasmtime_resolve_vmctx_memory_ptr"
230
// .. DW_AT_name = "operator->"
231
// .. DW_AT_type = <ptr_type>
232
// .. DW_TAG_formal_parameter
233
// .. .. DW_AT_type = <wrapper_ptr_type>
234
// .. .. DW_AT_artificial = 1
235
add_tag!(wrapper_die_id, gimli::DW_TAG_subprogram => deref_op_die as deref_op_die_id {
236
gimli::DW_AT_linkage_name = write::AttributeValue::StringRef(out_strings.add(versioned_stringify_ident!(wasmtime_resolve_vmctx_memory_ptr))),
237
gimli::DW_AT_name = write::AttributeValue::StringRef(out_strings.add("operator->")),
238
gimli::DW_AT_type = write::AttributeValue::UnitRef(ptr_type_id)
239
});
240
add_tag!(deref_op_die_id, gimli::DW_TAG_formal_parameter => deref_op_this_param as deref_op_this_param_id {
241
gimli::DW_AT_type = write::AttributeValue::UnitRef(wrapper_ptr_type_id),
242
gimli::DW_AT_artificial = write::AttributeValue::Flag(true)
243
});
244
245
Ok(wrapper_die_id)
246
}
247
248
pub(crate) fn clone_unit(
249
compilation: &mut Compilation<'_>,
250
module: StaticModuleIndex,
251
skeleton_unit: &Unit<Reader<'_>>,
252
split_unit: Option<&Unit<Reader<'_>>>,
253
split_dwarf: Option<&Dwarf<Reader<'_>>>,
254
context: &DebugInputContext,
255
addr_tr: &AddressTransform,
256
out_encoding: gimli::Encoding,
257
out_module_synthetic_unit: &ModuleSyntheticUnit,
258
out_units: &mut write::UnitTable,
259
out_strings: &mut write::StringTable,
260
translated: &mut HashSet<usize>,
261
isa: &dyn TargetIsa,
262
) -> Result<Option<(write::UnitId, UnitRefsMap, PendingDebugInfoRefs)>, Error> {
263
let mut die_ref_map = UnitRefsMap::new();
264
let mut pending_die_refs = PendingUnitRefs::new();
265
let mut pending_di_refs = PendingDebugInfoRefs::new();
266
let mut stack = Vec::new();
267
268
let skeleton_dwarf = &compilation.translations[module].debuginfo.dwarf;
269
270
// Iterate over all of this compilation unit's entries.
271
let dwarf = split_dwarf.unwrap_or(skeleton_dwarf);
272
let unit = split_unit.unwrap_or(skeleton_unit);
273
let mut entries = unit.entries();
274
dbi_log!("Cloning CU {:?}", log_get_cu_summary(unit));
275
276
let (mut out_unit, out_unit_id, file_map, file_index_base) = if let Some((depth_delta, entry)) =
277
entries.next_dfs()?
278
{
279
assert_eq!(depth_delta, 0);
280
let (out_line_program, debug_line_offset, file_map, file_index_base) = clone_line_program(
281
skeleton_dwarf,
282
skeleton_unit,
283
unit.name,
284
addr_tr,
285
out_encoding,
286
out_strings,
287
)?;
288
289
if entry.tag() == gimli::DW_TAG_compile_unit {
290
log_begin_input_die(dwarf, unit, entry, 0);
291
let out_unit_id = out_units.add(write::Unit::new(out_encoding, out_line_program));
292
let out_unit = out_units.get_mut(out_unit_id);
293
294
let out_root_id = out_unit.root();
295
die_ref_map.insert(entry.offset(), out_root_id);
296
297
clone_die_attributes(
298
dwarf,
299
&unit,
300
entry,
301
addr_tr,
302
None,
303
out_unit,
304
out_root_id,
305
None,
306
None,
307
out_strings,
308
&mut pending_die_refs,
309
&mut pending_di_refs,
310
EntryAttributesContext::Root(Some(debug_line_offset)),
311
isa,
312
)?;
313
if split_unit.is_some() {
314
if let Some((_, skeleton_entry)) = skeleton_unit.entries().next_dfs()? {
315
clone_die_attributes(
316
skeleton_dwarf,
317
skeleton_unit,
318
skeleton_entry,
319
addr_tr,
320
None,
321
out_unit,
322
out_root_id,
323
None,
324
None,
325
out_strings,
326
&mut pending_die_refs,
327
&mut pending_di_refs,
328
EntryAttributesContext::Root(Some(debug_line_offset)),
329
isa,
330
)?;
331
}
332
}
333
334
log_end_output_die(entry, unit, out_root_id, out_unit, out_strings, 0);
335
stack.push(out_root_id);
336
(out_unit, out_unit_id, file_map, file_index_base)
337
} else {
338
// Can happen when the DWARF is split and we dont have the package/dwo files.
339
// This is a better user experience than errorring.
340
dbi_log!("... skipped: split DW_TAG_compile_unit entry missing");
341
return Ok(None); // empty:
342
}
343
} else {
344
dbi_log!("... skipped: empty CU (no DW_TAG_compile_unit entry)");
345
return Ok(None); // empty
346
};
347
let mut current_depth = 0;
348
let mut skip_at_depth = None;
349
let mut current_frame_base = InheritedAttr::new();
350
let mut current_value_range = InheritedAttr::new();
351
let mut current_scope_ranges = InheritedAttr::new();
352
let mut current_subprogram = InheritedAttr::new();
353
while let Some((depth_delta, entry)) = entries.next_dfs()? {
354
current_depth += depth_delta;
355
log_begin_input_die(dwarf, unit, entry, current_depth);
356
357
// If `skip_at_depth` is `Some` then we previously decided to skip over
358
// a node and all it's children. Let A be the last node processed, B be
359
// the first node skipped, C be previous node, and D the current node.
360
// Then `cached` is the difference from A to B, `depth` is the difference
361
// from B to C, and `depth_delta` is the differenc from C to D.
362
let depth_delta = if let Some((depth, cached)) = skip_at_depth {
363
// `new_depth` = B to D
364
let new_depth = depth + depth_delta;
365
// if D is below B continue to skip
366
if new_depth > 0 {
367
skip_at_depth = Some((new_depth, cached));
368
log_end_output_die_skipped(entry, unit, "unreachable", current_depth);
369
continue;
370
}
371
// otherwise process D with `depth_delta` being the difference from A to D
372
skip_at_depth = None;
373
new_depth + cached
374
} else {
375
depth_delta
376
};
377
378
if !context
379
.reachable
380
.contains(&entry.offset().to_unit_section_offset(&unit))
381
{
382
// entry is not reachable: discarding all its info.
383
// Here B = C so `depth` is 0. A is the previous node so `cached` =
384
// `depth_delta`.
385
skip_at_depth = Some((0, depth_delta));
386
log_end_output_die_skipped(entry, unit, "unreachable", current_depth);
387
continue;
388
}
389
390
let new_stack_len = stack.len().wrapping_add(depth_delta as usize);
391
current_frame_base.update(new_stack_len);
392
current_scope_ranges.update(new_stack_len);
393
current_value_range.update(new_stack_len);
394
current_subprogram.update(new_stack_len);
395
let range_builder = if entry.tag() == gimli::DW_TAG_subprogram {
396
let range_builder =
397
RangeInfoBuilder::from_subprogram_die(dwarf, &unit, entry, addr_tr)?;
398
if let RangeInfoBuilder::Function(func) = range_builder {
399
let frame_info = compilation.function_frame_info(module, func);
400
current_value_range.push(new_stack_len, frame_info);
401
let (symbol, _) = compilation.function(module, func);
402
translated.insert(symbol);
403
current_scope_ranges.push(new_stack_len, range_builder.get_ranges(addr_tr));
404
Some(range_builder)
405
} else {
406
// FIXME current_scope_ranges.push()
407
None
408
}
409
} else {
410
let high_pc = entry.attr_value(gimli::DW_AT_high_pc)?;
411
let ranges = entry.attr_value(gimli::DW_AT_ranges)?;
412
if high_pc.is_some() || ranges.is_some() {
413
let range_builder = RangeInfoBuilder::from(dwarf, &unit, entry)?;
414
current_scope_ranges.push(new_stack_len, range_builder.get_ranges(addr_tr));
415
Some(range_builder)
416
} else {
417
None
418
}
419
};
420
421
if depth_delta <= 0 {
422
for _ in depth_delta..1 {
423
stack.pop();
424
}
425
} else {
426
assert_eq!(depth_delta, 1);
427
}
428
429
if let Some(AttributeValue::Exprloc(expr)) = entry.attr_value(gimli::DW_AT_frame_base)? {
430
if let Some(expr) = compile_expression(&expr, unit.encoding(), None)? {
431
current_frame_base.push(new_stack_len, expr);
432
}
433
}
434
435
let parent = stack.last().unwrap();
436
437
if entry.tag() == gimli::DW_TAG_pointer_type || entry.tag() == gimli::DW_TAG_reference_type
438
{
439
// Wrap pointer types.
440
let pointer_kind = match entry.tag() {
441
gimli::DW_TAG_pointer_type => WebAssemblyPtrKind::Pointer,
442
gimli::DW_TAG_reference_type => WebAssemblyPtrKind::Reference,
443
_ => panic!(),
444
};
445
let die_id = replace_pointer_type(
446
*parent,
447
pointer_kind,
448
out_unit,
449
out_module_synthetic_unit.wasm_ptr_die_ref(),
450
entry,
451
unit,
452
dwarf,
453
out_strings,
454
&mut pending_die_refs,
455
)?;
456
stack.push(die_id);
457
assert_eq!(stack.len(), new_stack_len);
458
die_ref_map.insert(entry.offset(), die_id);
459
log_end_output_die(entry, unit, die_id, out_unit, out_strings, current_depth);
460
continue;
461
}
462
463
let out_die_id = out_unit.add(*parent, entry.tag());
464
465
stack.push(out_die_id);
466
assert_eq!(stack.len(), new_stack_len);
467
die_ref_map.insert(entry.offset(), out_die_id);
468
469
clone_die_attributes(
470
dwarf,
471
&unit,
472
entry,
473
addr_tr,
474
current_value_range.top(),
475
&mut out_unit,
476
out_die_id,
477
range_builder,
478
current_scope_ranges.top(),
479
out_strings,
480
&mut pending_die_refs,
481
&mut pending_di_refs,
482
EntryAttributesContext::Children {
483
depth: current_depth as usize,
484
subprograms: &mut current_subprogram,
485
file_map: &file_map,
486
file_index_base,
487
frame_base: current_frame_base.top(),
488
},
489
isa,
490
)?;
491
492
// Data in WebAssembly memory always uses little-endian byte order.
493
// If the native architecture is big-endian, we need to mark all
494
// base types used to refer to WebAssembly memory as little-endian
495
// using the DW_AT_endianity attribute, so that the debugger will
496
// be able to correctly access them.
497
if entry.tag() == gimli::DW_TAG_base_type && isa.endianness() == Endianness::Big {
498
let current_scope = out_unit.get_mut(out_die_id);
499
current_scope.set(
500
gimli::DW_AT_endianity,
501
write::AttributeValue::Endianity(gimli::DW_END_little),
502
);
503
}
504
505
if entry.tag() == gimli::DW_TAG_subprogram && !current_scope_ranges.is_empty() {
506
append_vmctx_info(
507
out_unit,
508
out_die_id,
509
out_module_synthetic_unit.vmctx_ptr_die_ref(),
510
addr_tr,
511
current_value_range.top(),
512
current_scope_ranges.top().context("range")?,
513
out_strings,
514
isa,
515
)?;
516
}
517
518
log_end_output_die(
519
entry,
520
unit,
521
out_die_id,
522
out_unit,
523
out_strings,
524
current_depth,
525
);
526
}
527
die_ref_map.patch(pending_die_refs, out_unit);
528
Ok(Some((out_unit_id, die_ref_map, pending_di_refs)))
529
}
530
531