diff --git a/cranelift-faerie/Cargo.toml b/cranelift-faerie/Cargo.toml index c919b6354..da403b4b6 100644 --- a/cranelift-faerie/Cargo.toml +++ b/cranelift-faerie/Cargo.toml @@ -14,6 +14,8 @@ cranelift-module = { path = "../cranelift-module", version = "0.54.0" } 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 aca9196bc..ec3197974 100644 --- a/cranelift-faerie/src/backend.rs +++ b/cranelift-faerie/src/backend.rs @@ -9,13 +9,16 @@ use cranelift_codegen::binemit::{ 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::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 /// `FaerieBuilder` means that a `FaerieTrapManifest` will be present @@ -76,9 +79,76 @@ pub struct FaerieBackend { isa: Box, artifact: faerie::Artifact, trap_manifest: Option, + frame_sink: Option, libcall_names: Box String>, } +struct FaerieDebugSink<'a> { + pub data: &'a mut Vec, + pub functions: &'a [String], + pub artifact: &'a mut faerie::Artifact, +} + +impl<'a> gimli::write::Writer for FaerieDebugSink<'a> { + type Endian = gimli::LittleEndian; + + 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(()) + } + + 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(()) + } + + fn write_eh_pointer( + &mut self, + address: Address, + eh_pe: gimli::DwEhPe, + size: u8, + ) -> gimli::write::Result<()> { + match address { + Address::Constant(val) => self.write_udata(val, size), + Address::Symbol { symbol, addend } => { + 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(); + + 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) + } + } + } +} + pub struct FaerieCompiledFunction { code_length: u32, } @@ -108,6 +178,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, @@ -115,6 +186,7 @@ impl Backend for FaerieBackend { FaerieTrapCollection::Enabled => Some(FaerieTrapManifest::new()), FaerieTrapCollection::Disabled => None, }, + frame_sink: Some(frame_sink), libcall_names: builder.libcall_names, } } @@ -193,6 +265,44 @@ impl Backend for FaerieBackend { // because `define` will take ownership of code, this is our last chance let code_length = code.len() as u32; + if let Some(ref mut frame_sink) = self.frame_sink { + if let Some(layout) = ctx.func.frame_layout.as_ref() { + let (cie, mut encoder) = frame_sink.cie_for(&layout.initial); + + 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())); + } + } + } + } + + frame_changes.sort_by(|a, b| a.0.cmp(&b.0)); + + 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(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 + // using cranelift-faerie? + } + } + self.artifact .define(name, code) .expect("inconsistent declaration"); @@ -308,7 +418,25 @@ impl Backend for FaerieBackend { } fn publish(&mut self) { - // Nothing to do. + 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 = Vec::new(); + + let mut eh_frame_writer = gimli::write::EhFrame(FaerieDebugSink { + data: &mut eh_frame_bytes, + functions: frame_sink.fn_names_slice(), + artifact: &mut self.artifact, + }); + frame_sink.write_to(&mut eh_frame_writer).unwrap(); + + self.artifact.define(".eh_frame", eh_frame_bytes).unwrap(); + } } fn finish(self) -> FaerieProduct { 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,