Path: blob/main/cranelift/filetests/src/test_unwind.rs
1691 views
//! Test command for verifying the unwind emitted for each function.1//!2//! The `unwind` test command runs each function through the full code generator pipeline.34use crate::subtest::{Context, SubTest, run_filecheck};5use cranelift_codegen::{ir, isa::unwind::UnwindInfo};6use cranelift_reader::TestCommand;7use gimli::{8LittleEndian,9write::{Address, EhFrame, EndianVec, FrameTable},10};11use std::borrow::Cow;1213struct TestUnwind;1415pub fn subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn SubTest>> {16assert_eq!(parsed.command, "unwind");17if !parsed.options.is_empty() {18anyhow::bail!("No options allowed on {}", parsed);19}20Ok(Box::new(TestUnwind))21}2223impl SubTest for TestUnwind {24fn name(&self) -> &'static str {25"unwind"26}2728fn is_mutating(&self) -> bool {29false30}3132fn needs_isa(&self) -> bool {33true34}3536fn run(&self, func: Cow<ir::Function>, context: &Context) -> anyhow::Result<()> {37let isa = context.isa.expect("unwind needs an ISA");38let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned());3940let code = comp_ctx41.compile(isa, &mut Default::default())42.expect("failed to compile function");4344let mut text = String::new();45match code.create_unwind_info(isa).expect("unwind info") {46Some(UnwindInfo::WindowsX64(info)) => {47let mut mem = vec![0; info.emit_size()];48info.emit(&mut mem);49windowsx64::dump(&mut text, &mem);50}51Some(UnwindInfo::SystemV(info)) => {52let mut table = FrameTable::default();53let cie = isa54.create_systemv_cie()55.expect("the ISA should support a System V CIE");5657let cie_id = table.add_cie(cie);58table.add_fde(cie_id, info.to_fde(Address::Constant(0)));5960let mut eh_frame = EhFrame(EndianVec::new(LittleEndian));61table.write_eh_frame(&mut eh_frame).unwrap();62systemv::dump(&mut text, &eh_frame.0.into_vec(), isa.pointer_bytes())63}64Some(ui) => {65anyhow::bail!("Unexpected unwind info type: {:?}", ui);66}67None => {}68}6970run_filecheck(&text, context)71}72}7374mod windowsx64 {75use std::fmt::Write;7677pub fn dump<W: Write>(text: &mut W, mem: &[u8]) {78let info = UnwindInfo::from_slice(mem);7980writeln!(text, " version: {}", info.version).unwrap();81writeln!(text, " flags: {}", info.flags).unwrap();82writeln!(text, " prologue size: {}", info.prologue_size).unwrap();83writeln!(text, " frame register: {}", info.frame_register).unwrap();84writeln!(85text,86"frame register offset: {}",87info.frame_register_offset88)89.unwrap();90writeln!(text, " unwind codes: {}", info.unwind_codes.len()).unwrap();9192for code in info.unwind_codes.iter().rev() {93writeln!(text).unwrap();94writeln!(text, " offset: {}", code.offset).unwrap();95writeln!(text, " op: {:?}", code.op).unwrap();96writeln!(text, " info: {}", code.info).unwrap();97match code.value {98UnwindValue::None => {}99UnwindValue::U16(v) => writeln!(text, " value: {v} (u16)").unwrap(),100UnwindValue::U32(v) => writeln!(text, " value: {v} (u32)").unwrap(),101};102}103}104105#[derive(Debug)]106struct UnwindInfo {107version: u8,108flags: u8,109prologue_size: u8,110#[expect(dead_code, reason = "may get used later")]111unwind_code_count_raw: u8,112frame_register: u8,113frame_register_offset: u8,114unwind_codes: Vec<UnwindCode>,115}116117impl UnwindInfo {118fn from_slice(mem: &[u8]) -> Self {119let version_and_flags = mem[0];120let prologue_size = mem[1];121let unwind_code_count_raw = mem[2];122let frame_register_and_offset = mem[3];123let mut unwind_codes = Vec::new();124125let mut i = 0;126while i < unwind_code_count_raw {127let code = UnwindCode::from_slice(&mem[(4 + (i * 2) as usize)..]);128129i += match &code.value {130UnwindValue::None => 1,131UnwindValue::U16(_) => 2,132UnwindValue::U32(_) => 3,133};134135unwind_codes.push(code);136}137138Self {139version: version_and_flags & 0x3,140flags: (version_and_flags & 0xF8) >> 3,141prologue_size,142unwind_code_count_raw,143frame_register: frame_register_and_offset & 0xF,144frame_register_offset: (frame_register_and_offset & 0xF0) >> 4,145unwind_codes,146}147}148}149150#[derive(Debug)]151struct UnwindCode {152offset: u8,153op: UnwindOperation,154info: u8,155value: UnwindValue,156}157158impl UnwindCode {159fn from_slice(mem: &[u8]) -> Self {160let offset = mem[0];161let op_and_info = mem[1];162let op = UnwindOperation::from(op_and_info & 0xF);163let info = (op_and_info & 0xF0) >> 4;164let unwind_le_bytes = |bytes| match (bytes, &mem[2..]) {165(2, &[b0, b1, ..]) => UnwindValue::U16(u16::from_le_bytes([b0, b1])),166(4, &[b0, b1, b2, b3, ..]) => {167UnwindValue::U32(u32::from_le_bytes([b0, b1, b2, b3]))168}169(_, _) => panic!("not enough bytes to unwind value"),170};171172let value = match (&op, info) {173(UnwindOperation::LargeStackAlloc, 0) => unwind_le_bytes(2),174(UnwindOperation::LargeStackAlloc, 1) => unwind_le_bytes(4),175(UnwindOperation::LargeStackAlloc, _) => {176panic!("unexpected stack alloc info value")177}178(UnwindOperation::SaveNonvolatileRegister, _) => unwind_le_bytes(2),179(UnwindOperation::SaveNonvolatileRegisterFar, _) => unwind_le_bytes(4),180(UnwindOperation::SaveXmm128, _) => unwind_le_bytes(2),181(UnwindOperation::SaveXmm128Far, _) => unwind_le_bytes(4),182_ => UnwindValue::None,183};184185Self {186offset,187op,188info,189value,190}191}192}193194#[derive(Debug)]195enum UnwindOperation {196PushNonvolatileRegister = 0,197LargeStackAlloc = 1,198SmallStackAlloc = 2,199SetFramePointer = 3,200SaveNonvolatileRegister = 4,201SaveNonvolatileRegisterFar = 5,202SaveXmm128 = 8,203SaveXmm128Far = 9,204PushMachineFrame = 10,205}206207impl From<u8> for UnwindOperation {208fn from(value: u8) -> Self {209// The numerical value is specified as part of the Windows x64 ABI210match value {2110 => Self::PushNonvolatileRegister,2121 => Self::LargeStackAlloc,2132 => Self::SmallStackAlloc,2143 => Self::SetFramePointer,2154 => Self::SaveNonvolatileRegister,2165 => Self::SaveNonvolatileRegisterFar,2178 => Self::SaveXmm128,2189 => Self::SaveXmm128Far,21910 => Self::PushMachineFrame,220_ => panic!("unsupported unwind operation"),221}222}223}224225#[derive(Debug)]226enum UnwindValue {227None,228U16(u16),229U32(u32),230}231}232233mod systemv {234fn register_name<'a>(register: gimli::Register) -> std::borrow::Cow<'a, str> {235Cow::Owned(format!("r{}", register.0))236}237238pub fn dump<W: Write>(text: &mut W, bytes: &[u8], address_size: u8) {239let mut eh_frame = gimli::EhFrame::new(bytes, gimli::LittleEndian);240eh_frame.set_address_size(address_size);241let bases = gimli::BaseAddresses::default();242dump_eh_frame(text, &eh_frame, &bases, ®ister_name).unwrap();243}244245// Remainder copied from https://github.com/gimli-rs/gimli/blob/1e49ffc9af4ec64a1b7316924d73c933dd7157c5/examples/dwarfdump.rs246use gimli::UnwindSection;247use std::borrow::Cow;248use std::collections::HashMap;249use std::fmt::{self, Debug, Write};250use std::result;251252#[derive(Debug, Clone, Copy, PartialEq, Eq)]253pub(super) enum Error {254GimliError(gimli::Error),255IoError,256}257258impl fmt::Display for Error {259#[inline]260fn fmt(&self, f: &mut fmt::Formatter) -> ::std::result::Result<(), fmt::Error> {261Debug::fmt(self, f)262}263}264265impl From<gimli::Error> for Error {266fn from(err: gimli::Error) -> Self {267Self::GimliError(err)268}269}270271impl From<fmt::Error> for Error {272fn from(_: fmt::Error) -> Self {273Self::IoError274}275}276277pub(super) type Result<T> = result::Result<T, Error>;278279pub(super) trait Reader: gimli::Reader<Offset = usize> + Send + Sync {}280281impl<'input, Endian> Reader for gimli::EndianSlice<'input, Endian> where282Endian: gimli::Endianity + Send + Sync283{284}285286pub(super) fn dump_eh_frame<R: Reader, W: Write>(287w: &mut W,288eh_frame: &gimli::EhFrame<R>,289bases: &gimli::BaseAddresses,290register_name: &dyn Fn(gimli::Register) -> Cow<'static, str>,291) -> Result<()> {292let mut cies = HashMap::new();293294let mut entries = eh_frame.entries(bases);295loop {296match entries.next()? {297None => return Ok(()),298Some(gimli::CieOrFde::Cie(cie)) => {299writeln!(w, "{:#010x}: CIE", cie.offset())?;300writeln!(w, " length: {:#010x}", cie.entry_len())?;301// TODO: CIE_id302writeln!(w, " version: {:#04x}", cie.version())?;303// TODO: augmentation304writeln!(w, " code_align: {}", cie.code_alignment_factor())?;305writeln!(w, " data_align: {}", cie.data_alignment_factor())?;306writeln!(w, " ra_register: {:#x}", cie.return_address_register().0)?;307if let Some(encoding) = cie.lsda_encoding() {308writeln!(w, " lsda_encoding: {:#02x}", encoding.0)?;309}310if let Some((encoding, personality)) = cie.personality_with_encoding() {311write!(w, " personality: {:#02x} ", encoding.0)?;312dump_pointer(w, personality)?;313writeln!(w)?;314}315if let Some(encoding) = cie.fde_address_encoding() {316writeln!(w, " fde_encoding: {:#02x}", encoding.0)?;317}318dump_cfi_instructions(319w,320cie.instructions(eh_frame, bases),321true,322register_name,323)?;324writeln!(w)?;325}326Some(gimli::CieOrFde::Fde(partial)) => {327let mut offset = None;328let fde = partial.parse(|_, bases, o| {329offset = Some(o);330cies.entry(o)331.or_insert_with(|| eh_frame.cie_from_offset(bases, o))332.clone()333})?;334335writeln!(w)?;336writeln!(w, "{:#010x}: FDE", fde.offset())?;337writeln!(w, " length: {:#010x}", fde.entry_len())?;338writeln!(w, " CIE_pointer: {:#010x}", offset.unwrap().0)?;339// TODO: symbolicate the start address like the canonical dwarfdump does.340writeln!(w, " start_addr: {:#018x}", fde.initial_address())?;341writeln!(342w,343" range_size: {:#018x} (end_addr = {:#018x})",344fde.len(),345fde.initial_address() + fde.len()346)?;347if let Some(lsda) = fde.lsda() {348write!(w, " lsda: ")?;349dump_pointer(w, lsda)?;350writeln!(w)?;351}352dump_cfi_instructions(353w,354fde.instructions(eh_frame, bases),355false,356register_name,357)?;358writeln!(w)?;359}360}361}362}363364fn dump_pointer<W: Write>(w: &mut W, p: gimli::Pointer) -> Result<()> {365match p {366gimli::Pointer::Direct(p) => {367write!(w, "{p:#018x}")?;368}369gimli::Pointer::Indirect(p) => {370write!(w, "({p:#018x})")?;371}372}373Ok(())374}375376fn dump_cfi_instructions<R: Reader, W: Write>(377w: &mut W,378mut insns: gimli::CallFrameInstructionIter<R>,379is_initial: bool,380register_name: &dyn Fn(gimli::Register) -> Cow<'static, str>,381) -> Result<()> {382use gimli::CallFrameInstruction::*;383384// TODO: we need to actually evaluate these instructions as we iterate them385// so we can print the initialized state for CIEs, and each unwind row's386// registers for FDEs.387//388// TODO: We should print DWARF expressions for the CFI instructions that389// embed DWARF expressions within themselves.390391if !is_initial {392writeln!(w, " Instructions:")?;393}394395loop {396match insns.next() {397Err(e) => {398writeln!(w, "Failed to decode CFI instruction: {e}")?;399return Ok(());400}401Ok(None) => {402if is_initial {403writeln!(w, " Instructions: Init State:")?;404}405return Ok(());406}407Ok(Some(op)) => match op {408SetLoc { address } => {409writeln!(w, " DW_CFA_set_loc ({address:#x})")?;410}411AdvanceLoc { delta } => {412writeln!(w, " DW_CFA_advance_loc ({delta})")?;413}414DefCfa { register, offset } => {415writeln!(416w,417" DW_CFA_def_cfa ({}, {})",418register_name(register),419offset420)?;421}422DefCfaSf {423register,424factored_offset,425} => {426writeln!(427w,428" DW_CFA_def_cfa_sf ({}, {})",429register_name(register),430factored_offset431)?;432}433DefCfaRegister { register } => {434writeln!(435w,436" DW_CFA_def_cfa_register ({})",437register_name(register)438)?;439}440DefCfaOffset { offset } => {441writeln!(w, " DW_CFA_def_cfa_offset ({offset})")?;442}443DefCfaOffsetSf { factored_offset } => {444writeln!(445w,446" DW_CFA_def_cfa_offset_sf ({factored_offset})"447)?;448}449DefCfaExpression { expression: _ } => {450writeln!(w, " DW_CFA_def_cfa_expression (...)")?;451}452Undefined { register } => {453writeln!(454w,455" DW_CFA_undefined ({})",456register_name(register)457)?;458}459SameValue { register } => {460writeln!(461w,462" DW_CFA_same_value ({})",463register_name(register)464)?;465}466Offset {467register,468factored_offset,469} => {470writeln!(471w,472" DW_CFA_offset ({}, {})",473register_name(register),474factored_offset475)?;476}477OffsetExtendedSf {478register,479factored_offset,480} => {481writeln!(482w,483" DW_CFA_offset_extended_sf ({}, {})",484register_name(register),485factored_offset486)?;487}488ValOffset {489register,490factored_offset,491} => {492writeln!(493w,494" DW_CFA_val_offset ({}, {})",495register_name(register),496factored_offset497)?;498}499ValOffsetSf {500register,501factored_offset,502} => {503writeln!(504w,505" DW_CFA_val_offset_sf ({}, {})",506register_name(register),507factored_offset508)?;509}510Register {511dest_register,512src_register,513} => {514writeln!(515w,516" DW_CFA_register ({}, {})",517register_name(dest_register),518register_name(src_register)519)?;520}521Expression {522register,523expression: _,524} => {525writeln!(526w,527" DW_CFA_expression ({}, ...)",528register_name(register)529)?;530}531ValExpression {532register,533expression: _,534} => {535writeln!(536w,537" DW_CFA_val_expression ({}, ...)",538register_name(register)539)?;540}541Restore { register } => {542writeln!(543w,544" DW_CFA_restore ({})",545register_name(register)546)?;547}548RememberState => {549writeln!(w, " DW_CFA_remember_state")?;550}551RestoreState => {552writeln!(w, " DW_CFA_restore_state")?;553}554ArgsSize { size } => {555writeln!(w, " DW_CFA_GNU_args_size ({size})")?;556}557NegateRaState => {558writeln!(w, " DW_CFA_AARCH64_negate_ra_state")?;559}560Nop => {561writeln!(w, " DW_CFA_nop")?;562}563},564}565}566}567}568569570