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 #![cfg_attr(feature = "cargo-clippy", allow(clippy::cast_ptr_alignment))]
5
6 use crate::subtest::{run_filecheck, Context, SubTest, SubtestResult};
7 use cranelift_codegen::{self, ir, isa::unwind::UnwindInfo};
8 use cranelift_reader::TestCommand;
9 use gimli::{
10 write::{Address, EhFrame, EndianVec, FrameTable},
11 LittleEndian,
12 };
13 use std::borrow::Cow;
14
15 struct TestUnwind;
16
subtest(parsed: &TestCommand) -> SubtestResult<Box<dyn SubTest>>17 pub fn subtest(parsed: &TestCommand) -> SubtestResult<Box<dyn SubTest>> {
18 assert_eq!(parsed.command, "unwind");
19 if !parsed.options.is_empty() {
20 Err(format!("No options allowed on {}", parsed))
21 } else {
22 Ok(Box::new(TestUnwind))
23 }
24 }
25
26 impl SubTest for TestUnwind {
name(&self) -> &'static str27 fn name(&self) -> &'static str {
28 "unwind"
29 }
30
is_mutating(&self) -> bool31 fn is_mutating(&self) -> bool {
32 false
33 }
34
needs_isa(&self) -> bool35 fn needs_isa(&self) -> bool {
36 true
37 }
38
run(&self, func: Cow<ir::Function>, context: &Context) -> SubtestResult<()>39 fn run(&self, func: Cow<ir::Function>, context: &Context) -> SubtestResult<()> {
40 let isa = context.isa.expect("unwind needs an ISA");
41 let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned());
42
43 comp_ctx.compile(isa).expect("failed to compile function");
44
45 let mut text = String::new();
46 match comp_ctx.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 None => {}
66 }
67
68 run_filecheck(&text, context)
69 }
70 }
71
72 mod windowsx64 {
73 use byteorder::{ByteOrder, LittleEndian};
74 use std::fmt::Write;
75
dump<W: Write>(text: &mut W, mem: &[u8])76 pub fn dump<W: Write>(text: &mut W, mem: &[u8]) {
77 let info = UnwindInfo::from_slice(mem);
78
79 writeln!(text, " version: {}", info.version).unwrap();
80 writeln!(text, " flags: {}", info.flags).unwrap();
81 writeln!(text, " prologue size: {}", info.prologue_size).unwrap();
82 writeln!(text, " frame register: {}", info.frame_register).unwrap();
83 writeln!(
84 text,
85 "frame register offset: {}",
86 info.frame_register_offset
87 )
88 .unwrap();
89 writeln!(text, " unwind codes: {}", info.unwind_codes.len()).unwrap();
90
91 for code in info.unwind_codes.iter().rev() {
92 writeln!(text).unwrap();
93 writeln!(text, " offset: {}", code.offset).unwrap();
94 writeln!(text, " op: {:?}", code.op).unwrap();
95 writeln!(text, " info: {}", code.info).unwrap();
96 match code.value {
97 UnwindValue::None => {}
98 UnwindValue::U16(v) => {
99 writeln!(text, " value: {} (u16)", v).unwrap()
100 }
101 UnwindValue::U32(v) => {
102 writeln!(text, " value: {} (u32)", v).unwrap()
103 }
104 };
105 }
106 }
107
108 #[derive(Debug)]
109 struct UnwindInfo {
110 version: u8,
111 flags: u8,
112 prologue_size: u8,
113 unwind_code_count_raw: u8,
114 frame_register: u8,
115 frame_register_offset: u8,
116 unwind_codes: Vec<UnwindCode>,
117 }
118
119 impl UnwindInfo {
from_slice(mem: &[u8]) -> Self120 fn from_slice(mem: &[u8]) -> Self {
121 let version_and_flags = mem[0];
122 let prologue_size = mem[1];
123 let unwind_code_count_raw = mem[2];
124 let frame_register_and_offset = mem[3];
125 let mut unwind_codes = Vec::new();
126
127 let mut i = 0;
128 while i < unwind_code_count_raw {
129 let code = UnwindCode::from_slice(&mem[(4 + (i * 2) as usize)..]);
130
131 i += match &code.value {
132 UnwindValue::None => 1,
133 UnwindValue::U16(_) => 2,
134 UnwindValue::U32(_) => 3,
135 };
136
137 unwind_codes.push(code);
138 }
139
140 Self {
141 version: version_and_flags & 0x3,
142 flags: (version_and_flags & 0xF8) >> 3,
143 prologue_size,
144 unwind_code_count_raw,
145 frame_register: frame_register_and_offset & 0xF,
146 frame_register_offset: (frame_register_and_offset & 0xF0) >> 4,
147 unwind_codes,
148 }
149 }
150 }
151
152 #[derive(Debug)]
153 struct UnwindCode {
154 offset: u8,
155 op: UnwindOperation,
156 info: u8,
157 value: UnwindValue,
158 }
159
160 impl UnwindCode {
from_slice(mem: &[u8]) -> Self161 fn from_slice(mem: &[u8]) -> Self {
162 let offset = mem[0];
163 let op_and_info = mem[1];
164 let op = UnwindOperation::from(op_and_info & 0xF);
165 let info = (op_and_info & 0xF0) >> 4;
166
167 let value = match op {
168 UnwindOperation::LargeStackAlloc => match info {
169 0 => UnwindValue::U16(LittleEndian::read_u16(&mem[2..])),
170 1 => UnwindValue::U32(LittleEndian::read_u32(&mem[2..])),
171 _ => panic!("unexpected stack alloc info value"),
172 },
173 UnwindOperation::SaveNonvolatileRegister => {
174 UnwindValue::U16(LittleEndian::read_u16(&mem[2..]))
175 }
176 UnwindOperation::SaveNonvolatileRegisterFar => {
177 UnwindValue::U32(LittleEndian::read_u32(&mem[2..]))
178 }
179 UnwindOperation::SaveXmm128 => UnwindValue::U16(LittleEndian::read_u16(&mem[2..])),
180 UnwindOperation::SaveXmm128Far => {
181 UnwindValue::U32(LittleEndian::read_u32(&mem[2..]))
182 }
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 {
from(value: u8) -> Self209 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 {
register_name<'a>(register: gimli::Register) -> std::borrow::Cow<'a, str>235 fn register_name<'a>(register: gimli::Register) -> std::borrow::Cow<'a, str> {
236 Cow::Owned(format!("r{}", register.0))
237 }
238
dump<W: Write>(text: &mut W, bytes: &[u8], address_size: u8)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, ®ister_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]
fmt(&self, f: &mut fmt::Formatter) -> ::std::result::Result<(), fmt::Error>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 {
from(err: gimli::Error) -> Self267 fn from(err: gimli::Error) -> Self {
268 Self::GimliError(err)
269 }
270 }
271
272 impl From<fmt::Error> for Error {
from(_: fmt::Error) -> Self273 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
dump_eh_frame<R: Reader, W: Write>( w: &mut W, eh_frame: &gimli::EhFrame<R>, bases: &gimli::BaseAddresses, register_name: &dyn Fn(gimli::Register) -> Cow<'static, str>, ) -> Result<()>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
dump_pointer<W: Write>(w: &mut W, p: gimli::Pointer) -> Result<()>365 fn dump_pointer<W: Write>(w: &mut W, p: gimli::Pointer) -> Result<()> {
366 match p {
367 gimli::Pointer::Direct(p) => {
368 write!(w, "{:#018x}", p)?;
369 }
370 gimli::Pointer::Indirect(p) => {
371 write!(w, "({:#018x})", p)?;
372 }
373 }
374 Ok(())
375 }
376
377 #[allow(clippy::unneeded_field_pattern)]
dump_cfi_instructions<R: Reader, W: Write>( w: &mut W, mut insns: gimli::CallFrameInstructionIter<R>, is_initial: bool, register_name: &dyn Fn(gimli::Register) -> Cow<'static, str>, ) -> Result<()>378 fn dump_cfi_instructions<R: Reader, W: Write>(
379 w: &mut W,
380 mut insns: gimli::CallFrameInstructionIter<R>,
381 is_initial: bool,
382 register_name: &dyn Fn(gimli::Register) -> Cow<'static, str>,
383 ) -> Result<()> {
384 use gimli::CallFrameInstruction::*;
385
386 // TODO: we need to actually evaluate these instructions as we iterate them
387 // so we can print the initialized state for CIEs, and each unwind row's
388 // registers for FDEs.
389 //
390 // TODO: We should print DWARF expressions for the CFI instructions that
391 // embed DWARF expressions within themselves.
392
393 if !is_initial {
394 writeln!(w, " Instructions:")?;
395 }
396
397 loop {
398 match insns.next() {
399 Err(e) => {
400 writeln!(w, "Failed to decode CFI instruction: {}", e)?;
401 return Ok(());
402 }
403 Ok(None) => {
404 if is_initial {
405 writeln!(w, " Instructions: Init State:")?;
406 }
407 return Ok(());
408 }
409 Ok(Some(op)) => match op {
410 SetLoc { address } => {
411 writeln!(w, " DW_CFA_set_loc ({:#x})", address)?;
412 }
413 AdvanceLoc { delta } => {
414 writeln!(w, " DW_CFA_advance_loc ({})", delta)?;
415 }
416 DefCfa { register, offset } => {
417 writeln!(
418 w,
419 " DW_CFA_def_cfa ({}, {})",
420 register_name(register),
421 offset
422 )?;
423 }
424 DefCfaSf {
425 register,
426 factored_offset,
427 } => {
428 writeln!(
429 w,
430 " DW_CFA_def_cfa_sf ({}, {})",
431 register_name(register),
432 factored_offset
433 )?;
434 }
435 DefCfaRegister { register } => {
436 writeln!(
437 w,
438 " DW_CFA_def_cfa_register ({})",
439 register_name(register)
440 )?;
441 }
442 DefCfaOffset { offset } => {
443 writeln!(w, " DW_CFA_def_cfa_offset ({})", offset)?;
444 }
445 DefCfaOffsetSf { factored_offset } => {
446 writeln!(
447 w,
448 " DW_CFA_def_cfa_offset_sf ({})",
449 factored_offset
450 )?;
451 }
452 DefCfaExpression { expression: _ } => {
453 writeln!(w, " DW_CFA_def_cfa_expression (...)")?;
454 }
455 Undefined { register } => {
456 writeln!(
457 w,
458 " DW_CFA_undefined ({})",
459 register_name(register)
460 )?;
461 }
462 SameValue { register } => {
463 writeln!(
464 w,
465 " DW_CFA_same_value ({})",
466 register_name(register)
467 )?;
468 }
469 Offset {
470 register,
471 factored_offset,
472 } => {
473 writeln!(
474 w,
475 " DW_CFA_offset ({}, {})",
476 register_name(register),
477 factored_offset
478 )?;
479 }
480 OffsetExtendedSf {
481 register,
482 factored_offset,
483 } => {
484 writeln!(
485 w,
486 " DW_CFA_offset_extended_sf ({}, {})",
487 register_name(register),
488 factored_offset
489 )?;
490 }
491 ValOffset {
492 register,
493 factored_offset,
494 } => {
495 writeln!(
496 w,
497 " DW_CFA_val_offset ({}, {})",
498 register_name(register),
499 factored_offset
500 )?;
501 }
502 ValOffsetSf {
503 register,
504 factored_offset,
505 } => {
506 writeln!(
507 w,
508 " DW_CFA_val_offset_sf ({}, {})",
509 register_name(register),
510 factored_offset
511 )?;
512 }
513 Register {
514 dest_register,
515 src_register,
516 } => {
517 writeln!(
518 w,
519 " DW_CFA_register ({}, {})",
520 register_name(dest_register),
521 register_name(src_register)
522 )?;
523 }
524 Expression {
525 register,
526 expression: _,
527 } => {
528 writeln!(
529 w,
530 " DW_CFA_expression ({}, ...)",
531 register_name(register)
532 )?;
533 }
534 ValExpression {
535 register,
536 expression: _,
537 } => {
538 writeln!(
539 w,
540 " DW_CFA_val_expression ({}, ...)",
541 register_name(register)
542 )?;
543 }
544 Restore { register } => {
545 writeln!(
546 w,
547 " DW_CFA_restore ({})",
548 register_name(register)
549 )?;
550 }
551 RememberState => {
552 writeln!(w, " DW_CFA_remember_state")?;
553 }
554 RestoreState => {
555 writeln!(w, " DW_CFA_restore_state")?;
556 }
557 ArgsSize { size } => {
558 writeln!(w, " DW_CFA_GNU_args_size ({})", size)?;
559 }
560 Nop => {
561 writeln!(w, " DW_CFA_nop")?;
562 }
563 },
564 }
565 }
566 }
567 }
568