From 33784f2e756f026d92d8dd2799966e07941630cd Mon Sep 17 00:00:00 2001 From: iximeow Date: Tue, 6 Aug 2019 18:56:29 -0700 Subject: [PATCH 1/6] initial commit adding .eh_frame creation for x86_64 in cranelift-faerie --- cranelift-faerie/Cargo.toml | 1 + cranelift-faerie/src/backend.rs | 316 +++++++++++++++++++++++++++++++- 2 files changed, 316 insertions(+), 1 deletion(-) diff --git a/cranelift-faerie/Cargo.toml b/cranelift-faerie/Cargo.toml index c919b6354..9d444d5fb 100644 --- a/cranelift-faerie/Cargo.toml +++ b/cranelift-faerie/Cargo.toml @@ -14,6 +14,7 @@ cranelift-module = { path = "../cranelift-module", version = "0.54.0" } faerie = "0.14.0" goblin = "0.1.0" anyhow = "1.0" +byteorder = "1.2" target-lexicon = "0.10" [dependencies.cranelift-codegen] diff --git a/cranelift-faerie/src/backend.rs b/cranelift-faerie/src/backend.rs index aca9196bc..4c675603a 100644 --- a/cranelift-faerie/src/backend.rs +++ b/cranelift-faerie/src/backend.rs @@ -14,6 +14,7 @@ use cranelift_module::{ }; use faerie; use std::fs::File; +use std::io::Cursor; use target_lexicon::Triple; #[derive(Debug)] @@ -76,9 +77,265 @@ pub struct FaerieBackend { isa: Box, artifact: faerie::Artifact, trap_manifest: Option, + eh_frame_data: Option, libcall_names: Box String>, } +mod eh_frame { + use std::io::Cursor; + use byteorder::LittleEndian; + use byteorder::WriteBytesExt; + + pub struct ExceptionFrameInfo { + pub cie: CommonInformationEntry, + pub fdes: Vec, + } + + impl ExceptionFrameInfo { + pub fn new() -> Self { + ExceptionFrameInfo { + cie: CommonInformationEntry::v1( + // code alignment + 0x01, + // data alignment + -0x08, + 0x10, // x86-specific!! + ).with_augmentation( + // this sets that there _are_ augmentations, + // 0x1b may need to be enum'd - + // means DW_EH_PE_pcrel (relative to addr), encoded as + // DW_EH_PE_sdata4 (4byte signed value) + CIEAugmentation::PointerEncoding(0x1b) + ).with_initial_instructions( + // now the initial CFE instructions to set up rows in the table + vec![0x0c, 0x07, 0x08, 0x90, 0x01] + ), + fdes: vec![] + } + } + } + + pub struct CommonInformationEntry { + version: u8, + code_alignment: u32, + data_alignment: i32, + return_register: u8, + augmentations: Option>, + initial_instructions: Vec, + } + + impl CommonInformationEntry { + pub fn v1(code_alignment: u32, data_alignment: i32, return_register: u8) -> Self { + CommonInformationEntry { + version: 1, + code_alignment, + data_alignment, + return_register, + augmentations: None, + initial_instructions: vec![] + } + } + pub fn with_augmentation(mut self, aug: CIEAugmentation) -> Self { + if let Some(ref mut augmentations) = &mut self.augmentations { + augmentations.push(aug); + } else { + self.augmentations = Some(vec![aug]); + } + self + } + pub fn with_initial_instructions(mut self, instructions: Vec) -> Self { + self.initial_instructions = instructions; + self + } + + /* + let cie = [ + 0x14, 0x00, 0x00, 0x00, // size (after this field): 0x14 + 0x00, 0x00, 0x00, 0x00, // identifies this as a CIE + 0x01, // CIE version + 0x7a, 0x52, 0x00, // augmentation string (zR) + // 'z' means that there _is_ aumentation data + // including that there is an unsigned LEB128 for data length + // 'R' means that data is an FDE encoding with a DW_EH_PE_xxx value in data + 0x01, // code alignment factor + 0x78, // data alignment factor (-8) + 0x10, // return register + 0x01, // augmentation data length + 0x1b, // FDE pointer encoding + // and 0x00 to pad out to 0x10 alignment + ]; + */ + pub fn encode_to(&self, data: &mut Cursor>) { + let mut size = + 4 + // CIE identifier + 1 + // version + 1 + // augmentation string terminator + 3 + // code alignment, data alignment, and return register + self.initial_instructions.len(); // and the initial CFI instructions + + let mut augmentation_data_len = 0; + + // count additional data for augmentations... + if let Some(augmentations) = self.augmentations.as_ref() { + // augmentation string is `z.*\x00`, but we already counted the + // terminator unconditionally + size += 1 + augmentations.len(); + + augmentation_data_len = augmentations.iter().map(|x| x.data_len()).sum(); + + size += 1; // augmentation data length field + + size += augmentation_data_len; + } + + if size & 0x03 != 0 { + // round up for padding + size += 4 - (size & 0x03); + } + + // size + data.write_u32::(size as u32).unwrap(); + // CIE identifier + data.write_u32::(0).unwrap(); + data.write_u8(self.version).unwrap(); + if let Some(augmentations) = self.augmentations.as_ref() { + // write 'z' to begin an augmentation string + data.write_u8(0x7a).unwrap(); + for augmentation in augmentations { + data.write_u8(augmentation.data_type()); + } + } + // augmentation strings are null-terminated (empty string is just null) + data.write_u8(0x00).unwrap(); + data.write_u8(self.code_alignment as u8 & 0x7f).unwrap(); + data.write_u8(self.data_alignment as u8 & 0x7f).unwrap(); + data.write_u8(self.return_register).unwrap(); + if let Some(augmentations) = self.augmentations.as_ref() { + data.write_u8(augmentation_data_len as u8).unwrap(); + for augmentation in augmentations { + augmentation.write_to(data); + } + } + + for inst in self.initial_instructions.iter() { + data.write_u8(*inst); + } + + // and pad out to an even 4-byte offset + while data.position() & 0x3 != 0 { + data.write_u8(0x00); + } + } + } + + // Possible CIE augmentation data. The only supported kind currently is pointer encoding + #[derive(Debug)] + pub enum CIEAugmentation { + PointerEncoding(u8), + } + + impl CIEAugmentation { + pub fn write_to(&self, data: &mut Cursor>) { + match self { + CIEAugmentation::PointerEncoding(encoding) => { + data.write_u8(*encoding); + } + } + } + + pub fn data_type(&self) -> u8 { + match self { + CIEAugmentation::PointerEncoding(_) => { + 0x52 // 'R' + } + } + } + + pub fn data_len(&self) -> usize{ + match self { + CIEAugmentation::PointerEncoding(_) => 1 + } + } + } + + pub struct FrameDescriptorEntry { + pub function: String, + augmentations: Option>, + cfe_instructions: Vec, + function_size: usize, + } + + impl FrameDescriptorEntry { + pub fn new(function: String, cfe_instructions: Vec, function_size: usize) -> Self { + FrameDescriptorEntry { + function, + augmentations: Some(vec![]), + cfe_instructions, + function_size, + } + } + /* + let fde = [ + 0x34, 0x00, 0x00, 0x00, // size (after this field) + 0xXX, 0xXX, 0xXX, 0xXX, // CIE pointer (negative, from this field) + 0xYY, 0xYY, 0xYY, 0xYY, // initial location (relative reloc to start of function) + 0xZZ, 0xZZ, 0xZZ, 0xZZ, // range length (size of function) + 0x00, // augmentation data length - no supported FDE augmentations so this will be 0. + II, II, II, ... // CFE instructions (up to size) + ] + */ + pub fn encode_to(&self, data: &mut Cursor>, cie: &CommonInformationEntry) { + let mut size = + 4 + // CIE pointer + 4 + // start of FDE range + 4; // length of FDE range + + // from augmentation data being present + // present if CIE augmentation string begins iwth 'a' + if cie.augmentations.is_some() { + size += 1; + } + + size += self.cfe_instructions.len(); + + if size & 0x03 != 0 { + // round out to 4-byte aligned address + size += 4 - (size & 0x03); + } + + data.write_u32::(size as u32); + // we can be kind of clever about the CIE pointer: + // the CIE pointer is to get to the CIE from this FDE, but + // is written as its negation (meaning, this field holds the + // offset from CIE to DIE.cie_offset, rather than the reverse). + // Since the CIE begins at byte 0, the correct value to write + // here is `DIE.cie_offset - CIE_offset`, or `DIE.cie_offset - 0` + // which is the current cursor position. + // + // the alternative is a relocation in faerie from a section to itself, which I think + // might cause issues? + data.write_u32::(data.position() as u32); + // write 0 here where a relocation will point to the function later + data.write_u32::(0x00000000); + data.write_u32::(self.function_size as u32); + + // no frame descriptor entry augmentations are currently supported, so is always 0 + data.write_u8(0x00); + for inst in self.cfe_instructions.iter() { + data.write_u8(*inst); + } + while data.position() & 3 != 0 { + data.write_u8(0x00); + } + } + } + + /// The only FDE augmentation is to provide a pointer to + /// a language-specific data area (LSDA), which we don't + /// need to do and thus do not support (yet?). + enum FDEAugmentation { } +} + pub struct FaerieCompiledFunction { code_length: u32, } @@ -115,6 +372,7 @@ impl Backend for FaerieBackend { FaerieTrapCollection::Enabled => Some(FaerieTrapManifest::new()), FaerieTrapCollection::Disabled => None, }, + eh_frame_data: Some(eh_frame::ExceptionFrameInfo::new()), libcall_names: builder.libcall_names, } } @@ -190,6 +448,23 @@ impl Backend for FaerieBackend { } } + if let Some(ref mut eh_frame_data) = self.eh_frame_data { + use byteorder::LittleEndian; + use byteorder::WriteBytesExt; + + let mut cfi_instructions = vec![0x41, 0x0e, 0x10, 0x86, 0x02, 0x43, 0x0d, 0x06]; + let advance: u32 = code.len() as u32 - (4 + 1); + cfi_instructions.push(0x04); // DW_CFA_advance_loc4 + cfi_instructions.write_u32::(advance); + + cfi_instructions.extend_from_slice(&[0x0c, 0x07, 0x08]); + eh_frame_data.fdes.push(eh_frame::FrameDescriptorEntry::new( + name.to_string(), + cfi_instructions, + code.len(), + )); + } + // because `define` will take ownership of code, this is our last chance let code_length = code.len() as u32; @@ -308,7 +583,46 @@ impl Backend for FaerieBackend { } fn publish(&mut self) { - // Nothing to do. + if let Some(ref mut eh_frame_data) = self.eh_frame_data { + self.artifact + .declare(".eh_frame", faerie::Decl::section(faerie::SectionKind::Data)).unwrap(); + + let mut eh_frame_bytes = Cursor::new(Vec::new()); + + eh_frame_data.cie.encode_to(&mut eh_frame_bytes); + + for fde in eh_frame_data.fdes.iter() { + // Faerie requires all function references go through a PLT entry by default, + // but we need a direct offset to the function, so explicitly construct an absolute + // relocation and pass that. Because it's a reloc to an internal function, `ld` + // should turn this into a const offset and discard the relocation. + let absolute_reloc = match self.artifact.target.binary_format { + target_lexicon::BinaryFormat::Elf => faerie::artifact::Reloc::Raw { + reloc: goblin::elf::reloc::R_X86_64_PC32, + addend: 0, + }, + target_lexicon::BinaryFormat::Macho => faerie::artifact::Reloc::Raw { + // TODO: how do we get a 32bit relocaion here, instead of 64? + reloc: goblin::mach::relocation::X86_64_RELOC_UNSIGNED as u32, + addend: 0, + }, + _ => panic!("unsupported target format"), + }; + self.artifact.link_with( + faerie::Link { + to: &fde.function, + from: ".eh_frame", + at: eh_frame_bytes.position() + 8, + }, + absolute_reloc + ); + + fde.encode_to(&mut eh_frame_bytes, &eh_frame_data.cie); + } + + self.artifact + .define(".eh_frame", eh_frame_bytes.into_inner()).unwrap(); + } } fn finish(self) -> FaerieProduct { From 88d6c2c8c233cd2809a8420ab7048ac61bae6816 Mon Sep 17 00:00:00 2001 From: iximeow Date: Fri, 9 Aug 2019 17:39:47 -0700 Subject: [PATCH 2/6] build .eh_frame info via gimli --- cranelift-faerie/Cargo.toml | 1 + cranelift-faerie/src/backend.rs | 647 ++++++++++++++++++-------------- 2 files changed, 362 insertions(+), 286 deletions(-) diff --git a/cranelift-faerie/Cargo.toml b/cranelift-faerie/Cargo.toml index 9d444d5fb..da403b4b6 100644 --- a/cranelift-faerie/Cargo.toml +++ b/cranelift-faerie/Cargo.toml @@ -15,6 +15,7 @@ faerie = "0.14.0" goblin = "0.1.0" anyhow = "1.0" byteorder = "1.2" +gimli = "0.19.0" target-lexicon = "0.10" [dependencies.cranelift-codegen] diff --git a/cranelift-faerie/src/backend.rs b/cranelift-faerie/src/backend.rs index 4c675603a..f59e8256f 100644 --- a/cranelift-faerie/src/backend.rs +++ b/cranelift-faerie/src/backend.rs @@ -6,17 +6,21 @@ use anyhow::Error; use cranelift_codegen::binemit::{ Addend, CodeOffset, NullStackmapSink, NullTrapSink, Reloc, RelocSink, Stackmap, StackmapSink, }; -use cranelift_codegen::isa::TargetIsa; -use cranelift_codegen::{self, binemit, ir}; +use cranelift_codegen::isa::{RegInfo, RegUnit, TargetIsa}; +use cranelift_codegen::{self, binemit, ir, isa}; use cranelift_module::{ Backend, DataContext, DataDescription, DataId, FuncId, Init, Linkage, ModuleError, ModuleNamespace, ModuleResult, }; use faerie; use std::fs::File; -use std::io::Cursor; use target_lexicon::Triple; +use gimli::write::Address; +use gimli::write::CallFrameInstruction; +use gimli::write::CommonInformationEntry; +use gimli::write::FrameDescriptionEntry; + #[derive(Debug)] /// Setting to enable collection of traps. Setting this to `Enabled` in /// `FaerieBuilder` means that a `FaerieTrapManifest` will be present @@ -77,272 +81,346 @@ pub struct FaerieBackend { isa: Box, artifact: faerie::Artifact, trap_manifest: Option, - eh_frame_data: Option, + frame_sink: Option, libcall_names: Box String>, } -mod eh_frame { - use std::io::Cursor; - use byteorder::LittleEndian; - use byteorder::WriteBytesExt; - - pub struct ExceptionFrameInfo { - pub cie: CommonInformationEntry, - pub fdes: Vec, - } - - impl ExceptionFrameInfo { - pub fn new() -> Self { - ExceptionFrameInfo { - cie: CommonInformationEntry::v1( - // code alignment - 0x01, - // data alignment - -0x08, - 0x10, // x86-specific!! - ).with_augmentation( - // this sets that there _are_ augmentations, - // 0x1b may need to be enum'd - - // means DW_EH_PE_pcrel (relative to addr), encoded as - // DW_EH_PE_sdata4 (4byte signed value) - CIEAugmentation::PointerEncoding(0x1b) - ).with_initial_instructions( - // now the initial CFE instructions to set up rows in the table - vec![0x0c, 0x07, 0x08, 0x90, 0x01] - ), - fdes: vec![] - } +struct FrameSink { + // we need to retain function names to hand out usize identifiers for FDE addresses, + // which are then used to look up function names again for relocations, when `write_address` is + // called. + fn_names: Vec, + table: gimli::write::FrameTable, + default_cie: gimli::write::CieId, +} + +impl FrameSink { + pub fn new() -> FrameSink { + let mut table = gimli::write::FrameTable::default(); + let mut cie = CommonInformationEntry::new( + gimli::Encoding { + format: gimli::Format::Dwarf32, + version: 1, + address_size: 4, + }, + // code alignment factor + 0x01, + // data alignment factor + -0x08, + // ISA-specific, return address register + gimli::Register(0x10), + ); + + cie.fde_address_encoding = gimli::DwEhPe(0x1b); + + cie.add_instruction(CallFrameInstruction::Cfa(gimli::Register(7), 8)); + cie.add_instruction(CallFrameInstruction::Offset(gimli::Register(0x10), -8)); + let cie_id = table.add_cie(cie); + + FrameSink { + fn_names: vec![], + table, + default_cie: cie_id, } } - pub struct CommonInformationEntry { - version: u8, - code_alignment: u32, - data_alignment: i32, - return_register: u8, - augmentations: Option>, - initial_instructions: Vec, + pub fn address_for(&mut self, name: &str) -> Address { + // adding a FrameDescriptionEntry for a function twice would be a bug, + // so we can confidently expect that `name` will not be provided more than once. + // So `name` is always new, meaning we can just add it and return its index + self.fn_names.push(name.to_string()); + Address::Symbol { + symbol: self.fn_names.len() - 1, + addend: 0, + } } - impl CommonInformationEntry { - pub fn v1(code_alignment: u32, data_alignment: i32, return_register: u8) -> Self { - CommonInformationEntry { - version: 1, - code_alignment, - data_alignment, - return_register, - augmentations: None, - initial_instructions: vec![] - } - } - pub fn with_augmentation(mut self, aug: CIEAugmentation) -> Self { - if let Some(ref mut augmentations) = &mut self.augmentations { - augmentations.push(aug); - } else { - self.augmentations = Some(vec![aug]); - } - self - } - pub fn with_initial_instructions(mut self, instructions: Vec) -> Self { - self.initial_instructions = instructions; - self - } + /// Add a FrameDescriptionEntry to the FrameTable we're constructing + /// + /// This will always use the default CIE (which was build with this `FrameSink`). + pub fn add_fde(&mut self, fd_entry: FrameDescriptionEntry) { + self.table.add_fde(self.default_cie, fd_entry); + } +} - /* - let cie = [ - 0x14, 0x00, 0x00, 0x00, // size (after this field): 0x14 - 0x00, 0x00, 0x00, 0x00, // identifies this as a CIE - 0x01, // CIE version - 0x7a, 0x52, 0x00, // augmentation string (zR) - // 'z' means that there _is_ aumentation data - // including that there is an unsigned LEB128 for data length - // 'R' means that data is an FDE encoding with a DW_EH_PE_xxx value in data - 0x01, // code alignment factor - 0x78, // data alignment factor (-8) - 0x10, // return register - 0x01, // augmentation data length - 0x1b, // FDE pointer encoding - // and 0x00 to pad out to 0x10 alignment - ]; - */ - pub fn encode_to(&self, data: &mut Cursor>) { - let mut size = - 4 + // CIE identifier - 1 + // version - 1 + // augmentation string terminator - 3 + // code alignment, data alignment, and return register - self.initial_instructions.len(); // and the initial CFI instructions - - let mut augmentation_data_len = 0; - - // count additional data for augmentations... - if let Some(augmentations) = self.augmentations.as_ref() { - // augmentation string is `z.*\x00`, but we already counted the - // terminator unconditionally - size += 1 + augmentations.len(); - - augmentation_data_len = augmentations.iter().map(|x| x.data_len()).sum(); - - size += 1; // augmentation data length field - - size += augmentation_data_len; - } +struct FaerieDebugSink<'a> { + pub data: &'a mut Vec, + pub functions: &'a [String], + pub artifact: &'a mut faerie::Artifact, +} - if size & 0x03 != 0 { - // round up for padding - size += 4 - (size & 0x03); - } +impl<'a> gimli::write::Writer for FaerieDebugSink<'a> { + type Endian = gimli::LittleEndian; - // size - data.write_u32::(size as u32).unwrap(); - // CIE identifier - data.write_u32::(0).unwrap(); - data.write_u8(self.version).unwrap(); - if let Some(augmentations) = self.augmentations.as_ref() { - // write 'z' to begin an augmentation string - data.write_u8(0x7a).unwrap(); - for augmentation in augmentations { - data.write_u8(augmentation.data_type()); - } - } - // augmentation strings are null-terminated (empty string is just null) - data.write_u8(0x00).unwrap(); - data.write_u8(self.code_alignment as u8 & 0x7f).unwrap(); - data.write_u8(self.data_alignment as u8 & 0x7f).unwrap(); - data.write_u8(self.return_register).unwrap(); - if let Some(augmentations) = self.augmentations.as_ref() { - data.write_u8(augmentation_data_len as u8).unwrap(); - for augmentation in augmentations { - augmentation.write_to(data); - } - } + fn endian(&self) -> Self::Endian { + gimli::LittleEndian + } + fn len(&self) -> usize { + self.data.len() + } + fn write(&mut self, bytes: &[u8]) -> gimli::write::Result<()> { + self.data.extend_from_slice(bytes); + Ok(()) + } - for inst in self.initial_instructions.iter() { - data.write_u8(*inst); - } + fn write_at(&mut self, offset: usize, bytes: &[u8]) -> gimli::write::Result<()> { + if offset + bytes.len() > self.data.len() { + return Err(gimli::write::Error::LengthOutOfBounds); + } + self.data[offset..][..bytes.len()].copy_from_slice(bytes); + Ok(()) + } - // and pad out to an even 4-byte offset - while data.position() & 0x3 != 0 { - data.write_u8(0x00); + fn write_eh_pointer( + &mut self, + address: Address, + eh_pe: gimli::DwEhPe, + size: u8, + ) -> gimli::write::Result<()> { + // we only support PC-relative 4byte signed offsets for eh_frame pointers currently. Other + // encodings may be permissible, but aren't seen even by gcc/clang/etc, and have not been + // tested. Currently, relocations used for addresses expect to be relocating four bytes, + // PC-relative, and larger pointer sizes would require selection of other relocation types. + assert!(eh_pe.0 == 0x1b); + + // if size is not 4, then the size indicated by `eh_pe` doesn't match with the pointer + // we're trying to encode. That's a logical bug, possibly in gimli? + assert!(size == 4); + + self.write_address(address, size) + } + + fn write_address(&mut self, address: Address, size: u8) -> gimli::write::Result<()> { + match address { + Address::Constant(val) => self.write_udata(val, size), + Address::Symbol { symbol, addend } => { + assert!(addend == 0); + + let name = self.functions[symbol].as_str(); + + let reloc = faerie::artifact::Reloc::Raw { + reloc: goblin::elf::reloc::R_X86_64_PC32, + addend: 0, + }; + + self.artifact + .link_with( + faerie::Link { + to: name, + from: ".eh_frame", + at: self.data.len() as u64, + }, + reloc, + ) + .map_err(|_link_err| gimli::write::Error::InvalidAddress)?; + + self.write_udata(0, size) } } } +} - // Possible CIE augmentation data. The only supported kind currently is pointer encoding - #[derive(Debug)] - pub enum CIEAugmentation { - PointerEncoding(u8), +pub struct FaerieCompiledFunction { + code_length: u32, +} + +impl FaerieCompiledFunction { + pub fn code_length(&self) -> u32 { + self.code_length } +} - impl CIEAugmentation { - pub fn write_to(&self, data: &mut Cursor>) { - match self { - CIEAugmentation::PointerEncoding(encoding) => { - data.write_u8(*encoding); - } - } +struct CFIEncoder { + cfa_def_reg: Option, + cfa_def_offset: Option, +} + +struct DwarfRegMapper<'a> { + isa: &'a Box, + reg_info: RegInfo, +} + +impl<'a> DwarfRegMapper<'a> { + pub fn for_isa(isa: &'a Box) -> Self { + DwarfRegMapper { + isa, + reg_info: isa.register_info(), } + } - pub fn data_type(&self) -> u8 { - match self { - CIEAugmentation::PointerEncoding(_) => { - 0x52 // 'R' + /// Translate a Cranelift `RegUnit` to its matching `Register` for DWARF use. + /// + /// panics if `reg` cannot be translated - the requested debug information would be + /// unencodable. + pub fn translate_reg(&self, reg: RegUnit) -> gimli::Register { + match self.isa.name() { + "x86" => { + const X86_GP_REG_MAP: [u16; 16] = [ + // cranelift rax == 0 -> dwarf rax == 0 + 0, // cranelift rcx == 1 -> dwarf rcx == 2 + 2, // cranelift rdx == 2 -> dwarf rdx == 1 + 1, // cranelift rbx == 3 -> dwarf rbx == 3 + 3, // cranelift rsp == 4 -> dwarf rsp == 7 + 7, // cranelift rbp == 5 -> dwarf rbp == 6 + 6, // cranelift rsi == 6 -> dwarf rsi == 4 + 4, // cranelift rdi == 7 -> dwarf rdi == 5 + 5, // all of r8 to r15 do map directly over + 8, 9, 10, 11, 12, 13, 14, 15, + ]; + let bank = self.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. + gimli::Register(X86_GP_REG_MAP[(reg - bank.first_unit) as usize]) + } + "FloatRegs" => { + // xmm registers are all contiguous, but a bit offset + let xmm_num = reg - bank.first_unit; + // Cranelift only knows about sse4 + assert!(xmm_num < 16); + gimli::Register(17 + xmm_num) + } + _ => { + panic!("unsupported register bank: {}", bank.name); + } } } - } - - pub fn data_len(&self) -> usize{ - match self { - CIEAugmentation::PointerEncoding(_) => 1 + /* + * Other architectures, like "arm32", "arm64", and "riscv", do not have mappings to + * DWARF register numbers yet. + */ + name => { + panic!("don't know how to encode registers for isa {}", name); } } } - pub struct FrameDescriptorEntry { - pub function: String, - augmentations: Option>, - cfe_instructions: Vec, - function_size: usize, - } - - impl FrameDescriptorEntry { - pub fn new(function: String, cfe_instructions: Vec, function_size: usize) -> Self { - FrameDescriptorEntry { - function, - augmentations: Some(vec![]), - cfe_instructions, - function_size, + /// Get the DWARF location describing the call frame's return address. + /// + /// panics if that location is unknown - the requested debug information would be unencodable. + pub fn return_address(&self) -> gimli::Register { + match self.isa.name() { + "x86" => gimli::Register(0x10), + "arm32" => { + // unlike AArch64, there is no explicit DWARF number for a return address + // so trying to encode a return address in arm32 is a logical error. + panic!("arm32 DWARF has no distinct return address register - this is a FrameChange bug, or you may want to have specified lr (r14)"); } - } - /* - let fde = [ - 0x34, 0x00, 0x00, 0x00, // size (after this field) - 0xXX, 0xXX, 0xXX, 0xXX, // CIE pointer (negative, from this field) - 0xYY, 0xYY, 0xYY, 0xYY, // initial location (relative reloc to start of function) - 0xZZ, 0xZZ, 0xZZ, 0xZZ, // range length (size of function) - 0x00, // augmentation data length - no supported FDE augmentations so this will be 0. - II, II, II, ... // CFE instructions (up to size) - ] - */ - pub fn encode_to(&self, data: &mut Cursor>, cie: &CommonInformationEntry) { - let mut size = - 4 + // CIE pointer - 4 + // start of FDE range - 4; // length of FDE range - - // from augmentation data being present - // present if CIE augmentation string begins iwth 'a' - if cie.augmentations.is_some() { - size += 1; + "arm64" => { + // from "DWARF for the ARM 64-bit architecture (AArch64)" + // + // this is actually the "current mode exception link register". ARM uses LR for + // return address purposes and CFI directives to preserve the parent call frame + // should have been performed by preserving LR. + gimli::Register(33) } - - size += self.cfe_instructions.len(); - - if size & 0x03 != 0 { - // round out to 4-byte aligned address - size += 4 - (size & 0x03); + "riscv" => { + // Taking a guess from reading + // https://github.com/riscv/riscv-elf-psabi-doc/blob/master/riscv-elf.md#dwarf-register-numbers + // + // which says dwarf number 64 is the "Alternate Frame Return Column", talking about + // use for unwinding from signal handlers, recording the address the signal handler + // will return to. + gimli::Register(64) } - - data.write_u32::(size as u32); - // we can be kind of clever about the CIE pointer: - // the CIE pointer is to get to the CIE from this FDE, but - // is written as its negation (meaning, this field holds the - // offset from CIE to DIE.cie_offset, rather than the reverse). - // Since the CIE begins at byte 0, the correct value to write - // here is `DIE.cie_offset - CIE_offset`, or `DIE.cie_offset - 0` - // which is the current cursor position. - // - // the alternative is a relocation in faerie from a section to itself, which I think - // might cause issues? - data.write_u32::(data.position() as u32); - // write 0 here where a relocation will point to the function later - data.write_u32::(0x00000000); - data.write_u32::(self.function_size as u32); - - // no frame descriptor entry augmentations are currently supported, so is always 0 - data.write_u8(0x00); - for inst in self.cfe_instructions.iter() { - data.write_u8(*inst); - } - while data.position() & 3 != 0 { - data.write_u8(0x00); + name => { + panic!("don't know how to encode registers for isa {}", name); } } } - - /// The only FDE augmentation is to provide a pointer to - /// a language-specific data area (LSDA), which we don't - /// need to do and thus do not support (yet?). - enum FDEAugmentation { } } -pub struct FaerieCompiledFunction { - code_length: u32, -} +impl CFIEncoder { + pub fn new() -> Self { + CFIEncoder { + // this is rsp, which is really ISA- and CIE-defined. another CIE could have rax as the + // CFA-referencing register, as unlikely as it would be. + cfa_def_reg: Some(4), + // this should be -8, but same as above is ISA and CIE-defined. `None` as a default + // means the first location to set it will set it to the correct offset. + cfa_def_offset: None, + } + } -impl FaerieCompiledFunction { - pub fn code_length(&self) -> u32 { - self.code_length + pub fn encode( + &mut self, + fd_entry: &mut FrameDescriptionEntry, + reg_map: &DwarfRegMapper, + changes: impl Iterator, + ) { + for (addr, change) in changes { + match change { + ir::FrameLayoutChange::CallFrameAddressAt { reg, offset } => { + // if your call frame is more than 2gb, or -2gb.. sorry? + assert_eq!( + offset, offset as i32 as isize, + "call frame offset beyond i32 range" + ); + match ( + Some(reg) == self.cfa_def_reg, + Some(offset) == self.cfa_def_offset, + ) { + (true, true) => { + /* + * this "change" would change nothing, so we don't have to + * do anything. + */ + } + (false, true) => { + // reg pointing to the call frame has changed + fd_entry.add_instruction( + addr, + CallFrameInstruction::CfaRegister(reg_map.translate_reg(reg)), + ); + } + (true, false) => { + // the offset has changed, so emit CfaOffset + fd_entry.add_instruction( + addr, + CallFrameInstruction::CfaOffset(offset as i32), + ); + } + (false, false) => { + // the register and cfa offset have changed, so update both + fd_entry.add_instruction( + addr, + CallFrameInstruction::Cfa( + reg_map.translate_reg(reg), + offset as i32, + ), + ); + } + } + self.cfa_def_offset = Some(offset); + self.cfa_def_reg = Some(reg); + } + ir::FrameLayoutChange::RegAt { reg, cfa_offset } => { + fd_entry.add_instruction( + addr, + CallFrameInstruction::Offset(reg_map.translate_reg(reg), cfa_offset as i32), + ); + } + ir::FrameLayoutChange::ReturnAddressAt { cfa_offset } => { + fd_entry.add_instruction( + addr, + CallFrameInstruction::Offset(reg_map.return_address(), cfa_offset as i32), + ); + } + ir::FrameLayoutChange::Preserve => { + fd_entry.add_instruction( + addr, + CallFrameInstruction::RememberState, + ); + }, + ir::FrameLayoutChange::Restore => { + fd_entry.add_instruction( + addr, + CallFrameInstruction::RestoreState, + ); + }, + } + } } } @@ -372,7 +450,7 @@ impl Backend for FaerieBackend { FaerieTrapCollection::Enabled => Some(FaerieTrapManifest::new()), FaerieTrapCollection::Disabled => None, }, - eh_frame_data: Some(eh_frame::ExceptionFrameInfo::new()), + frame_sink: Some(FrameSink::new()), libcall_names: builder.libcall_names, } } @@ -448,25 +526,40 @@ impl Backend for FaerieBackend { } } - if let Some(ref mut eh_frame_data) = self.eh_frame_data { - use byteorder::LittleEndian; - use byteorder::WriteBytesExt; + // because `define` will take ownership of code, this is our last chance + let code_length = code.len() as u32; - let mut cfi_instructions = vec![0x41, 0x0e, 0x10, 0x86, 0x02, 0x43, 0x0d, 0x06]; - let advance: u32 = code.len() as u32 - (4 + 1); - cfi_instructions.push(0x04); // DW_CFA_advance_loc4 - cfi_instructions.write_u32::(advance); + if let Some(ref mut frame_sink) = self.frame_sink { + if let Some(layout) = ctx.func.frame_layout.as_ref() { + let mut fd_entry = + FrameDescriptionEntry::new(frame_sink.address_for(name), code_length); + + let mut frame_changes = vec![]; + for ebb in ctx.func.layout.ebbs() { + for (offset, inst, size) in ctx.func.inst_offsets(ebb, &self.isa.encoding_info()) { + if let Some(changes) = layout.instructions.get(&inst) { + for change in changes.iter() { + frame_changes.push((offset + size, change.clone())); + } + } + } + } - cfi_instructions.extend_from_slice(&[0x0c, 0x07, 0x08]); - eh_frame_data.fdes.push(eh_frame::FrameDescriptorEntry::new( - name.to_string(), - cfi_instructions, - code.len(), - )); - } + frame_changes.sort_by(|a, b| a.0.cmp(&b.0)); - // because `define` will take ownership of code, this is our last chance - let code_length = code.len() as u32; + CFIEncoder::new().encode( + &mut fd_entry, + &DwarfRegMapper::for_isa(&self.isa), + frame_changes.into_iter(), + ); + + frame_sink.add_fde(fd_entry); + } else { + // we have a frame sink to write .eh_frames into, but are not collecting debug + // information for at least the current function. This might be a bug in the code + // using cranelift-faerie? + } + } self.artifact .define(name, code) @@ -583,45 +676,27 @@ impl Backend for FaerieBackend { } fn publish(&mut self) { - if let Some(ref mut eh_frame_data) = self.eh_frame_data { + if let Some(ref mut frame_sink) = self.frame_sink { self.artifact - .declare(".eh_frame", faerie::Decl::section(faerie::SectionKind::Data)).unwrap(); - - let mut eh_frame_bytes = Cursor::new(Vec::new()); - - eh_frame_data.cie.encode_to(&mut eh_frame_bytes); - - for fde in eh_frame_data.fdes.iter() { - // Faerie requires all function references go through a PLT entry by default, - // but we need a direct offset to the function, so explicitly construct an absolute - // relocation and pass that. Because it's a reloc to an internal function, `ld` - // should turn this into a const offset and discard the relocation. - let absolute_reloc = match self.artifact.target.binary_format { - target_lexicon::BinaryFormat::Elf => faerie::artifact::Reloc::Raw { - reloc: goblin::elf::reloc::R_X86_64_PC32, - addend: 0, - }, - target_lexicon::BinaryFormat::Macho => faerie::artifact::Reloc::Raw { - // TODO: how do we get a 32bit relocaion here, instead of 64? - reloc: goblin::mach::relocation::X86_64_RELOC_UNSIGNED as u32, - addend: 0, - }, - _ => panic!("unsupported target format"), - }; - self.artifact.link_with( - faerie::Link { - to: &fde.function, - from: ".eh_frame", - at: eh_frame_bytes.position() + 8, - }, - absolute_reloc - ); + .declare( + ".eh_frame", + faerie::Decl::section(faerie::SectionKind::Data), + ) + .unwrap(); - fde.encode_to(&mut eh_frame_bytes, &eh_frame_data.cie); - } + let mut eh_frame_bytes = Vec::new(); - self.artifact - .define(".eh_frame", eh_frame_bytes.into_inner()).unwrap(); + let mut eh_frame_writer = gimli::write::EhFrame(FaerieDebugSink { + data: &mut eh_frame_bytes, + functions: frame_sink.fn_names.as_slice(), + artifact: &mut self.artifact, + }); + frame_sink + .table + .write_eh_frame(&mut eh_frame_writer) + .unwrap(); + + self.artifact.define(".eh_frame", eh_frame_bytes).unwrap(); } } From dc3dc2e2881cec6f18ebc76cbc64a071a9c30b9b Mon Sep 17 00:00:00 2001 From: iximeow Date: Thu, 15 Aug 2019 14:04:08 -0700 Subject: [PATCH 3/6] decouple eh_frame creation from x86 magic numbers a bit some x86-ism magic numbers are still present, but they at least should be suboptimal (rather than invalid) for non-x86 architectures. --- cranelift-faerie/src/backend.rs | 195 +++++++++++++++++--------------- 1 file changed, 104 insertions(+), 91 deletions(-) diff --git a/cranelift-faerie/src/backend.rs b/cranelift-faerie/src/backend.rs index f59e8256f..20923cb63 100644 --- a/cranelift-faerie/src/backend.rs +++ b/cranelift-faerie/src/backend.rs @@ -91,36 +91,55 @@ struct FrameSink { // called. fn_names: Vec, table: gimli::write::FrameTable, - default_cie: gimli::write::CieId, } impl FrameSink { - pub fn new() -> FrameSink { - let mut table = gimli::write::FrameTable::default(); + /// Find a CIE appropriate for the register mapper and initial state provided. This will + /// construct a CIE and rely on `gimli` to return an id for an appropriate existing CIE, if + /// one exists. + /// + /// This function also returns a `CFIEncoder` already initialized to the state matching the + /// initial CFI instructions for this CIE, ready for use to encode an FDE. + pub fn cie_for<'a>( + &mut self, + initial_state: &[ir::FrameLayoutChange], + reg_mapper: &'a DwarfRegMapper, + ) -> (gimli::write::CieId, CFIEncoder<'a>) { let mut cie = CommonInformationEntry::new( gimli::Encoding { format: gimli::Format::Dwarf32, version: 1, address_size: 4, }, - // code alignment factor + // code alignment factor. Is this right for non-x86_64 ISAs? Probably could be 2 or 4 + // elsewhere. 0x01, - // data alignment factor + // data alignment factor. Same question for non-x86_64 ISAs. -0x08, - // ISA-specific, return address register - gimli::Register(0x10), + // ISA-specific, column for the return address (may be a register, may not) + reg_mapper.return_address(), ); cie.fde_address_encoding = gimli::DwEhPe(0x1b); - cie.add_instruction(CallFrameInstruction::Cfa(gimli::Register(7), 8)); - cie.add_instruction(CallFrameInstruction::Offset(gimli::Register(0x10), -8)); - let cie_id = table.add_cie(cie); + let mut encoder = CFIEncoder::new(®_mapper); + + for inst in initial_state + .iter() + .flat_map(|change| encoder.translate(change)) + { + cie.add_instruction(inst); + } + + let cie_id = self.table.add_cie(cie); + + (cie_id, encoder) + } + pub fn new() -> FrameSink { FrameSink { fn_names: vec![], - table, - default_cie: cie_id, + table: gimli::write::FrameTable::default(), } } @@ -138,8 +157,8 @@ impl FrameSink { /// Add a FrameDescriptionEntry to the FrameTable we're constructing /// /// This will always use the default CIE (which was build with this `FrameSink`). - pub fn add_fde(&mut self, fd_entry: FrameDescriptionEntry) { - self.table.add_fde(self.default_cie, fd_entry); + pub fn add_fde(&mut self, cie_id: gimli::write::CieId, fd_entry: FrameDescriptionEntry) { + self.table.add_fde(cie_id, fd_entry); } } @@ -230,7 +249,8 @@ impl FaerieCompiledFunction { } } -struct CFIEncoder { +struct CFIEncoder<'a> { + reg_map: &'a DwarfRegMapper<'a>, cfa_def_reg: Option, cfa_def_offset: Option, } @@ -331,81 +351,59 @@ impl<'a> DwarfRegMapper<'a> { } } -impl CFIEncoder { - pub fn new() -> Self { +impl<'a> CFIEncoder<'a> { + pub fn new(reg_map: &'a DwarfRegMapper) -> Self { CFIEncoder { - // this is rsp, which is really ISA- and CIE-defined. another CIE could have rax as the - // CFA-referencing register, as unlikely as it would be. - cfa_def_reg: Some(4), - // this should be -8, but same as above is ISA and CIE-defined. `None` as a default - // means the first location to set it will set it to the correct offset. + reg_map, + // Both of the below are typically defined by per-CIE initial instructions, such that + // neither are `None` when encoding instructions for an FDE. It is, however, likely not + // an error for these to be `None` when encoding an FDE *AS LONG AS* they are + // initialized before the CFI for an FDE advance into the function. + cfa_def_reg: None, cfa_def_offset: None, } } - pub fn encode( - &mut self, - fd_entry: &mut FrameDescriptionEntry, - reg_map: &DwarfRegMapper, - changes: impl Iterator, - ) { - for (addr, change) in changes { - match change { - ir::FrameLayoutChange::CallFrameAddressAt { reg, offset } => { - // if your call frame is more than 2gb, or -2gb.. sorry? - assert_eq!( - offset, offset as i32 as isize, - "call frame offset beyond i32 range" - ); - match ( - Some(reg) == self.cfa_def_reg, - Some(offset) == self.cfa_def_offset, - ) { - (true, true) => { - /* - * this "change" would change nothing, so we don't have to - * do anything. - */ - } - (false, true) => { - // reg pointing to the call frame has changed - fd_entry.add_instruction( - addr, - CallFrameInstruction::CfaRegister(reg_map.translate_reg(reg)), - ); - } - (true, false) => { - // the offset has changed, so emit CfaOffset - fd_entry.add_instruction( - addr, - CallFrameInstruction::CfaOffset(offset as i32), - ); - } - (false, false) => { - // the register and cfa offset have changed, so update both - fd_entry.add_instruction( - addr, - CallFrameInstruction::Cfa( - reg_map.translate_reg(reg), - offset as i32, - ), - ); - } + pub fn translate(&mut self, change: &ir::FrameLayoutChange) -> Option { + match change { + ir::FrameLayoutChange::CallFrameAddressAt { reg, offset } => { + // if your call frame is more than 2gb, or -2gb.. sorry? I don't think .eh_frame + // can express that? Maybe chaining `cfa_advance_loc4`, or something.. + assert_eq!( + *offset, *offset as i32 as isize, + "call frame offset beyond i32 range" + ); + let (reg_updated, offset_updated) = ( + Some(*reg) != self.cfa_def_reg, + Some(*offset) != self.cfa_def_offset, + ); + self.cfa_def_offset = Some(*offset); + self.cfa_def_reg = Some(*reg); + match (reg_updated, offset_updated) { + (false, false) => { + /* + * this "change" would change nothing, so we don't have to + * do anything. + */ + None + } + (true, false) => { + // reg pointing to the call frame has changed + Some(CallFrameInstruction::CfaRegister( + self.reg_map.translate_reg(*reg), + )) + } + (false, true) => { + // the offset has changed, so emit CfaOffset + Some(CallFrameInstruction::CfaOffset(*offset as i32)) + } + (true, true) => { + // the register and cfa offset have changed, so update both + Some(CallFrameInstruction::Cfa( + self.reg_map.translate_reg(*reg), + *offset as i32, + )) } - self.cfa_def_offset = Some(offset); - self.cfa_def_reg = Some(reg); - } - ir::FrameLayoutChange::RegAt { reg, cfa_offset } => { - fd_entry.add_instruction( - addr, - CallFrameInstruction::Offset(reg_map.translate_reg(reg), cfa_offset as i32), - ); - } - ir::FrameLayoutChange::ReturnAddressAt { cfa_offset } => { - fd_entry.add_instruction( - addr, - CallFrameInstruction::Offset(reg_map.return_address(), cfa_offset as i32), - ); } ir::FrameLayoutChange::Preserve => { fd_entry.add_instruction( @@ -420,6 +418,13 @@ impl CFIEncoder { ); }, } + ir::FrameLayoutChange::RegAt { reg, cfa_offset } => Some(CallFrameInstruction::Offset( + self.reg_map.translate_reg(*reg), + *cfa_offset as i32, + )), + ir::FrameLayoutChange::ReturnAddressAt { cfa_offset } => Some( + CallFrameInstruction::Offset(self.reg_map.return_address(), *cfa_offset as i32), + ), } } } @@ -531,12 +536,18 @@ impl Backend for FaerieBackend { if let Some(ref mut frame_sink) = self.frame_sink { if let Some(layout) = ctx.func.frame_layout.as_ref() { + let reg_mapper = DwarfRegMapper::for_isa(&self.isa); + + let (cie, mut encoder) = frame_sink.cie_for(&layout.initial, ®_mapper); + let mut fd_entry = FrameDescriptionEntry::new(frame_sink.address_for(name), code_length); let mut frame_changes = vec![]; for ebb in ctx.func.layout.ebbs() { - for (offset, inst, size) in ctx.func.inst_offsets(ebb, &self.isa.encoding_info()) { + for (offset, inst, size) in + ctx.func.inst_offsets(ebb, &self.isa.encoding_info()) + { if let Some(changes) = layout.instructions.get(&inst) { for change in changes.iter() { frame_changes.push((offset + size, change.clone())); @@ -547,13 +558,15 @@ impl Backend for FaerieBackend { frame_changes.sort_by(|a, b| a.0.cmp(&b.0)); - CFIEncoder::new().encode( - &mut fd_entry, - &DwarfRegMapper::for_isa(&self.isa), - frame_changes.into_iter(), - ); + let fde_insts = frame_changes + .into_iter() + .flat_map(|(addr, change)| encoder.translate(&change).map(|inst| (addr, inst))); + + for (addr, inst) in fde_insts.into_iter() { + fd_entry.add_instruction(addr, inst); + } - frame_sink.add_fde(fd_entry); + frame_sink.add_fde(cie, fd_entry); } else { // we have a frame sink to write .eh_frames into, but are not collecting debug // information for at least the current function. This might be a bug in the code From c865e8760c78bfbba3b6db4aa90366a078362268 Mon Sep 17 00:00:00 2001 From: iximeow Date: Fri, 22 Nov 2019 15:27:01 -0800 Subject: [PATCH 4/6] first part of review comments --- cranelift-faerie/src/backend.rs | 104 +++++++++++++++++--------------- 1 file changed, 55 insertions(+), 49 deletions(-) diff --git a/cranelift-faerie/src/backend.rs b/cranelift-faerie/src/backend.rs index 20923cb63..40bac72b6 100644 --- a/cranelift-faerie/src/backend.rs +++ b/cranelift-faerie/src/backend.rs @@ -16,6 +16,7 @@ use faerie; use std::fs::File; use target_lexicon::Triple; +use gimli::constants::{DW_EH_PE_pcrel, DW_EH_PE_sdata4}; use gimli::write::Address; use gimli::write::CallFrameInstruction; use gimli::write::CommonInformationEntry; @@ -93,6 +94,8 @@ struct FrameSink { table: gimli::write::FrameTable, } +const PC_SDATA4: u8 = DW_EH_PE_pcrel.0 | DW_EH_PE_sdata4.0; + impl FrameSink { /// Find a CIE appropriate for the register mapper and initial state provided. This will /// construct a CIE and rely on `gimli` to return an id for an appropriate existing CIE, if @@ -111,16 +114,18 @@ impl FrameSink { version: 1, address_size: 4, }, - // code alignment factor. Is this right for non-x86_64 ISAs? Probably could be 2 or 4 + // Code alignment factor. Is this right for non-x86_64 ISAs? Probably could be 2 or 4 // elsewhere. 0x01, - // data alignment factor. Same question for non-x86_64 ISAs. + // Data alignment factor. Same question for non-x86_64 ISAs. -8 is a mostly-arbitrary + // choice, selected here for equivalence with other DWARF-generating toolchains, such + // as gcc and llvm. -0x08, // ISA-specific, column for the return address (may be a register, may not) reg_mapper.return_address(), ); - cie.fde_address_encoding = gimli::DwEhPe(0x1b); + cie.fde_address_encoding = gimli::DwEhPe(PC_SDATA4); let mut encoder = CFIEncoder::new(®_mapper); @@ -200,7 +205,7 @@ impl<'a> gimli::write::Writer for FaerieDebugSink<'a> { // encodings may be permissible, but aren't seen even by gcc/clang/etc, and have not been // tested. Currently, relocations used for addresses expect to be relocating four bytes, // PC-relative, and larger pointer sizes would require selection of other relocation types. - assert!(eh_pe.0 == 0x1b); + assert!(eh_pe.0 == PC_SDATA4); // if size is not 4, then the size indicated by `eh_pe` doesn't match with the pointer // we're trying to encode. That's a logical bug, possibly in gimli? @@ -276,16 +281,40 @@ impl<'a> DwarfRegMapper<'a> { match self.isa.name() { "x86" => { const X86_GP_REG_MAP: [u16; 16] = [ - // cranelift rax == 0 -> dwarf rax == 0 - 0, // cranelift rcx == 1 -> dwarf rcx == 2 - 2, // cranelift rdx == 2 -> dwarf rdx == 1 - 1, // cranelift rbx == 3 -> dwarf rbx == 3 - 3, // cranelift rsp == 4 -> dwarf rsp == 7 - 7, // cranelift rbp == 5 -> dwarf rbp == 6 - 6, // cranelift rsi == 6 -> dwarf rsi == 4 - 4, // cranelift rdi == 7 -> dwarf rdi == 5 - 5, // all of r8 to r15 do map directly over - 8, 9, 10, 11, 12, 13, 14, 15, + gimli::X86_64::RAX, + gimli::X86_64::RCX, + gimli::X86_64::RDX, + gimli::X86_64::RBX, + gimli::X86_64::RSP, + gimli::X86_64::RBP, + gimli::X86_64::RSI, + gimli::X86_64::RDI, + gimli::X86_64::R8, + gimli::X86_64::R9, + gimli::X86_64::R10, + gimli::X86_64::R11, + gimli::X86_64::R12, + gimli::X86_64::R13, + gimli::X86_64::R14, + gimli::X86_64::R15, + ]; + const X86_XMM_REG_MAP: [u16; 16] = [ + gimli::X86_64::XMM0, + gimli::X86_64::XMM1, + gimli::X86_64::XMM2, + gimli::X86_64::XMM3, + gimli::X86_64::XMM4, + gimli::X86_64::XMM5, + gimli::X86_64::XMM6, + gimli::X86_64::XMM7, + gimli::X86_64::XMM8, + gimli::X86_64::XMM9, + gimli::X86_64::XMM10, + gimli::X86_64::XMM11, + gimli::X86_64::XMM12, + gimli::X86_64::XMM13, + gimli::X86_64::XMM14, + gimli::X86_64::XMM15, ]; let bank = self.reg_info.bank_containing_regunit(reg).unwrap(); match bank.name { @@ -295,11 +324,7 @@ impl<'a> DwarfRegMapper<'a> { gimli::Register(X86_GP_REG_MAP[(reg - bank.first_unit) as usize]) } "FloatRegs" => { - // xmm registers are all contiguous, but a bit offset - let xmm_num = reg - bank.first_unit; - // Cranelift only knows about sse4 - assert!(xmm_num < 16); - gimli::Register(17 + xmm_num) + gimli::Register(X86_XMM_REG_MAP[(reg - bank.first_unit) as usize]) } _ => { panic!("unsupported register bank: {}", bank.name); @@ -321,28 +346,15 @@ impl<'a> DwarfRegMapper<'a> { /// panics if that location is unknown - the requested debug information would be unencodable. pub fn return_address(&self) -> gimli::Register { match self.isa.name() { - "x86" => gimli::Register(0x10), + "x86" => gimli::Register(gimli::X86_64::RA), "arm32" => { - // unlike AArch64, there is no explicit DWARF number for a return address - // so trying to encode a return address in arm32 is a logical error. - panic!("arm32 DWARF has no distinct return address register - this is a FrameChange bug, or you may want to have specified lr (r14)"); + panic!("don't know the DWARF register for arm32 return address"); } "arm64" => { - // from "DWARF for the ARM 64-bit architecture (AArch64)" - // - // this is actually the "current mode exception link register". ARM uses LR for - // return address purposes and CFI directives to preserve the parent call frame - // should have been performed by preserving LR. - gimli::Register(33) + panic!("don't know the DWARF register for arm64 return address"); } "riscv" => { - // Taking a guess from reading - // https://github.com/riscv/riscv-elf-psabi-doc/blob/master/riscv-elf.md#dwarf-register-numbers - // - // which says dwarf number 64 is the "Alternate Frame Return Column", talking about - // use for unwinding from signal handlers, recording the address the signal handler - // will return to. - gimli::Register(64) + panic!("don't know the DWARF register for riscv return address"); } name => { panic!("don't know how to encode registers for isa {}", name); @@ -405,19 +417,13 @@ impl<'a> CFIEncoder<'a> { )) } } - ir::FrameLayoutChange::Preserve => { - fd_entry.add_instruction( - addr, - CallFrameInstruction::RememberState, - ); - }, - ir::FrameLayoutChange::Restore => { - fd_entry.add_instruction( - addr, - CallFrameInstruction::RestoreState, - ); - }, - } + }, + ir::FrameLayoutChange::Preserve => { + Some(CallFrameInstruction::RememberState) + }, + ir::FrameLayoutChange::Restore => { + Some(CallFrameInstruction::RestoreState) + }, ir::FrameLayoutChange::RegAt { reg, cfa_offset } => Some(CallFrameInstruction::Offset( self.reg_map.translate_reg(*reg), *cfa_offset as i32, From 5e586cd0e0646069f8f3de9ff3831d2f187bb5bc Mon Sep 17 00:00:00 2001 From: iximeow Date: Fri, 22 Nov 2019 22:16:50 -0800 Subject: [PATCH 5/6] move non-faerie-specific parts of .eh_frame generation to cranelift-module --- cranelift-faerie/src/backend.rs | 297 +------------------------------- cranelift-module/Cargo.toml | 1 + cranelift-module/src/dwarf.rs | 295 +++++++++++++++++++++++++++++++ cranelift-module/src/lib.rs | 2 + 4 files changed, 307 insertions(+), 288 deletions(-) create mode 100644 cranelift-module/src/dwarf.rs diff --git a/cranelift-faerie/src/backend.rs b/cranelift-faerie/src/backend.rs index 40bac72b6..b5e9e8768 100644 --- a/cranelift-faerie/src/backend.rs +++ b/cranelift-faerie/src/backend.rs @@ -6,20 +6,17 @@ use anyhow::Error; use cranelift_codegen::binemit::{ Addend, CodeOffset, NullStackmapSink, NullTrapSink, Reloc, RelocSink, Stackmap, StackmapSink, }; -use cranelift_codegen::isa::{RegInfo, RegUnit, TargetIsa}; -use cranelift_codegen::{self, binemit, ir, isa}; +use cranelift_codegen::isa::TargetIsa; +use cranelift_codegen::{self, binemit, ir}; use cranelift_module::{ - Backend, DataContext, DataDescription, DataId, FuncId, Init, Linkage, ModuleError, + Backend, DataContext, DataDescription, DataId, FrameSink, FuncId, Init, Linkage, ModuleError, ModuleNamespace, ModuleResult, }; use faerie; use std::fs::File; use target_lexicon::Triple; -use gimli::constants::{DW_EH_PE_pcrel, DW_EH_PE_sdata4}; use gimli::write::Address; -use gimli::write::CallFrameInstruction; -use gimli::write::CommonInformationEntry; use gimli::write::FrameDescriptionEntry; #[derive(Debug)] @@ -86,87 +83,6 @@ pub struct FaerieBackend { libcall_names: Box String>, } -struct FrameSink { - // we need to retain function names to hand out usize identifiers for FDE addresses, - // which are then used to look up function names again for relocations, when `write_address` is - // called. - fn_names: Vec, - table: gimli::write::FrameTable, -} - -const PC_SDATA4: u8 = DW_EH_PE_pcrel.0 | DW_EH_PE_sdata4.0; - -impl FrameSink { - /// Find a CIE appropriate for the register mapper and initial state provided. This will - /// construct a CIE and rely on `gimli` to return an id for an appropriate existing CIE, if - /// one exists. - /// - /// This function also returns a `CFIEncoder` already initialized to the state matching the - /// initial CFI instructions for this CIE, ready for use to encode an FDE. - pub fn cie_for<'a>( - &mut self, - initial_state: &[ir::FrameLayoutChange], - reg_mapper: &'a DwarfRegMapper, - ) -> (gimli::write::CieId, CFIEncoder<'a>) { - let mut cie = CommonInformationEntry::new( - gimli::Encoding { - format: gimli::Format::Dwarf32, - version: 1, - address_size: 4, - }, - // Code alignment factor. Is this right for non-x86_64 ISAs? Probably could be 2 or 4 - // elsewhere. - 0x01, - // Data alignment factor. Same question for non-x86_64 ISAs. -8 is a mostly-arbitrary - // choice, selected here for equivalence with other DWARF-generating toolchains, such - // as gcc and llvm. - -0x08, - // ISA-specific, column for the return address (may be a register, may not) - reg_mapper.return_address(), - ); - - cie.fde_address_encoding = gimli::DwEhPe(PC_SDATA4); - - let mut encoder = CFIEncoder::new(®_mapper); - - for inst in initial_state - .iter() - .flat_map(|change| encoder.translate(change)) - { - cie.add_instruction(inst); - } - - let cie_id = self.table.add_cie(cie); - - (cie_id, encoder) - } - - pub fn new() -> FrameSink { - FrameSink { - fn_names: vec![], - table: gimli::write::FrameTable::default(), - } - } - - pub fn address_for(&mut self, name: &str) -> Address { - // adding a FrameDescriptionEntry for a function twice would be a bug, - // so we can confidently expect that `name` will not be provided more than once. - // So `name` is always new, meaning we can just add it and return its index - self.fn_names.push(name.to_string()); - Address::Symbol { - symbol: self.fn_names.len() - 1, - addend: 0, - } - } - - /// Add a FrameDescriptionEntry to the FrameTable we're constructing - /// - /// This will always use the default CIE (which was build with this `FrameSink`). - pub fn add_fde(&mut self, cie_id: gimli::write::CieId, fd_entry: FrameDescriptionEntry) { - self.table.add_fde(cie_id, fd_entry); - } -} - struct FaerieDebugSink<'a> { pub data: &'a mut Vec, pub functions: &'a [String], @@ -198,19 +114,9 @@ impl<'a> gimli::write::Writer for FaerieDebugSink<'a> { fn write_eh_pointer( &mut self, address: Address, - eh_pe: gimli::DwEhPe, + _eh_pe: gimli::DwEhPe, size: u8, ) -> gimli::write::Result<()> { - // we only support PC-relative 4byte signed offsets for eh_frame pointers currently. Other - // encodings may be permissible, but aren't seen even by gcc/clang/etc, and have not been - // tested. Currently, relocations used for addresses expect to be relocating four bytes, - // PC-relative, and larger pointer sizes would require selection of other relocation types. - assert!(eh_pe.0 == PC_SDATA4); - - // if size is not 4, then the size indicated by `eh_pe` doesn't match with the pointer - // we're trying to encode. That's a logical bug, possibly in gimli? - assert!(size == 4); - self.write_address(address, size) } @@ -254,187 +160,6 @@ impl FaerieCompiledFunction { } } -struct CFIEncoder<'a> { - reg_map: &'a DwarfRegMapper<'a>, - cfa_def_reg: Option, - cfa_def_offset: Option, -} - -struct DwarfRegMapper<'a> { - isa: &'a Box, - reg_info: RegInfo, -} - -impl<'a> DwarfRegMapper<'a> { - pub fn for_isa(isa: &'a Box) -> Self { - DwarfRegMapper { - isa, - reg_info: isa.register_info(), - } - } - - /// Translate a Cranelift `RegUnit` to its matching `Register` for DWARF use. - /// - /// panics if `reg` cannot be translated - the requested debug information would be - /// unencodable. - pub fn translate_reg(&self, reg: RegUnit) -> gimli::Register { - match self.isa.name() { - "x86" => { - const X86_GP_REG_MAP: [u16; 16] = [ - gimli::X86_64::RAX, - gimli::X86_64::RCX, - gimli::X86_64::RDX, - gimli::X86_64::RBX, - gimli::X86_64::RSP, - gimli::X86_64::RBP, - gimli::X86_64::RSI, - gimli::X86_64::RDI, - gimli::X86_64::R8, - gimli::X86_64::R9, - gimli::X86_64::R10, - gimli::X86_64::R11, - gimli::X86_64::R12, - gimli::X86_64::R13, - gimli::X86_64::R14, - gimli::X86_64::R15, - ]; - const X86_XMM_REG_MAP: [u16; 16] = [ - gimli::X86_64::XMM0, - gimli::X86_64::XMM1, - gimli::X86_64::XMM2, - gimli::X86_64::XMM3, - gimli::X86_64::XMM4, - gimli::X86_64::XMM5, - gimli::X86_64::XMM6, - gimli::X86_64::XMM7, - gimli::X86_64::XMM8, - gimli::X86_64::XMM9, - gimli::X86_64::XMM10, - gimli::X86_64::XMM11, - gimli::X86_64::XMM12, - gimli::X86_64::XMM13, - gimli::X86_64::XMM14, - gimli::X86_64::XMM15, - ]; - let bank = self.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. - gimli::Register(X86_GP_REG_MAP[(reg - bank.first_unit) as usize]) - } - "FloatRegs" => { - gimli::Register(X86_XMM_REG_MAP[(reg - bank.first_unit) as usize]) - } - _ => { - panic!("unsupported register bank: {}", bank.name); - } - } - } - /* - * Other architectures, like "arm32", "arm64", and "riscv", do not have mappings to - * DWARF register numbers yet. - */ - name => { - panic!("don't know how to encode registers for isa {}", name); - } - } - } - - /// Get the DWARF location describing the call frame's return address. - /// - /// panics if that location is unknown - the requested debug information would be unencodable. - pub fn return_address(&self) -> gimli::Register { - match self.isa.name() { - "x86" => gimli::Register(gimli::X86_64::RA), - "arm32" => { - panic!("don't know the DWARF register for arm32 return address"); - } - "arm64" => { - panic!("don't know the DWARF register for arm64 return address"); - } - "riscv" => { - panic!("don't know the DWARF register for riscv return address"); - } - name => { - panic!("don't know how to encode registers for isa {}", name); - } - } - } -} - -impl<'a> CFIEncoder<'a> { - pub fn new(reg_map: &'a DwarfRegMapper) -> Self { - CFIEncoder { - reg_map, - // Both of the below are typically defined by per-CIE initial instructions, such that - // neither are `None` when encoding instructions for an FDE. It is, however, likely not - // an error for these to be `None` when encoding an FDE *AS LONG AS* they are - // initialized before the CFI for an FDE advance into the function. - cfa_def_reg: None, - cfa_def_offset: None, - } - } - - pub fn translate(&mut self, change: &ir::FrameLayoutChange) -> Option { - match change { - ir::FrameLayoutChange::CallFrameAddressAt { reg, offset } => { - // if your call frame is more than 2gb, or -2gb.. sorry? I don't think .eh_frame - // can express that? Maybe chaining `cfa_advance_loc4`, or something.. - assert_eq!( - *offset, *offset as i32 as isize, - "call frame offset beyond i32 range" - ); - let (reg_updated, offset_updated) = ( - Some(*reg) != self.cfa_def_reg, - Some(*offset) != self.cfa_def_offset, - ); - self.cfa_def_offset = Some(*offset); - self.cfa_def_reg = Some(*reg); - match (reg_updated, offset_updated) { - (false, false) => { - /* - * this "change" would change nothing, so we don't have to - * do anything. - */ - None - } - (true, false) => { - // reg pointing to the call frame has changed - Some(CallFrameInstruction::CfaRegister( - self.reg_map.translate_reg(*reg), - )) - } - (false, true) => { - // the offset has changed, so emit CfaOffset - Some(CallFrameInstruction::CfaOffset(*offset as i32)) - } - (true, true) => { - // the register and cfa offset have changed, so update both - Some(CallFrameInstruction::Cfa( - self.reg_map.translate_reg(*reg), - *offset as i32, - )) - } - } - }, - ir::FrameLayoutChange::Preserve => { - Some(CallFrameInstruction::RememberState) - }, - ir::FrameLayoutChange::Restore => { - Some(CallFrameInstruction::RestoreState) - }, - ir::FrameLayoutChange::RegAt { reg, cfa_offset } => Some(CallFrameInstruction::Offset( - self.reg_map.translate_reg(*reg), - *cfa_offset as i32, - )), - ir::FrameLayoutChange::ReturnAddressAt { cfa_offset } => Some( - CallFrameInstruction::Offset(self.reg_map.return_address(), *cfa_offset as i32), - ), - } - } -} - pub struct FaerieCompiledData {} impl Backend for FaerieBackend { @@ -454,6 +179,7 @@ impl Backend for FaerieBackend { /// Create a new `FaerieBackend` using the given Cranelift target. fn new(builder: FaerieBuilder) -> Self { + let frame_sink = FrameSink::new(&builder.isa); Self { artifact: faerie::Artifact::new(builder.isa.triple().clone(), builder.name), isa: builder.isa, @@ -461,7 +187,7 @@ impl Backend for FaerieBackend { FaerieTrapCollection::Enabled => Some(FaerieTrapManifest::new()), FaerieTrapCollection::Disabled => None, }, - frame_sink: Some(FrameSink::new()), + frame_sink: Some(frame_sink), libcall_names: builder.libcall_names, } } @@ -542,9 +268,7 @@ impl Backend for FaerieBackend { if let Some(ref mut frame_sink) = self.frame_sink { if let Some(layout) = ctx.func.frame_layout.as_ref() { - let reg_mapper = DwarfRegMapper::for_isa(&self.isa); - - let (cie, mut encoder) = frame_sink.cie_for(&layout.initial, ®_mapper); + let (cie, mut encoder) = frame_sink.cie_for(&layout.initial); let mut fd_entry = FrameDescriptionEntry::new(frame_sink.address_for(name), code_length); @@ -707,13 +431,10 @@ impl Backend for FaerieBackend { let mut eh_frame_writer = gimli::write::EhFrame(FaerieDebugSink { data: &mut eh_frame_bytes, - functions: frame_sink.fn_names.as_slice(), + functions: frame_sink.fn_names_slice(), artifact: &mut self.artifact, }); - frame_sink - .table - .write_eh_frame(&mut eh_frame_writer) - .unwrap(); + frame_sink.write_to(&mut eh_frame_writer).unwrap(); self.artifact.define(".eh_frame", eh_frame_bytes).unwrap(); } diff --git a/cranelift-module/Cargo.toml b/cranelift-module/Cargo.toml index 7665c74c9..d6eacfb5e 100644 --- a/cranelift-module/Cargo.toml +++ b/cranelift-module/Cargo.toml @@ -13,6 +13,7 @@ edition = "2018" [dependencies] cranelift-codegen = { path = "../cranelift-codegen", version = "0.54.0", default-features = false } cranelift-entity = { path = "../cranelift-entity", version = "0.54.0" } +gimli = "0.19.0" hashbrown = { version = "0.6", optional = true } log = { version = "0.4.6", default-features = false } thiserror = "1.0.4" diff --git a/cranelift-module/src/dwarf.rs b/cranelift-module/src/dwarf.rs new file mode 100644 index 000000000..6f01f7286 --- /dev/null +++ b/cranelift-module/src/dwarf.rs @@ -0,0 +1,295 @@ +use cranelift_codegen::isa::{RegInfo, RegUnit, TargetIsa}; +use cranelift_codegen::{ir, isa}; + +use std::boxed::Box; +use std::string::String; +use std::string::ToString; +use std::vec::Vec; + +use gimli; +use gimli::constants::{DW_EH_PE_pcrel, DW_EH_PE_sdata4}; +use gimli::write::Address; +use gimli::write::CallFrameInstruction; +use gimli::write::CommonInformationEntry; +use gimli::write::EhFrame; +use gimli::write::Error; +use gimli::write::FrameDescriptionEntry; + +const PC_SDATA4: u8 = DW_EH_PE_pcrel.0 | DW_EH_PE_sdata4.0; + +/// FrameSink maintains state for and an interface to construct DWARF frame information for a +/// cranelift-produced module. +pub struct FrameSink { + // we need to retain function names to hand out usize identifiers for FDE addresses, + // which are then used to look up function names again for relocations, when `write_address` is + // called. + fn_names: Vec, + table: gimli::write::FrameTable, + reg_mapper: DwarfRegMapper, +} + +impl FrameSink { + /// Construct a new `FrameSink`. + pub fn new(isa: &Box) -> FrameSink { + FrameSink { + fn_names: vec![], + table: gimli::write::FrameTable::default(), + reg_mapper: DwarfRegMapper::for_isa(isa), + } + } + + /// Retrieve `gimli::write::Address` for some function name. Typically necessary in backends + /// that need to retrieve symbolic addresses. + pub fn address_for(&mut self, name: &str) -> Address { + // adding a FrameDescriptionEntry for a function twice would be a bug, + // so we can confidently expect that `name` will not be provided more than once. + // So `name` is always new, meaning we can just add it and return its index + self.fn_names.push(name.to_string()); + Address::Symbol { + symbol: self.fn_names.len() - 1, + addend: 0, + } + } + + /// Get the list of functions declared into this frame sink in order they were written. This + /// order must be preserved for `gimli::Address` symbols to refer to the right functions. + pub fn fn_names_slice(&self) -> &[String] { + &self.fn_names + } + + /// Write out data for this `FrameSink` through the provided `writer`. + pub fn write_to(&self, writer: &mut EhFrame) -> Result<(), Error> { + self.table.write_eh_frame(writer) + } + + /// Find a CIE appropriate for the register mapper and initial state provided. This will + /// construct a CIE and rely on `gimli` to return an id for an appropriate existing CIE, if + /// one exists. + /// + /// This function also returns a `CFIEncoder` already initialized to the state matching the + /// initial CFI instructions for this CIE, ready for use to encode an FDE. + pub fn cie_for( + &mut self, + initial_state: &[ir::FrameLayoutChange], + ) -> (gimli::write::CieId, CFIEncoder) { + let mut cie = CommonInformationEntry::new( + gimli::Encoding { + format: gimli::Format::Dwarf32, + version: 1, + address_size: 4, + }, + // Code alignment factor. Is this right for non-x86_64 ISAs? Probably could be 2 or 4 + // elsewhere. + 0x01, + // Data alignment factor. Same question for non-x86_64 ISAs. -8 is a mostly-arbitrary + // choice, selected here for equivalence with other DWARF-generating toolchains, such + // as gcc and llvm. + -0x08, + // ISA-specific, column for the return address (may be a register, may not) + self.reg_mapper.return_address(), + ); + + cie.fde_address_encoding = gimli::DwEhPe(PC_SDATA4); + + let mut encoder = CFIEncoder::new(&self.reg_mapper); + + for inst in initial_state + .iter() + .flat_map(|change| encoder.translate(change)) + { + cie.add_instruction(inst); + } + + let cie_id = self.table.add_cie(cie); + + (cie_id, encoder) + } + + /// Add a FrameDescriptionEntry to the FrameTable we're constructing + /// + /// This will always use the default CIE (which was build with this `FrameSink`). + pub fn add_fde(&mut self, cie_id: gimli::write::CieId, fd_entry: FrameDescriptionEntry) { + self.table.add_fde(cie_id, fd_entry); + } +} + +/// `CFIEncoder` is used to translate from `FrameLayoutChange` to DWARF Call Frame Instructions +pub struct CFIEncoder { + reg_map: DwarfRegMapper, + cfa_def_reg: Option, + cfa_def_offset: Option, +} + +/// `DwarfRegMapper` maps cranelift `RegUnit` to DWARF-appropriate register numbers. +#[derive(Clone)] +struct DwarfRegMapper { + isa_name: &'static str, + reg_info: RegInfo, +} + +impl DwarfRegMapper { + fn for_isa(isa: &Box) -> Self { + DwarfRegMapper { + isa_name: isa.name(), + reg_info: isa.register_info(), + } + } + + /// Translate a Cranelift `RegUnit` to its matching `Register` for DWARF use. + /// + /// panics if `reg` cannot be translated - the requested debug information would be + /// unencodable. + pub fn translate_reg(&self, reg: RegUnit) -> gimli::Register { + match self.isa_name { + "x86" => { + const X86_GP_REG_MAP: [gimli::Register; 16] = [ + gimli::X86_64::RAX, + gimli::X86_64::RCX, + gimli::X86_64::RDX, + gimli::X86_64::RBX, + gimli::X86_64::RSP, + gimli::X86_64::RBP, + gimli::X86_64::RSI, + gimli::X86_64::RDI, + gimli::X86_64::R8, + gimli::X86_64::R9, + gimli::X86_64::R10, + gimli::X86_64::R11, + gimli::X86_64::R12, + gimli::X86_64::R13, + gimli::X86_64::R14, + gimli::X86_64::R15, + ]; + const X86_XMM_REG_MAP: [gimli::Register; 16] = [ + gimli::X86_64::XMM0, + gimli::X86_64::XMM1, + gimli::X86_64::XMM2, + gimli::X86_64::XMM3, + gimli::X86_64::XMM4, + gimli::X86_64::XMM5, + gimli::X86_64::XMM6, + gimli::X86_64::XMM7, + gimli::X86_64::XMM8, + gimli::X86_64::XMM9, + gimli::X86_64::XMM10, + gimli::X86_64::XMM11, + gimli::X86_64::XMM12, + gimli::X86_64::XMM13, + gimli::X86_64::XMM14, + gimli::X86_64::XMM15, + ]; + let bank = self.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); + } + } + } + /* + * Other architectures, like "arm32", "arm64", and "riscv", do not have mappings to + * DWARF register numbers yet. + */ + name => { + panic!("don't know how to encode registers for isa {}", name); + } + } + } + + /// Get the DWARF location describing the call frame's return address. + /// + /// panics if that location is unknown - the requested debug information would be unencodable. + pub fn return_address(&self) -> gimli::Register { + match self.isa_name { + "x86" => gimli::X86_64::RA, + "arm32" => { + panic!("don't know the DWARF register for arm32 return address"); + } + "arm64" => { + panic!("don't know the DWARF register for arm64 return address"); + } + "riscv" => { + panic!("don't know the DWARF register for riscv return address"); + } + name => { + panic!("don't know how to encode registers for isa {}", name); + } + } + } +} + +impl CFIEncoder { + /// Construct a new `CFIEncoder`. `CFIEncoder` should not be reused across multiple functions. + fn new(reg_map: &DwarfRegMapper) -> Self { + CFIEncoder { + reg_map: reg_map.clone(), + // Both of the below are typically defined by per-CIE initial instructions, such that + // neither are `None` when encoding instructions for an FDE. It is, however, likely not + // an error for these to be `None` when encoding an FDE *AS LONG AS* they are + // initialized before the CFI for an FDE advance into the function. + cfa_def_reg: None, + cfa_def_offset: None, + } + } + + /// Given the current `CFIEncoder` and a `FrameLayoutChange`, update the encoder to match the + /// layout after this change and emit a corresponding `CallFrameInstruction` if one is needed. + pub fn translate(&mut self, change: &ir::FrameLayoutChange) -> Option { + match change { + ir::FrameLayoutChange::CallFrameAddressAt { reg, offset } => { + // if your call frame is more than 2gb, or -2gb.. sorry? I don't think .eh_frame + // can express that? Maybe chaining `cfa_advance_loc4`, or something.. + assert_eq!( + *offset, *offset as i32 as isize, + "call frame offset beyond i32 range" + ); + let (reg_updated, offset_updated) = ( + Some(*reg) != self.cfa_def_reg, + Some(*offset) != self.cfa_def_offset, + ); + self.cfa_def_offset = Some(*offset); + self.cfa_def_reg = Some(*reg); + match (reg_updated, offset_updated) { + (false, false) => { + /* + * this "change" would change nothing, so we don't have to + * do anything. + */ + None + } + (true, false) => { + // reg pointing to the call frame has changed + Some(CallFrameInstruction::CfaRegister( + self.reg_map.translate_reg(*reg), + )) + } + (false, true) => { + // the offset has changed, so emit CfaOffset + Some(CallFrameInstruction::CfaOffset(*offset as i32)) + } + (true, true) => { + // the register and cfa offset have changed, so update both + Some(CallFrameInstruction::Cfa( + self.reg_map.translate_reg(*reg), + *offset as i32, + )) + } + } + } + ir::FrameLayoutChange::Preserve => Some(CallFrameInstruction::RememberState), + ir::FrameLayoutChange::Restore => Some(CallFrameInstruction::RestoreState), + ir::FrameLayoutChange::RegAt { reg, cfa_offset } => Some(CallFrameInstruction::Offset( + self.reg_map.translate_reg(*reg), + *cfa_offset as i32, + )), + ir::FrameLayoutChange::ReturnAddressAt { cfa_offset } => Some( + CallFrameInstruction::Offset(self.reg_map.return_address(), *cfa_offset as i32), + ), + } + } +} diff --git a/cranelift-module/src/lib.rs b/cranelift-module/src/lib.rs index 0122171e9..59aee6eb5 100644 --- a/cranelift-module/src/lib.rs +++ b/cranelift-module/src/lib.rs @@ -34,10 +34,12 @@ use std::collections::{hash_map, HashMap}; mod backend; mod data_context; +mod dwarf; mod module; pub use crate::backend::{default_libcall_names, Backend}; pub use crate::data_context::{DataContext, DataDescription, Init}; +pub use crate::dwarf::FrameSink; pub use crate::module::{ DataId, FuncId, FuncOrDataId, Linkage, Module, ModuleError, ModuleFunction, ModuleNamespace, ModuleResult, From b1c81fd6e0396554033230520697d52020dd68be Mon Sep 17 00:00:00 2001 From: iximeow Date: Mon, 25 Nov 2019 16:25:49 -0800 Subject: [PATCH 6/6] remove wrong write_address impl --- cranelift-faerie/src/backend.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/cranelift-faerie/src/backend.rs b/cranelift-faerie/src/backend.rs index b5e9e8768..ec3197974 100644 --- a/cranelift-faerie/src/backend.rs +++ b/cranelift-faerie/src/backend.rs @@ -16,8 +16,8 @@ use faerie; use std::fs::File; use target_lexicon::Triple; -use gimli::write::Address; -use gimli::write::FrameDescriptionEntry; +use gimli::write::{Address, FrameDescriptionEntry}; +use gimli::{DW_EH_PE_pcrel, DW_EH_PE_sdata4}; #[derive(Debug)] /// Setting to enable collection of traps. Setting this to `Enabled` in @@ -114,17 +114,16 @@ impl<'a> gimli::write::Writer for FaerieDebugSink<'a> { fn write_eh_pointer( &mut self, address: Address, - _eh_pe: gimli::DwEhPe, + eh_pe: gimli::DwEhPe, size: u8, ) -> gimli::write::Result<()> { - self.write_address(address, size) - } - - fn write_address(&mut self, address: Address, size: u8) -> gimli::write::Result<()> { match address { Address::Constant(val) => self.write_udata(val, size), Address::Symbol { symbol, addend } => { - assert!(addend == 0); + assert_eq!(addend, 0); + + assert_eq!(eh_pe.format(), DW_EH_PE_sdata4, "faerie backend currently only supports PC-relative 4-byte offsets for DWARF pointers."); + assert_eq!(eh_pe.application(), DW_EH_PE_pcrel, "faerie backend currently only supports PC-relative 4-byte offsets for DWARF pointers."); let name = self.functions[symbol].as_str();