From e6e67154c2cd4b68f1045930097df36783cc748c Mon Sep 17 00:00:00 2001 From: Yury Delendik Date: Mon, 13 Jan 2020 10:32:55 -0600 Subject: [PATCH] Refactor unwind; add FDE support. (#1320) * Refactor unwind * add FDE support * use sink directly in emit functions * pref off all unwinding generation with feature --- cranelift-codegen/Cargo.toml | 6 +- cranelift-codegen/src/binemit/mod.rs | 30 ++ cranelift-codegen/src/context.rs | 13 +- cranelift-codegen/src/ir/function.rs | 5 + cranelift-codegen/src/isa/mod.rs | 8 +- cranelift-codegen/src/isa/x86/abi.rs | 31 +- cranelift-codegen/src/isa/x86/fde.rs | 354 +++++++++++++++ cranelift-codegen/src/isa/x86/mod.rs | 16 +- cranelift-codegen/src/isa/x86/unwind.rs | 95 ++-- cranelift-filetests/Cargo.toml | 1 + cranelift-filetests/src/lib.rs | 2 + cranelift-filetests/src/test_fde.rs | 415 ++++++++++++++++++ cranelift-filetests/src/test_unwind.rs | 25 +- .../isa/x86/windows_systemv_x64_fde.clif | 54 +++ 14 files changed, 1000 insertions(+), 55 deletions(-) create mode 100644 cranelift-codegen/src/isa/x86/fde.rs create mode 100644 cranelift-filetests/src/test_fde.rs create mode 100644 filetests/isa/x86/windows_systemv_x64_fde.clif diff --git a/cranelift-codegen/Cargo.toml b/cranelift-codegen/Cargo.toml index 831c24f10..1a7b19564 100644 --- a/cranelift-codegen/Cargo.toml +++ b/cranelift-codegen/Cargo.toml @@ -20,6 +20,7 @@ hashbrown = { version = "0.6", optional = true } target-lexicon = "0.10" log = { version = "0.4.6", default-features = false } serde = { version = "1.0.94", features = ["derive"], optional = true } +gimli = { version = "0.19.0", default-features = false, features = ["write"], optional = true } smallvec = { version = "1.0.0" } thiserror = "1.0.4" byteorder = { version = "1.3.2", default-features = false } @@ -32,7 +33,7 @@ byteorder = { version = "1.3.2", default-features = false } cranelift-codegen-meta = { path = "meta", version = "0.54.0" } [features] -default = ["std", "basic-blocks"] +default = ["std", "basic-blocks", "unwind"] # The "std" feature enables use of libstd. The "core" feature enables use # of some minimal std-like replacement libraries. At least one of these two @@ -47,6 +48,9 @@ core = ["hashbrown"] # can significantly increase the size of the library. testing_hooks = [] +# This enables unwind info generation functionality. +unwind = ["gimli"] + # ISA targets for which we should build. # If no ISA targets are explicitly enabled, the ISA target for the host machine is enabled. x86 = [] diff --git a/cranelift-codegen/src/binemit/mod.rs b/cranelift-codegen/src/binemit/mod.rs index f7bdf01a3..450bcd33d 100644 --- a/cranelift-codegen/src/binemit/mod.rs +++ b/cranelift-codegen/src/binemit/mod.rs @@ -155,6 +155,36 @@ pub trait CodeSink { fn add_stackmap(&mut self, _: &[Value], _: &Function, _: &dyn TargetIsa); } +/// Type of the frame unwind information. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum FrameUnwindKind { + /// Windows fastcall unwinding (as in .pdata). + Fastcall, + /// FDE entry for libunwind (similar to .eh_frame format). + Libunwind, +} + +/// Offset in frame unwind information buffer. +pub type FrameUnwindOffset = usize; + +/// Sink for frame unwind information. +pub trait FrameUnwindSink { + /// Get the current position. + fn len(&self) -> FrameUnwindOffset; + + /// Add bytes to the code section. + fn bytes(&mut self, _: &[u8]); + + /// Reserves bytes in the buffer. + fn reserve(&mut self, _len: usize) {} + + /// Add a relocation entry. + fn reloc(&mut self, _: Reloc, _: FrameUnwindOffset); + + /// Specified offset to main structure. + fn set_entry_offset(&mut self, _: FrameUnwindOffset); +} + /// Report a bad encoding error. #[cold] pub fn bad_encoding(func: &Function, inst: Inst) -> ! { diff --git a/cranelift-codegen/src/context.rs b/cranelift-codegen/src/context.rs index c9fd1e6bc..ca70293c0 100644 --- a/cranelift-codegen/src/context.rs +++ b/cranelift-codegen/src/context.rs @@ -10,8 +10,8 @@ //! single ISA instance. use crate::binemit::{ - relax_branches, shrink_instructions, CodeInfo, MemoryCodeSink, RelocSink, StackmapSink, - TrapSink, + relax_branches, shrink_instructions, CodeInfo, FrameUnwindKind, FrameUnwindSink, + MemoryCodeSink, RelocSink, StackmapSink, TrapSink, }; use crate::dce::do_dce; use crate::dominator_tree::DominatorTree; @@ -201,8 +201,13 @@ impl Context { /// /// Only some calling conventions (e.g. Windows fastcall) will have unwind information. /// This is a no-op if the function has no unwind information. - pub fn emit_unwind_info(&self, isa: &dyn TargetIsa, mem: &mut Vec) { - isa.emit_unwind_info(&self.func, mem); + pub fn emit_unwind_info( + &self, + isa: &dyn TargetIsa, + kind: FrameUnwindKind, + sink: &mut dyn FrameUnwindSink, + ) { + isa.emit_unwind_info(&self.func, kind, sink); } /// Run the verifier on the function. diff --git a/cranelift-codegen/src/ir/function.rs b/cranelift-codegen/src/ir/function.rs index 48690c429..10536cd23 100644 --- a/cranelift-codegen/src/ir/function.rs +++ b/cranelift-codegen/src/ir/function.rs @@ -253,6 +253,11 @@ impl Function { /// Starts collection of debug information. pub fn collect_debug_info(&mut self) { self.dfg.collect_debug_info(); + self.collect_frame_layout_info(); + } + + /// Starts collection of frame layout information. + pub fn collect_frame_layout_info(&mut self) { self.frame_layout = Some(FrameLayout::new()); } diff --git a/cranelift-codegen/src/isa/mod.rs b/cranelift-codegen/src/isa/mod.rs index 4b0a3e7de..590f59a29 100644 --- a/cranelift-codegen/src/isa/mod.rs +++ b/cranelift-codegen/src/isa/mod.rs @@ -63,7 +63,6 @@ use crate::settings::SetResult; use crate::timing; use alloc::borrow::Cow; use alloc::boxed::Box; -use alloc::vec::Vec; use core::fmt; use target_lexicon::{triple, Architecture, PointerWidth, Triple}; use thiserror::Error; @@ -382,7 +381,12 @@ pub trait TargetIsa: fmt::Display + Send + Sync { /// Emit unwind information for the given function. /// /// Only some calling conventions (e.g. Windows fastcall) will have unwind information. - fn emit_unwind_info(&self, _func: &ir::Function, _mem: &mut Vec) { + fn emit_unwind_info( + &self, + _func: &ir::Function, + _kind: binemit::FrameUnwindKind, + _sink: &mut dyn binemit::FrameUnwindSink, + ) { // No-op by default } } diff --git a/cranelift-codegen/src/isa/x86/abi.rs b/cranelift-codegen/src/isa/x86/abi.rs index 78f3288fd..011548a5b 100644 --- a/cranelift-codegen/src/isa/x86/abi.rs +++ b/cranelift-codegen/src/isa/x86/abi.rs @@ -1,10 +1,15 @@ //! x86 ABI implementation. use super::super::settings as shared_settings; +#[cfg(feature = "unwind")] +use super::fde::emit_fde; use super::registers::{FPR, GPR, RU}; use super::settings as isa_settings; +#[cfg(feature = "unwind")] use super::unwind::UnwindInfo; use crate::abi::{legalize_args, ArgAction, ArgAssigner, ValueConversion}; +#[cfg(feature = "unwind")] +use crate::binemit::{FrameUnwindKind, FrameUnwindSink}; use crate::cursor::{Cursor, CursorPosition, EncCursor}; use crate::ir; use crate::ir::immediates::Imm64; @@ -18,7 +23,6 @@ use crate::regalloc::RegisterSet; use crate::result::CodegenResult; use crate::stack_layout::layout_stack; use alloc::borrow::Cow; -use alloc::vec::Vec; use core::i32; use std::boxed::Box; use target_lexicon::{PointerWidth, Triple}; @@ -947,10 +951,25 @@ fn insert_common_epilogue( } } -pub fn emit_unwind_info(func: &ir::Function, isa: &dyn TargetIsa, mem: &mut Vec) { - // Assumption: RBP is being used as the frame pointer - // In the future, Windows fastcall codegen should usually omit the frame pointer - if let Some(info) = UnwindInfo::try_from_func(func, isa, Some(RU::rbp.into())) { - info.emit(mem); +#[cfg(feature = "unwind")] +pub fn emit_unwind_info( + func: &ir::Function, + isa: &dyn TargetIsa, + kind: FrameUnwindKind, + sink: &mut dyn FrameUnwindSink, +) { + match kind { + FrameUnwindKind::Fastcall => { + // Assumption: RBP is being used as the frame pointer + // In the future, Windows fastcall codegen should usually omit the frame pointer + if let Some(info) = UnwindInfo::try_from_func(func, isa, Some(RU::rbp.into())) { + info.emit(sink); + } + } + FrameUnwindKind::Libunwind => { + if func.frame_layout.is_some() { + emit_fde(func, isa, sink); + } + } } } diff --git a/cranelift-codegen/src/isa/x86/fde.rs b/cranelift-codegen/src/isa/x86/fde.rs new file mode 100644 index 000000000..6572c650b --- /dev/null +++ b/cranelift-codegen/src/isa/x86/fde.rs @@ -0,0 +1,354 @@ +//! Support for FDE data generation. + +use crate::binemit::{FrameUnwindOffset, FrameUnwindSink, Reloc}; +use crate::ir::{FrameLayoutChange, Function}; +use crate::isa::{CallConv, RegUnit, TargetIsa}; +use alloc::vec::Vec; +use core::convert::TryInto; +use gimli::write::{ + Address, CallFrameInstruction, CommonInformationEntry, EhFrame, EndianVec, + FrameDescriptionEntry, FrameTable, Result, Writer, +}; +use gimli::{Encoding, Format, LittleEndian, Register, X86_64}; +use std::ptr; + +pub type FDERelocEntry = (FrameUnwindOffset, Reloc); + +const FUNCTION_ENTRY_ADDRESS: Address = Address::Symbol { + symbol: 0, + addend: 0, +}; + +#[derive(Clone)] +struct FDEWriter { + vec: EndianVec, + relocs: Vec, +} + +impl FDEWriter { + fn new() -> Self { + Self { + vec: EndianVec::new(LittleEndian), + relocs: Vec::new(), + } + } + fn into_vec_and_relocs(self) -> (Vec, Vec) { + (self.vec.into_vec(), self.relocs) + } +} + +impl Writer for FDEWriter { + type Endian = LittleEndian; + fn endian(&self) -> Self::Endian { + LittleEndian + } + fn len(&self) -> usize { + self.vec.len() + } + fn write(&mut self, bytes: &[u8]) -> Result<()> { + self.vec.write(bytes) + } + fn write_at(&mut self, offset: usize, bytes: &[u8]) -> Result<()> { + self.vec.write_at(offset, bytes) + } + fn write_address(&mut self, address: Address, size: u8) -> Result<()> { + match address { + Address::Constant(_) => self.vec.write_address(address, size), + Address::Symbol { .. } => { + assert_eq!(address, FUNCTION_ENTRY_ADDRESS); + let rt = match size { + 4 => Reloc::Abs4, + 8 => Reloc::Abs8, + _ => { + panic!("Unexpected address size at FDEWriter::write_address"); + } + }; + self.relocs.push((self.vec.len().try_into().unwrap(), rt)); + self.vec.write_udata(0, size) + } + } + } +} + +fn return_address_reg(isa: &dyn TargetIsa) -> Register { + assert!(isa.name() == "x86" && isa.pointer_bits() == 64); + X86_64::RA +} + +fn map_reg(isa: &dyn TargetIsa, reg: RegUnit) -> Register { + assert!(isa.name() == "x86" && isa.pointer_bits() == 64); + // Mapping from https://github.com/bytecodealliance/cranelift/pull/902 by @iximeow + const X86_GP_REG_MAP: [gimli::Register; 16] = [ + X86_64::RAX, + X86_64::RCX, + X86_64::RDX, + X86_64::RBX, + X86_64::RSP, + X86_64::RBP, + X86_64::RSI, + X86_64::RDI, + X86_64::R8, + X86_64::R9, + X86_64::R10, + X86_64::R11, + X86_64::R12, + X86_64::R13, + X86_64::R14, + X86_64::R15, + ]; + const X86_XMM_REG_MAP: [gimli::Register; 16] = [ + X86_64::XMM0, + X86_64::XMM1, + X86_64::XMM2, + X86_64::XMM3, + X86_64::XMM4, + X86_64::XMM5, + X86_64::XMM6, + X86_64::XMM7, + X86_64::XMM8, + X86_64::XMM9, + X86_64::XMM10, + X86_64::XMM11, + X86_64::XMM12, + X86_64::XMM13, + X86_64::XMM14, + X86_64::XMM15, + ]; + let reg_info = isa.register_info(); + let bank = reg_info.bank_containing_regunit(reg).unwrap(); + match bank.name { + "IntRegs" => { + // x86 GP registers have a weird mapping to DWARF registers, so we use a + // lookup table. + X86_GP_REG_MAP[(reg - bank.first_unit) as usize] + } + "FloatRegs" => X86_XMM_REG_MAP[(reg - bank.first_unit) as usize], + _ => { + panic!("unsupported register bank: {}", bank.name); + } + } +} + +fn to_cfi( + isa: &dyn TargetIsa, + change: &FrameLayoutChange, + cfa_def_reg: &mut Register, + cfa_def_offset: &mut i32, +) -> Option { + Some(match change { + FrameLayoutChange::CallFrameAddressAt { reg, offset } => { + let mapped = map_reg(isa, *reg); + let offset = (*offset) as i32; + if mapped != *cfa_def_reg && offset != *cfa_def_offset { + *cfa_def_reg = mapped; + *cfa_def_offset = offset; + CallFrameInstruction::Cfa(mapped, offset) + } else if offset != *cfa_def_offset { + *cfa_def_offset = offset; + CallFrameInstruction::CfaOffset(offset) + } else if mapped != *cfa_def_reg { + *cfa_def_reg = mapped; + CallFrameInstruction::CfaRegister(mapped) + } else { + return None; + } + } + FrameLayoutChange::RegAt { reg, cfa_offset } => { + assert!(cfa_offset % -8 == 0); + let cfa_offset = *cfa_offset as i32; + let mapped = map_reg(isa, *reg); + CallFrameInstruction::Offset(mapped, cfa_offset) + } + FrameLayoutChange::ReturnAddressAt { cfa_offset } => { + assert!(cfa_offset % -8 == 0); + let cfa_offset = *cfa_offset as i32; + CallFrameInstruction::Offset(X86_64::RA, cfa_offset) + } + _ => { + return None; + } + }) +} + +/// Creates FDE structure from FrameLayout. +pub fn emit_fde(func: &Function, isa: &dyn TargetIsa, sink: &mut dyn FrameUnwindSink) { + assert!(isa.name() == "x86"); + + // Expecting function with System V prologue + assert!( + func.signature.call_conv == CallConv::Fast + || func.signature.call_conv == CallConv::Cold + || func.signature.call_conv == CallConv::SystemV + ); + + assert!(func.frame_layout.is_some(), "expected func.frame_layout"); + let frame_layout = func.frame_layout.as_ref().unwrap(); + + let mut ebbs = func.layout.ebbs().collect::>(); + ebbs.sort_by_key(|ebb| func.offsets[*ebb]); // Ensure inst offsets always increase + + let encinfo = isa.encoding_info(); + let mut last_offset = 0; + let mut changes = Vec::new(); + for ebb in ebbs { + for (offset, inst, size) in func.inst_offsets(ebb, &encinfo) { + let address_offset = (offset + size) as usize; + assert!(last_offset <= address_offset); + if let Some(cmds) = frame_layout.instructions.get(&inst) { + for cmd in cmds.iter() { + changes.push((address_offset, cmd.clone())); + } + } + last_offset = address_offset; + } + } + + let len = last_offset as u32; + + let word_size = isa.pointer_bytes() as i32; + + let encoding = Encoding { + format: Format::Dwarf32, + version: 1, + address_size: word_size as u8, + }; + let mut frames = FrameTable::default(); + + let mut cfa_def_reg = return_address_reg(isa); + let mut cfa_def_offset = 0i32; + + let mut cie = CommonInformationEntry::new( + encoding, + /* code_alignment_factor = */ 1, + /* data_alignment_factor = */ -word_size as i8, + return_address_reg(isa), + ); + for ch in frame_layout.initial.iter() { + if let Some(cfi) = to_cfi(isa, ch, &mut cfa_def_reg, &mut cfa_def_offset) { + cie.add_instruction(cfi); + } + } + + let cie_id = frames.add_cie(cie); + + let mut fde = FrameDescriptionEntry::new(FUNCTION_ENTRY_ADDRESS, len); + + for (addr, ch) in changes.iter() { + if let Some(cfi) = to_cfi(isa, ch, &mut cfa_def_reg, &mut cfa_def_offset) { + fde.add_instruction((*addr) as u32, cfi); + } + } + + frames.add_fde(cie_id, fde); + + let mut eh_frame = EhFrame::from(FDEWriter::new()); + frames.write_eh_frame(&mut eh_frame).unwrap(); + + let (bytes, relocs) = eh_frame.clone().into_vec_and_relocs(); + + let unwind_start = sink.len(); + sink.bytes(&bytes); + + for (off, r) in relocs { + sink.reloc(r, off + unwind_start); + } + let fde_offset = unsafe { ptr::read::(bytes.as_ptr() as *const u32) } as usize + 4; + sink.set_entry_offset(unwind_start + fde_offset); + + // Need 0 marker for GCC unwind to end FDE "list". + sink.bytes(&[0, 0, 0, 0]); +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::binemit::{FrameUnwindOffset, Reloc}; + use crate::cursor::{Cursor, FuncCursor}; + use crate::ir::{ExternalName, InstBuilder, Signature, StackSlotData, StackSlotKind}; + use crate::isa::{lookup, CallConv}; + use crate::settings::{builder, Flags}; + use crate::Context; + use std::str::FromStr; + use target_lexicon::triple; + + struct SimpleUnwindSink(pub Vec, pub usize, pub Vec<(Reloc, usize)>); + impl FrameUnwindSink for SimpleUnwindSink { + fn len(&self) -> FrameUnwindOffset { + self.0.len() + } + fn bytes(&mut self, b: &[u8]) { + self.0.extend_from_slice(b); + } + fn reloc(&mut self, r: Reloc, off: FrameUnwindOffset) { + self.2.push((r, off)); + } + fn set_entry_offset(&mut self, off: FrameUnwindOffset) { + self.1 = off; + } + } + + #[test] + fn test_simple_func() { + let isa = lookup(triple!("x86_64")) + .expect("expect x86 ISA") + .finish(Flags::new(builder())); + + let mut context = Context::for_function(create_function( + CallConv::SystemV, + Some(StackSlotData::new(StackSlotKind::ExplicitSlot, 64)), + )); + context.func.collect_frame_layout_info(); + + context.compile(&*isa).expect("expected compilation"); + + let mut sink = SimpleUnwindSink(Vec::new(), 0, Vec::new()); + emit_fde(&context.func, &*isa, &mut sink); + + assert_eq!( + sink.0, + vec![ + 20, 0, 0, 0, // CIE len + 0, 0, 0, 0, // CIE marker + 1, // version + 0, // augmentation string + 1, // code aligment = 1 + 120, // data alignment = -8 + 16, // RA = r16 + 0x0c, 0x07, 0x08, // DW_CFA_def_cfa r7, 8 + 0x90, 0x01, // DW_CFA_offset r16, -8 * 1 + 0, 0, 0, 0, 0, 0, // padding + 36, 0, 0, 0, // FDE len + 28, 0, 0, 0, // CIE offset + 0, 0, 0, 0, 0, 0, 0, 0, // addr reloc + 16, 0, 0, 0, 0, 0, 0, 0, // function length + 0x42, // DW_CFA_advance_loc 2 + 0x0e, 0x10, // DW_CFA_def_cfa_offset 16 + 0x86, 0x02, // DW_CFA_offset r6, -8 * 2 + 0x43, // DW_CFA_advance_loc 3 + 0x0d, 0x06, // DW_CFA_def_cfa_register + 0x4a, // DW_CFA_advance_loc 10 + 0x0c, 0x07, 0x08, // DW_CFA_def_cfa r7, 8 + 0, 0, 0, 0, // padding + 0, 0, 0, 0, // End of FDEs + ] + ); + assert_eq!(sink.1, 24); + assert_eq!(sink.2.len(), 1); + } + + fn create_function(call_conv: CallConv, stack_slot: Option) -> Function { + let mut func = + Function::with_name_signature(ExternalName::user(0, 0), Signature::new(call_conv)); + + let ebb0 = func.dfg.make_ebb(); + let mut pos = FuncCursor::new(&mut func); + pos.insert_ebb(ebb0); + pos.ins().return_(&[]); + + if let Some(stack_slot) = stack_slot { + func.stack_slots.push(stack_slot); + } + + func + } +} diff --git a/cranelift-codegen/src/isa/x86/mod.rs b/cranelift-codegen/src/isa/x86/mod.rs index e2e785675..c3b871ab4 100644 --- a/cranelift-codegen/src/isa/x86/mod.rs +++ b/cranelift-codegen/src/isa/x86/mod.rs @@ -3,14 +3,19 @@ mod abi; mod binemit; mod enc_tables; +#[cfg(feature = "unwind")] +mod fde; mod registers; pub mod settings; +#[cfg(feature = "unwind")] mod unwind; use super::super::settings as shared_settings; #[cfg(feature = "testing_hooks")] use crate::binemit::CodeSink; use crate::binemit::{emit_function, MemoryCodeSink}; +#[cfg(feature = "unwind")] +use crate::binemit::{FrameUnwindKind, FrameUnwindSink}; use crate::ir; use crate::isa::enc_tables::{self as shared_enc_tables, lookup_enclist, Encodings}; use crate::isa::Builder as IsaBuilder; @@ -20,7 +25,6 @@ use crate::result::CodegenResult; use crate::timing; use alloc::borrow::Cow; use alloc::boxed::Box; -use alloc::vec::Vec; use core::fmt; use target_lexicon::{PointerWidth, Triple}; @@ -157,8 +161,14 @@ impl TargetIsa for Isa { /// Emit unwind information for the given function. /// /// Only some calling conventions (e.g. Windows fastcall) will have unwind information. - fn emit_unwind_info(&self, func: &ir::Function, mem: &mut Vec) { - abi::emit_unwind_info(func, self, mem); + #[cfg(feature = "unwind")] + fn emit_unwind_info( + &self, + func: &ir::Function, + kind: FrameUnwindKind, + sink: &mut dyn FrameUnwindSink, + ) { + abi::emit_unwind_info(func, self, kind, sink); } } diff --git a/cranelift-codegen/src/isa/x86/unwind.rs b/cranelift-codegen/src/isa/x86/unwind.rs index 2cddf4078..ce05f3afa 100644 --- a/cranelift-codegen/src/isa/x86/unwind.rs +++ b/cranelift-codegen/src/isa/x86/unwind.rs @@ -1,6 +1,7 @@ //! Unwind information for x64 Windows. use super::registers::RU; +use crate::binemit::FrameUnwindSink; use crate::ir::{Function, InstructionData, Opcode}; use crate::isa::{CallConv, RegUnit, TargetIsa}; use alloc::vec::Vec; @@ -11,16 +12,20 @@ const SMALL_ALLOC_MAX_SIZE: u32 = 128; /// Maximum (inclusive) size of a "large" stack allocation that can represented in 16-bits const LARGE_ALLOC_16BIT_MAX_SIZE: u32 = 524280; -fn write_u16(mem: &mut Vec, v: u16) { +fn write_u8(sink: &mut dyn FrameUnwindSink, v: u8) { + sink.bytes(&[v]); +} + +fn write_u16(sink: &mut dyn FrameUnwindSink, v: u16) { let mut buf = [0; 2]; T::write_u16(&mut buf, v); - mem.extend(buf.iter()); + sink.bytes(&buf); } -fn write_u32(mem: &mut Vec, v: u32) { +fn write_u32(sink: &mut dyn FrameUnwindSink, v: u32) { let mut buf = [0; 4]; T::write_u32(&mut buf, v); - mem.extend(buf.iter()); + sink.bytes(&buf); } /// The supported unwind codes for the x64 Windows ABI. @@ -36,7 +41,7 @@ enum UnwindCode { } impl UnwindCode { - fn emit(&self, mem: &mut Vec) { + fn emit(&self, sink: &mut dyn FrameUnwindSink) { enum UnwindOperation { PushNonvolatileRegister, LargeStackAlloc, @@ -46,30 +51,37 @@ impl UnwindCode { match self { Self::PushRegister { offset, reg } => { - mem.push(*offset); - mem.push(((*reg as u8) << 4) | (UnwindOperation::PushNonvolatileRegister as u8)); + write_u8(sink, *offset); + write_u8( + sink, + ((*reg as u8) << 4) | (UnwindOperation::PushNonvolatileRegister as u8), + ); } Self::StackAlloc { offset, size } => { // Stack allocations on Windows must be a multiple of 8 and be at least 1 slot assert!(*size >= 8); assert!((*size % 8) == 0); - mem.push(*offset); + write_u8(sink, *offset); if *size <= SMALL_ALLOC_MAX_SIZE { - mem.push( + write_u8( + sink, ((((*size - 8) / 8) as u8) << 4) | UnwindOperation::SmallStackAlloc as u8, ); } else if *size <= LARGE_ALLOC_16BIT_MAX_SIZE { - mem.push(UnwindOperation::LargeStackAlloc as u8); - write_u16::(mem, (*size / 8) as u16); + write_u8(sink, UnwindOperation::LargeStackAlloc as u8); + write_u16::(sink, (*size / 8) as u16); } else { - mem.push((1 << 4) | (UnwindOperation::LargeStackAlloc as u8)); - write_u32::(mem, *size); + write_u8(sink, (1 << 4) | (UnwindOperation::LargeStackAlloc as u8)); + write_u32::(sink, *size); } } Self::SetFramePointer { offset, sp_offset } => { - mem.push(*offset); - mem.push((*sp_offset << 4) | (UnwindOperation::SetFramePointer as u8)); + write_u8(sink, *offset); + write_u8( + sink, + (*sp_offset << 4) | (UnwindOperation::SetFramePointer as u8), + ); } }; } @@ -231,48 +243,49 @@ impl UnwindInfo { .fold(0, |nodes, c| nodes + c.node_count()) } - pub fn emit(&self, mem: &mut Vec) { + pub fn emit(&self, sink: &mut dyn FrameUnwindSink) { const UNWIND_INFO_VERSION: u8 = 1; let size = self.size(); - let offset = mem.len(); + let offset = sink.len(); // Ensure the memory is 32-bit aligned assert_eq!(offset % 4, 0); - mem.reserve(offset + size); + sink.reserve(offset + size); let node_count = self.node_count(); assert!(node_count <= 256); - mem.push((self.flags << 3) | UNWIND_INFO_VERSION); - mem.push(self.prologue_size); - mem.push(node_count as u8); + write_u8(sink, (self.flags << 3) | UNWIND_INFO_VERSION); + write_u8(sink, self.prologue_size); + write_u8(sink, node_count as u8); if let Some(reg) = self.frame_register { - mem.push((self.frame_register_offset << 4) | reg as u8); + write_u8(sink, (self.frame_register_offset << 4) | reg as u8); } else { - mem.push(0); + write_u8(sink, 0); } // Unwind codes are written in reverse order (prologue offset descending) for code in self.unwind_codes.iter().rev() { - code.emit(mem); + code.emit(sink); } // To keep a 32-bit alignment, emit 2 bytes of padding if there's an odd number of 16-bit nodes if (node_count & 1) == 1 { - write_u16::(mem, 0); + write_u16::(sink, 0); } // Ensure the correct number of bytes was emitted - assert_eq!(mem.len() - offset, size); + assert_eq!(sink.len() - offset, size); } } #[cfg(test)] mod tests { use super::*; + use crate::binemit::{FrameUnwindOffset, Reloc}; use crate::cursor::{Cursor, FuncCursor}; use crate::ir::{ExternalName, InstBuilder, Signature, StackSlotData, StackSlotKind}; use crate::isa::{lookup, CallConv}; @@ -281,6 +294,18 @@ mod tests { use std::str::FromStr; use target_lexicon::triple; + struct SimpleUnwindSink(pub Vec); + impl FrameUnwindSink for SimpleUnwindSink { + fn len(&self) -> FrameUnwindOffset { + self.0.len() + } + fn bytes(&mut self, b: &[u8]) { + self.0.extend_from_slice(b); + } + fn reloc(&mut self, _: Reloc, _: FrameUnwindOffset) {} + fn set_entry_offset(&mut self, _: FrameUnwindOffset) {} + } + #[test] fn test_wrong_calling_convention() { let isa = lookup(triple!("x86_64")) @@ -336,11 +361,11 @@ mod tests { assert_eq!(unwind.size(), 12); - let mut mem = Vec::new(); - unwind.emit(&mut mem); + let mut sink = SimpleUnwindSink(Vec::new()); + unwind.emit(&mut sink); assert_eq!( - mem, + sink.0, [ 0x01, // Version and flags (version 1, no flags) 0x09, // Prologue size @@ -400,11 +425,11 @@ mod tests { assert_eq!(unwind.size(), 12); - let mut mem = Vec::new(); - unwind.emit(&mut mem); + let mut sink = SimpleUnwindSink(Vec::new()); + unwind.emit(&mut sink); assert_eq!( - mem, + sink.0, [ 0x01, // Version and flags (version 1, no flags) 0x1B, // Prologue size @@ -464,11 +489,11 @@ mod tests { assert_eq!(unwind.size(), 16); - let mut mem = Vec::new(); - unwind.emit(&mut mem); + let mut sink = SimpleUnwindSink(Vec::new()); + unwind.emit(&mut sink); assert_eq!( - mem, + sink.0, [ 0x01, // Version and flags (version 1, no flags) 0x1B, // Prologue size diff --git a/cranelift-filetests/Cargo.toml b/cranelift-filetests/Cargo.toml index da2a92814..def330776 100644 --- a/cranelift-filetests/Cargo.toml +++ b/cranelift-filetests/Cargo.toml @@ -16,6 +16,7 @@ cranelift-reader = { path = "../cranelift-reader", version = "0.54.0" } cranelift-preopt = { path = "../cranelift-preopt", version = "0.54.0" } file-per-thread-logger = "0.1.2" filecheck = "0.4.0" +gimli = { version = "0.19.0", default-features = false, features = ["read"] } log = "0.4.6" memmap = "0.7.0" num_cpus = "1.8.0" diff --git a/cranelift-filetests/src/lib.rs b/cranelift-filetests/src/lib.rs index ded3acd16..0d3b12e45 100644 --- a/cranelift-filetests/src/lib.rs +++ b/cranelift-filetests/src/lib.rs @@ -42,6 +42,7 @@ mod test_cat; mod test_compile; mod test_dce; mod test_domtree; +mod test_fde; mod test_legalizer; mod test_licm; mod test_postopt; @@ -137,6 +138,7 @@ fn new_subtest(parsed: &TestCommand) -> subtest::SubtestResult test_preopt::subtest(parsed), "safepoint" => test_safepoint::subtest(parsed), "unwind" => test_unwind::subtest(parsed), + "fde" => test_fde::subtest(parsed), _ => Err(format!("unknown test command '{}'", parsed.command)), } } diff --git a/cranelift-filetests/src/test_fde.rs b/cranelift-filetests/src/test_fde.rs new file mode 100644 index 000000000..1a9e3250d --- /dev/null +++ b/cranelift-filetests/src/test_fde.rs @@ -0,0 +1,415 @@ +//! Test command for verifying the unwind emitted for each function. +//! +//! The `unwind` test command runs each function through the full code generator pipeline. +#![cfg_attr(feature = "cargo-clippy", allow(clippy::cast_ptr_alignment))] + +use crate::subtest::{run_filecheck, Context, SubTest, SubtestResult}; +use cranelift_codegen; +use cranelift_codegen::binemit::{FrameUnwindKind, FrameUnwindOffset, FrameUnwindSink, Reloc}; +use cranelift_codegen::ir; +use cranelift_reader::TestCommand; +use std::borrow::Cow; +use std::fmt::Write; + +struct TestUnwind; + +pub fn subtest(parsed: &TestCommand) -> SubtestResult> { + assert_eq!(parsed.command, "fde"); + if !parsed.options.is_empty() { + Err(format!("No options allowed on {}", parsed)) + } else { + Ok(Box::new(TestUnwind)) + } +} + +impl SubTest for TestUnwind { + fn name(&self) -> &'static str { + "fde" + } + + fn is_mutating(&self) -> bool { + false + } + + fn needs_isa(&self) -> bool { + true + } + + fn run(&self, func: Cow, context: &Context) -> SubtestResult<()> { + let isa = context.isa.expect("unwind needs an ISA"); + + if func.signature.call_conv != cranelift_codegen::isa::CallConv::SystemV { + return run_filecheck(&"No unwind information.", context); + } + + let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned()); + comp_ctx.func.collect_frame_layout_info(); + + comp_ctx.compile(isa).expect("failed to compile function"); + + struct SimpleUnwindSink(pub Vec, pub usize, pub Vec<(Reloc, usize)>); + impl FrameUnwindSink for SimpleUnwindSink { + fn len(&self) -> FrameUnwindOffset { + self.0.len() + } + fn bytes(&mut self, b: &[u8]) { + self.0.extend_from_slice(b); + } + fn reloc(&mut self, r: Reloc, off: FrameUnwindOffset) { + self.2.push((r, off)); + } + fn set_entry_offset(&mut self, off: FrameUnwindOffset) { + self.1 = off; + } + } + + let mut sink = SimpleUnwindSink(Vec::new(), 0, Vec::new()); + comp_ctx.emit_unwind_info(isa, FrameUnwindKind::Libunwind, &mut sink); + + let mut text = String::new(); + if sink.0.is_empty() { + writeln!(text, "No unwind information.").unwrap(); + } else { + print_unwind_info(&mut text, &sink.0, isa.pointer_bytes()); + writeln!(text, "Entry: {}", sink.1).unwrap(); + writeln!(text, "Relocs: {:?}", sink.2).unwrap(); + } + + run_filecheck(&text, context) + } +} + +fn register_name<'a>(register: gimli::Register) -> std::borrow::Cow<'a, str> { + Cow::Owned(format!("r{}", register.0)) +} + +fn print_unwind_info(text: &mut String, mem: &[u8], address_size: u8) { + let mut eh_frame = gimli::EhFrame::new(mem, gimli::LittleEndian); + eh_frame.set_address_size(address_size); + let bases = gimli::BaseAddresses::default(); + dwarfdump::dump_eh_frame(text, &eh_frame, &bases, ®ister_name).unwrap(); +} + +mod dwarfdump { + // Copied from https://github.com/gimli-rs/gimli/blob/1e49ffc9af4ec64a1b7316924d73c933dd7157c5/examples/dwarfdump.rs + use gimli::UnwindSection; + use std::borrow::Cow; + use std::collections::HashMap; + use std::fmt::{self, Debug, Write}; + use std::result; + + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub(super) enum Error { + GimliError(gimli::Error), + IoError, + } + + impl fmt::Display for Error { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter) -> ::std::result::Result<(), fmt::Error> { + Debug::fmt(self, f) + } + } + + impl From for Error { + fn from(err: gimli::Error) -> Self { + Error::GimliError(err) + } + } + + impl From for Error { + fn from(_: fmt::Error) -> Self { + Error::IoError + } + } + + pub(super) type Result = result::Result; + + pub(super) trait Reader: gimli::Reader + Send + Sync {} + + impl<'input, Endian> Reader for gimli::EndianSlice<'input, Endian> where + Endian: gimli::Endianity + Send + Sync + { + } + + pub(super) fn dump_eh_frame( + w: &mut W, + eh_frame: &gimli::EhFrame, + bases: &gimli::BaseAddresses, + register_name: &dyn Fn(gimli::Register) -> Cow<'static, str>, + ) -> Result<()> { + let mut cies = HashMap::new(); + + let mut entries = eh_frame.entries(bases); + loop { + match entries.next()? { + None => return Ok(()), + Some(gimli::CieOrFde::Cie(cie)) => { + writeln!(w, "{:#010x}: CIE", cie.offset())?; + writeln!(w, " length: {:#010x}", cie.entry_len())?; + // TODO: CIE_id + writeln!(w, " version: {:#04x}", cie.version())?; + // TODO: augmentation + writeln!(w, " code_align: {}", cie.code_alignment_factor())?; + writeln!(w, " data_align: {}", cie.data_alignment_factor())?; + writeln!(w, " ra_register: {:#x}", cie.return_address_register().0)?; + if let Some(encoding) = cie.lsda_encoding() { + writeln!(w, " lsda_encoding: {:#02x}", encoding.0)?; + } + if let Some((encoding, personality)) = cie.personality_with_encoding() { + write!(w, " personality: {:#02x} ", encoding.0)?; + dump_pointer(w, personality)?; + writeln!(w)?; + } + if let Some(encoding) = cie.fde_address_encoding() { + writeln!(w, " fde_encoding: {:#02x}", encoding.0)?; + } + dump_cfi_instructions( + w, + cie.instructions(eh_frame, bases), + true, + register_name, + )?; + writeln!(w)?; + } + Some(gimli::CieOrFde::Fde(partial)) => { + let mut offset = None; + let fde = partial.parse(|_, bases, o| { + offset = Some(o); + cies.entry(o) + .or_insert_with(|| eh_frame.cie_from_offset(bases, o)) + .clone() + })?; + + writeln!(w)?; + writeln!(w, "{:#010x}: FDE", fde.offset())?; + writeln!(w, " length: {:#010x}", fde.entry_len())?; + writeln!(w, " CIE_pointer: {:#010x}", offset.unwrap().0)?; + // TODO: symbolicate the start address like the canonical dwarfdump does. + writeln!(w, " start_addr: {:#018x}", fde.initial_address())?; + writeln!( + w, + " range_size: {:#018x} (end_addr = {:#018x})", + fde.len(), + fde.initial_address() + fde.len() + )?; + if let Some(lsda) = fde.lsda() { + write!(w, " lsda: ")?; + dump_pointer(w, lsda)?; + writeln!(w)?; + } + dump_cfi_instructions( + w, + fde.instructions(eh_frame, bases), + false, + register_name, + )?; + writeln!(w)?; + } + } + } + } + + fn dump_pointer(w: &mut W, p: gimli::Pointer) -> Result<()> { + match p { + gimli::Pointer::Direct(p) => { + write!(w, "{:#018x}", p)?; + } + gimli::Pointer::Indirect(p) => { + write!(w, "({:#018x})", p)?; + } + } + Ok(()) + } + + #[allow(clippy::unneeded_field_pattern)] + fn dump_cfi_instructions( + w: &mut W, + mut insns: gimli::CallFrameInstructionIter, + is_initial: bool, + register_name: &dyn Fn(gimli::Register) -> Cow<'static, str>, + ) -> Result<()> { + use gimli::CallFrameInstruction::*; + + // TODO: we need to actually evaluate these instructions as we iterate them + // so we can print the initialized state for CIEs, and each unwind row's + // registers for FDEs. + // + // TODO: We should print DWARF expressions for the CFI instructions that + // embed DWARF expressions within themselves. + + if !is_initial { + writeln!(w, " Instructions:")?; + } + + loop { + match insns.next() { + Err(e) => { + writeln!(w, "Failed to decode CFI instruction: {}", e)?; + return Ok(()); + } + Ok(None) => { + if is_initial { + writeln!(w, " Instructions: Init State:")?; + } + return Ok(()); + } + Ok(Some(op)) => match op { + SetLoc { address } => { + writeln!(w, " DW_CFA_set_loc ({:#x})", address)?; + } + AdvanceLoc { delta } => { + writeln!(w, " DW_CFA_advance_loc ({})", delta)?; + } + DefCfa { register, offset } => { + writeln!( + w, + " DW_CFA_def_cfa ({}, {})", + register_name(register), + offset + )?; + } + DefCfaSf { + register, + factored_offset, + } => { + writeln!( + w, + " DW_CFA_def_cfa_sf ({}, {})", + register_name(register), + factored_offset + )?; + } + DefCfaRegister { register } => { + writeln!( + w, + " DW_CFA_def_cfa_register ({})", + register_name(register) + )?; + } + DefCfaOffset { offset } => { + writeln!(w, " DW_CFA_def_cfa_offset ({})", offset)?; + } + DefCfaOffsetSf { factored_offset } => { + writeln!( + w, + " DW_CFA_def_cfa_offset_sf ({})", + factored_offset + )?; + } + DefCfaExpression { expression: _ } => { + writeln!(w, " DW_CFA_def_cfa_expression (...)")?; + } + Undefined { register } => { + writeln!( + w, + " DW_CFA_undefined ({})", + register_name(register) + )?; + } + SameValue { register } => { + writeln!( + w, + " DW_CFA_same_value ({})", + register_name(register) + )?; + } + Offset { + register, + factored_offset, + } => { + writeln!( + w, + " DW_CFA_offset ({}, {})", + register_name(register), + factored_offset + )?; + } + OffsetExtendedSf { + register, + factored_offset, + } => { + writeln!( + w, + " DW_CFA_offset_extended_sf ({}, {})", + register_name(register), + factored_offset + )?; + } + ValOffset { + register, + factored_offset, + } => { + writeln!( + w, + " DW_CFA_val_offset ({}, {})", + register_name(register), + factored_offset + )?; + } + ValOffsetSf { + register, + factored_offset, + } => { + writeln!( + w, + " DW_CFA_val_offset_sf ({}, {})", + register_name(register), + factored_offset + )?; + } + Register { + dest_register, + src_register, + } => { + writeln!( + w, + " DW_CFA_register ({}, {})", + register_name(dest_register), + register_name(src_register) + )?; + } + Expression { + register, + expression: _, + } => { + writeln!( + w, + " DW_CFA_expression ({}, ...)", + register_name(register) + )?; + } + ValExpression { + register, + expression: _, + } => { + writeln!( + w, + " DW_CFA_val_expression ({}, ...)", + register_name(register) + )?; + } + Restore { register } => { + writeln!( + w, + " DW_CFA_restore ({})", + register_name(register) + )?; + } + RememberState => { + writeln!(w, " DW_CFA_remember_state")?; + } + RestoreState => { + writeln!(w, " DW_CFA_restore_state")?; + } + ArgsSize { size } => { + writeln!(w, " DW_CFA_GNU_args_size ({})", size)?; + } + Nop => { + writeln!(w, " DW_CFA_nop")?; + } + }, + } + } + } +} diff --git a/cranelift-filetests/src/test_unwind.rs b/cranelift-filetests/src/test_unwind.rs index 7b67ee731..3db1cbf82 100644 --- a/cranelift-filetests/src/test_unwind.rs +++ b/cranelift-filetests/src/test_unwind.rs @@ -6,6 +6,7 @@ use crate::subtest::{run_filecheck, Context, SubTest, SubtestResult}; use byteorder::{ByteOrder, LittleEndian}; use cranelift_codegen; +use cranelift_codegen::binemit::{FrameUnwindKind, FrameUnwindOffset, FrameUnwindSink, Reloc}; use cranelift_codegen::ir; use cranelift_reader::TestCommand; use std::borrow::Cow; @@ -41,14 +42,30 @@ impl SubTest for TestUnwind { comp_ctx.compile(isa).expect("failed to compile function"); - let mut mem = Vec::new(); - comp_ctx.emit_unwind_info(isa, &mut mem); + struct Sink(Vec); + impl FrameUnwindSink for Sink { + fn len(&self) -> FrameUnwindOffset { + self.0.len() + } + fn bytes(&mut self, b: &[u8]) { + self.0.extend_from_slice(b); + } + fn reloc(&mut self, _: Reloc, _: FrameUnwindOffset) { + unimplemented!(); + } + fn set_entry_offset(&mut self, _: FrameUnwindOffset) { + unimplemented!(); + } + } + + let mut sink = Sink(Vec::new()); + comp_ctx.emit_unwind_info(isa, FrameUnwindKind::Fastcall, &mut sink); let mut text = String::new(); - if mem.is_empty() { + if sink.0.is_empty() { writeln!(text, "No unwind information.").unwrap(); } else { - print_unwind_info(&mut text, &mem); + print_unwind_info(&mut text, &sink.0); } run_filecheck(&text, context) diff --git a/filetests/isa/x86/windows_systemv_x64_fde.clif b/filetests/isa/x86/windows_systemv_x64_fde.clif new file mode 100644 index 000000000..1444359de --- /dev/null +++ b/filetests/isa/x86/windows_systemv_x64_fde.clif @@ -0,0 +1,54 @@ +test fde +set opt_level=speed_and_size +set is_pic +target x86_64 haswell + +; check that there is no libunwind information for a windows_fastcall function +function %not_fastcall() windows_fastcall { +ebb0: + return +} +; sameln: No unwind information. + +; check the libunwind information with a function with no args +function %no_args() system_v { +ebb0: + return +} +; sameln: 0x00000000: CIE +; nextln: length: 0x00000014 +; nextln: version: 0x01 +; nextln: code_align: 1 +; nextln: data_align: -8 +; nextln: ra_register: 0x10 +; nextln: DW_CFA_def_cfa (r7, 8) +; nextln: DW_CFA_offset (r16, 1) +; nextln: DW_CFA_nop +; nextln: DW_CFA_nop +; nextln: DW_CFA_nop +; nextln: DW_CFA_nop +; nextln: DW_CFA_nop +; nextln: DW_CFA_nop +; nextln: Instructions: Init State: +; nextln: +; nextln: +; nextln: 0x00000018: FDE +; nextln: length: 0x00000024 +; nextln: CIE_pointer: 0x00000000 +; nextln: start_addr: 0x0000000000000000 +; nextln: range_size: 0x0000000000000006 (end_addr = 0x0000000000000006) +; nextln: Instructions: +; nextln: DW_CFA_advance_loc (1) +; nextln: DW_CFA_def_cfa_offset (16) +; nextln: DW_CFA_offset (r6, 2) +; nextln: DW_CFA_advance_loc (3) +; nextln: DW_CFA_def_cfa_register (r6) +; nextln: DW_CFA_advance_loc (1) +; nextln: DW_CFA_def_cfa (r7, 8) +; nextln: DW_CFA_nop +; nextln: DW_CFA_nop +; nextln: DW_CFA_nop +; nextln: DW_CFA_nop +; nextln: +; nextln: Entry: 24 +; nextln: Relocs: [(Abs8, 32)]