cranelift_filetests/
test_unwind.rs

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
5use crate::subtest::{run_filecheck, Context, SubTest};
6use cranelift_codegen::{ir, isa::unwind::UnwindInfo};
7use cranelift_reader::TestCommand;
8use gimli::{
9    write::{Address, EhFrame, EndianVec, FrameTable},
10    LittleEndian,
11};
12use std::borrow::Cow;
13
14struct TestUnwind;
15
16pub 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
24impl 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
75mod 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
234mod 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                        writeln!(w, "                DW_CFA_<unknown>")?;
566                    }
567                },
568            }
569        }
570    }
571}