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, &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]
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