1use 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 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, ®ister_name).unwrap();
244 }
245
246 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 writeln!(w, " version: {:#04x}", cie.version())?;
304 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 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 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}