Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/cranelift/filetests/src/test_unwind.rs
1691 views
1
//! Test command for verifying the unwind emitted for each function.
2
//!
3
//! The `unwind` test command runs each function through the full code generator pipeline.
4
5
use crate::subtest::{Context, SubTest, run_filecheck};
6
use cranelift_codegen::{ir, isa::unwind::UnwindInfo};
7
use cranelift_reader::TestCommand;
8
use gimli::{
9
LittleEndian,
10
write::{Address, EhFrame, EndianVec, FrameTable},
11
};
12
use std::borrow::Cow;
13
14
struct TestUnwind;
15
16
pub fn subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn SubTest>> {
17
assert_eq!(parsed.command, "unwind");
18
if !parsed.options.is_empty() {
19
anyhow::bail!("No options allowed on {}", parsed);
20
}
21
Ok(Box::new(TestUnwind))
22
}
23
24
impl SubTest for TestUnwind {
25
fn name(&self) -> &'static str {
26
"unwind"
27
}
28
29
fn is_mutating(&self) -> bool {
30
false
31
}
32
33
fn needs_isa(&self) -> bool {
34
true
35
}
36
37
fn run(&self, func: Cow<ir::Function>, context: &Context) -> anyhow::Result<()> {
38
let isa = context.isa.expect("unwind needs an ISA");
39
let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned());
40
41
let code = comp_ctx
42
.compile(isa, &mut Default::default())
43
.expect("failed to compile function");
44
45
let mut text = String::new();
46
match code.create_unwind_info(isa).expect("unwind info") {
47
Some(UnwindInfo::WindowsX64(info)) => {
48
let mut mem = vec![0; info.emit_size()];
49
info.emit(&mut mem);
50
windowsx64::dump(&mut text, &mem);
51
}
52
Some(UnwindInfo::SystemV(info)) => {
53
let mut table = FrameTable::default();
54
let cie = isa
55
.create_systemv_cie()
56
.expect("the ISA should support a System V CIE");
57
58
let cie_id = table.add_cie(cie);
59
table.add_fde(cie_id, info.to_fde(Address::Constant(0)));
60
61
let mut eh_frame = EhFrame(EndianVec::new(LittleEndian));
62
table.write_eh_frame(&mut eh_frame).unwrap();
63
systemv::dump(&mut text, &eh_frame.0.into_vec(), isa.pointer_bytes())
64
}
65
Some(ui) => {
66
anyhow::bail!("Unexpected unwind info type: {:?}", ui);
67
}
68
None => {}
69
}
70
71
run_filecheck(&text, context)
72
}
73
}
74
75
mod windowsx64 {
76
use std::fmt::Write;
77
78
pub fn dump<W: Write>(text: &mut W, mem: &[u8]) {
79
let info = UnwindInfo::from_slice(mem);
80
81
writeln!(text, " version: {}", info.version).unwrap();
82
writeln!(text, " flags: {}", info.flags).unwrap();
83
writeln!(text, " prologue size: {}", info.prologue_size).unwrap();
84
writeln!(text, " frame register: {}", info.frame_register).unwrap();
85
writeln!(
86
text,
87
"frame register offset: {}",
88
info.frame_register_offset
89
)
90
.unwrap();
91
writeln!(text, " unwind codes: {}", info.unwind_codes.len()).unwrap();
92
93
for code in info.unwind_codes.iter().rev() {
94
writeln!(text).unwrap();
95
writeln!(text, " offset: {}", code.offset).unwrap();
96
writeln!(text, " op: {:?}", code.op).unwrap();
97
writeln!(text, " info: {}", code.info).unwrap();
98
match code.value {
99
UnwindValue::None => {}
100
UnwindValue::U16(v) => writeln!(text, " value: {v} (u16)").unwrap(),
101
UnwindValue::U32(v) => writeln!(text, " value: {v} (u32)").unwrap(),
102
};
103
}
104
}
105
106
#[derive(Debug)]
107
struct UnwindInfo {
108
version: u8,
109
flags: u8,
110
prologue_size: u8,
111
#[expect(dead_code, reason = "may get used later")]
112
unwind_code_count_raw: u8,
113
frame_register: u8,
114
frame_register_offset: u8,
115
unwind_codes: Vec<UnwindCode>,
116
}
117
118
impl UnwindInfo {
119
fn from_slice(mem: &[u8]) -> Self {
120
let version_and_flags = mem[0];
121
let prologue_size = mem[1];
122
let unwind_code_count_raw = mem[2];
123
let frame_register_and_offset = mem[3];
124
let mut unwind_codes = Vec::new();
125
126
let mut i = 0;
127
while i < unwind_code_count_raw {
128
let code = UnwindCode::from_slice(&mem[(4 + (i * 2) as usize)..]);
129
130
i += match &code.value {
131
UnwindValue::None => 1,
132
UnwindValue::U16(_) => 2,
133
UnwindValue::U32(_) => 3,
134
};
135
136
unwind_codes.push(code);
137
}
138
139
Self {
140
version: version_and_flags & 0x3,
141
flags: (version_and_flags & 0xF8) >> 3,
142
prologue_size,
143
unwind_code_count_raw,
144
frame_register: frame_register_and_offset & 0xF,
145
frame_register_offset: (frame_register_and_offset & 0xF0) >> 4,
146
unwind_codes,
147
}
148
}
149
}
150
151
#[derive(Debug)]
152
struct UnwindCode {
153
offset: u8,
154
op: UnwindOperation,
155
info: u8,
156
value: UnwindValue,
157
}
158
159
impl UnwindCode {
160
fn from_slice(mem: &[u8]) -> Self {
161
let offset = mem[0];
162
let op_and_info = mem[1];
163
let op = UnwindOperation::from(op_and_info & 0xF);
164
let info = (op_and_info & 0xF0) >> 4;
165
let unwind_le_bytes = |bytes| match (bytes, &mem[2..]) {
166
(2, &[b0, b1, ..]) => UnwindValue::U16(u16::from_le_bytes([b0, b1])),
167
(4, &[b0, b1, b2, b3, ..]) => {
168
UnwindValue::U32(u32::from_le_bytes([b0, b1, b2, b3]))
169
}
170
(_, _) => panic!("not enough bytes to unwind value"),
171
};
172
173
let value = match (&op, info) {
174
(UnwindOperation::LargeStackAlloc, 0) => unwind_le_bytes(2),
175
(UnwindOperation::LargeStackAlloc, 1) => unwind_le_bytes(4),
176
(UnwindOperation::LargeStackAlloc, _) => {
177
panic!("unexpected stack alloc info value")
178
}
179
(UnwindOperation::SaveNonvolatileRegister, _) => unwind_le_bytes(2),
180
(UnwindOperation::SaveNonvolatileRegisterFar, _) => unwind_le_bytes(4),
181
(UnwindOperation::SaveXmm128, _) => unwind_le_bytes(2),
182
(UnwindOperation::SaveXmm128Far, _) => unwind_le_bytes(4),
183
_ => UnwindValue::None,
184
};
185
186
Self {
187
offset,
188
op,
189
info,
190
value,
191
}
192
}
193
}
194
195
#[derive(Debug)]
196
enum UnwindOperation {
197
PushNonvolatileRegister = 0,
198
LargeStackAlloc = 1,
199
SmallStackAlloc = 2,
200
SetFramePointer = 3,
201
SaveNonvolatileRegister = 4,
202
SaveNonvolatileRegisterFar = 5,
203
SaveXmm128 = 8,
204
SaveXmm128Far = 9,
205
PushMachineFrame = 10,
206
}
207
208
impl From<u8> for UnwindOperation {
209
fn from(value: u8) -> Self {
210
// The numerical value is specified as part of the Windows x64 ABI
211
match value {
212
0 => Self::PushNonvolatileRegister,
213
1 => Self::LargeStackAlloc,
214
2 => Self::SmallStackAlloc,
215
3 => Self::SetFramePointer,
216
4 => Self::SaveNonvolatileRegister,
217
5 => Self::SaveNonvolatileRegisterFar,
218
8 => Self::SaveXmm128,
219
9 => Self::SaveXmm128Far,
220
10 => Self::PushMachineFrame,
221
_ => panic!("unsupported unwind operation"),
222
}
223
}
224
}
225
226
#[derive(Debug)]
227
enum UnwindValue {
228
None,
229
U16(u16),
230
U32(u32),
231
}
232
}
233
234
mod systemv {
235
fn register_name<'a>(register: gimli::Register) -> std::borrow::Cow<'a, str> {
236
Cow::Owned(format!("r{}", register.0))
237
}
238
239
pub fn dump<W: Write>(text: &mut W, bytes: &[u8], address_size: u8) {
240
let mut eh_frame = gimli::EhFrame::new(bytes, gimli::LittleEndian);
241
eh_frame.set_address_size(address_size);
242
let bases = gimli::BaseAddresses::default();
243
dump_eh_frame(text, &eh_frame, &bases, &register_name).unwrap();
244
}
245
246
// Remainder copied from https://github.com/gimli-rs/gimli/blob/1e49ffc9af4ec64a1b7316924d73c933dd7157c5/examples/dwarfdump.rs
247
use gimli::UnwindSection;
248
use std::borrow::Cow;
249
use std::collections::HashMap;
250
use std::fmt::{self, Debug, Write};
251
use std::result;
252
253
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
254
pub(super) enum Error {
255
GimliError(gimli::Error),
256
IoError,
257
}
258
259
impl fmt::Display for Error {
260
#[inline]
261
fn fmt(&self, f: &mut fmt::Formatter) -> ::std::result::Result<(), fmt::Error> {
262
Debug::fmt(self, f)
263
}
264
}
265
266
impl From<gimli::Error> for Error {
267
fn from(err: gimli::Error) -> Self {
268
Self::GimliError(err)
269
}
270
}
271
272
impl From<fmt::Error> for Error {
273
fn from(_: fmt::Error) -> Self {
274
Self::IoError
275
}
276
}
277
278
pub(super) type Result<T> = result::Result<T, Error>;
279
280
pub(super) trait Reader: gimli::Reader<Offset = usize> + Send + Sync {}
281
282
impl<'input, Endian> Reader for gimli::EndianSlice<'input, Endian> where
283
Endian: gimli::Endianity + Send + Sync
284
{
285
}
286
287
pub(super) fn dump_eh_frame<R: Reader, W: Write>(
288
w: &mut W,
289
eh_frame: &gimli::EhFrame<R>,
290
bases: &gimli::BaseAddresses,
291
register_name: &dyn Fn(gimli::Register) -> Cow<'static, str>,
292
) -> Result<()> {
293
let mut cies = HashMap::new();
294
295
let mut entries = eh_frame.entries(bases);
296
loop {
297
match entries.next()? {
298
None => return Ok(()),
299
Some(gimli::CieOrFde::Cie(cie)) => {
300
writeln!(w, "{:#010x}: CIE", cie.offset())?;
301
writeln!(w, " length: {:#010x}", cie.entry_len())?;
302
// TODO: CIE_id
303
writeln!(w, " version: {:#04x}", cie.version())?;
304
// TODO: augmentation
305
writeln!(w, " code_align: {}", cie.code_alignment_factor())?;
306
writeln!(w, " data_align: {}", cie.data_alignment_factor())?;
307
writeln!(w, " ra_register: {:#x}", cie.return_address_register().0)?;
308
if let Some(encoding) = cie.lsda_encoding() {
309
writeln!(w, " lsda_encoding: {:#02x}", encoding.0)?;
310
}
311
if let Some((encoding, personality)) = cie.personality_with_encoding() {
312
write!(w, " personality: {:#02x} ", encoding.0)?;
313
dump_pointer(w, personality)?;
314
writeln!(w)?;
315
}
316
if let Some(encoding) = cie.fde_address_encoding() {
317
writeln!(w, " fde_encoding: {:#02x}", encoding.0)?;
318
}
319
dump_cfi_instructions(
320
w,
321
cie.instructions(eh_frame, bases),
322
true,
323
register_name,
324
)?;
325
writeln!(w)?;
326
}
327
Some(gimli::CieOrFde::Fde(partial)) => {
328
let mut offset = None;
329
let fde = partial.parse(|_, bases, o| {
330
offset = Some(o);
331
cies.entry(o)
332
.or_insert_with(|| eh_frame.cie_from_offset(bases, o))
333
.clone()
334
})?;
335
336
writeln!(w)?;
337
writeln!(w, "{:#010x}: FDE", fde.offset())?;
338
writeln!(w, " length: {:#010x}", fde.entry_len())?;
339
writeln!(w, " CIE_pointer: {:#010x}", offset.unwrap().0)?;
340
// TODO: symbolicate the start address like the canonical dwarfdump does.
341
writeln!(w, " start_addr: {:#018x}", fde.initial_address())?;
342
writeln!(
343
w,
344
" range_size: {:#018x} (end_addr = {:#018x})",
345
fde.len(),
346
fde.initial_address() + fde.len()
347
)?;
348
if let Some(lsda) = fde.lsda() {
349
write!(w, " lsda: ")?;
350
dump_pointer(w, lsda)?;
351
writeln!(w)?;
352
}
353
dump_cfi_instructions(
354
w,
355
fde.instructions(eh_frame, bases),
356
false,
357
register_name,
358
)?;
359
writeln!(w)?;
360
}
361
}
362
}
363
}
364
365
fn dump_pointer<W: Write>(w: &mut W, p: gimli::Pointer) -> Result<()> {
366
match p {
367
gimli::Pointer::Direct(p) => {
368
write!(w, "{p:#018x}")?;
369
}
370
gimli::Pointer::Indirect(p) => {
371
write!(w, "({p:#018x})")?;
372
}
373
}
374
Ok(())
375
}
376
377
fn dump_cfi_instructions<R: Reader, W: Write>(
378
w: &mut W,
379
mut insns: gimli::CallFrameInstructionIter<R>,
380
is_initial: bool,
381
register_name: &dyn Fn(gimli::Register) -> Cow<'static, str>,
382
) -> Result<()> {
383
use gimli::CallFrameInstruction::*;
384
385
// TODO: we need to actually evaluate these instructions as we iterate them
386
// so we can print the initialized state for CIEs, and each unwind row's
387
// registers for FDEs.
388
//
389
// TODO: We should print DWARF expressions for the CFI instructions that
390
// embed DWARF expressions within themselves.
391
392
if !is_initial {
393
writeln!(w, " Instructions:")?;
394
}
395
396
loop {
397
match insns.next() {
398
Err(e) => {
399
writeln!(w, "Failed to decode CFI instruction: {e}")?;
400
return Ok(());
401
}
402
Ok(None) => {
403
if is_initial {
404
writeln!(w, " Instructions: Init State:")?;
405
}
406
return Ok(());
407
}
408
Ok(Some(op)) => match op {
409
SetLoc { address } => {
410
writeln!(w, " DW_CFA_set_loc ({address:#x})")?;
411
}
412
AdvanceLoc { delta } => {
413
writeln!(w, " DW_CFA_advance_loc ({delta})")?;
414
}
415
DefCfa { register, offset } => {
416
writeln!(
417
w,
418
" DW_CFA_def_cfa ({}, {})",
419
register_name(register),
420
offset
421
)?;
422
}
423
DefCfaSf {
424
register,
425
factored_offset,
426
} => {
427
writeln!(
428
w,
429
" DW_CFA_def_cfa_sf ({}, {})",
430
register_name(register),
431
factored_offset
432
)?;
433
}
434
DefCfaRegister { register } => {
435
writeln!(
436
w,
437
" DW_CFA_def_cfa_register ({})",
438
register_name(register)
439
)?;
440
}
441
DefCfaOffset { offset } => {
442
writeln!(w, " DW_CFA_def_cfa_offset ({offset})")?;
443
}
444
DefCfaOffsetSf { factored_offset } => {
445
writeln!(
446
w,
447
" DW_CFA_def_cfa_offset_sf ({factored_offset})"
448
)?;
449
}
450
DefCfaExpression { expression: _ } => {
451
writeln!(w, " DW_CFA_def_cfa_expression (...)")?;
452
}
453
Undefined { register } => {
454
writeln!(
455
w,
456
" DW_CFA_undefined ({})",
457
register_name(register)
458
)?;
459
}
460
SameValue { register } => {
461
writeln!(
462
w,
463
" DW_CFA_same_value ({})",
464
register_name(register)
465
)?;
466
}
467
Offset {
468
register,
469
factored_offset,
470
} => {
471
writeln!(
472
w,
473
" DW_CFA_offset ({}, {})",
474
register_name(register),
475
factored_offset
476
)?;
477
}
478
OffsetExtendedSf {
479
register,
480
factored_offset,
481
} => {
482
writeln!(
483
w,
484
" DW_CFA_offset_extended_sf ({}, {})",
485
register_name(register),
486
factored_offset
487
)?;
488
}
489
ValOffset {
490
register,
491
factored_offset,
492
} => {
493
writeln!(
494
w,
495
" DW_CFA_val_offset ({}, {})",
496
register_name(register),
497
factored_offset
498
)?;
499
}
500
ValOffsetSf {
501
register,
502
factored_offset,
503
} => {
504
writeln!(
505
w,
506
" DW_CFA_val_offset_sf ({}, {})",
507
register_name(register),
508
factored_offset
509
)?;
510
}
511
Register {
512
dest_register,
513
src_register,
514
} => {
515
writeln!(
516
w,
517
" DW_CFA_register ({}, {})",
518
register_name(dest_register),
519
register_name(src_register)
520
)?;
521
}
522
Expression {
523
register,
524
expression: _,
525
} => {
526
writeln!(
527
w,
528
" DW_CFA_expression ({}, ...)",
529
register_name(register)
530
)?;
531
}
532
ValExpression {
533
register,
534
expression: _,
535
} => {
536
writeln!(
537
w,
538
" DW_CFA_val_expression ({}, ...)",
539
register_name(register)
540
)?;
541
}
542
Restore { register } => {
543
writeln!(
544
w,
545
" DW_CFA_restore ({})",
546
register_name(register)
547
)?;
548
}
549
RememberState => {
550
writeln!(w, " DW_CFA_remember_state")?;
551
}
552
RestoreState => {
553
writeln!(w, " DW_CFA_restore_state")?;
554
}
555
ArgsSize { size } => {
556
writeln!(w, " DW_CFA_GNU_args_size ({size})")?;
557
}
558
NegateRaState => {
559
writeln!(w, " DW_CFA_AARCH64_negate_ra_state")?;
560
}
561
Nop => {
562
writeln!(w, " DW_CFA_nop")?;
563
}
564
},
565
}
566
}
567
}
568
}
569
570