From 0b6670f9e86dbf5e9abcb1c30b6c2d4829451928 Mon Sep 17 00:00:00 2001 From: Philip Craig Date: Thu, 10 Aug 2023 15:55:58 +1000 Subject: [PATCH] read/cfi: implement DW_CFA_AARCH64_negate_ra_state --- crates/examples/src/bin/dwarfdump.rs | 11 ++ src/common.rs | 10 ++ src/constants.rs | 11 +- src/read/cfi.rs | 158 ++++++++++++++++++++++++++- src/write/cfi.rs | 91 +++++++++------ 5 files changed, 242 insertions(+), 39 deletions(-) diff --git a/crates/examples/src/bin/dwarfdump.rs b/crates/examples/src/bin/dwarfdump.rs index 30bf809d..84e10e04 100644 --- a/crates/examples/src/bin/dwarfdump.rs +++ b/crates/examples/src/bin/dwarfdump.rs @@ -735,6 +735,11 @@ fn dump_eh_frame( .unwrap_or(mem::size_of::() as u8); eh_frame.set_address_size(address_size); + match file.architecture() { + object::Architecture::Aarch64 => eh_frame.set_vendor(gimli::Vendor::AArch64), + _ => {} + } + fn register_name_none(_: gimli::Register) -> Option<&'static str> { None } @@ -1050,9 +1055,15 @@ fn dump_cfi_instructions( ArgsSize { size } => { writeln!(w, " DW_CFA_GNU_args_size ({})", size)?; } + NegateRaState => { + writeln!(w, " DW_CFA_AARCH64_negate_ra_state")?; + } Nop => { writeln!(w, " DW_CFA_nop")?; } + _ => { + writeln!(w, " {:?}", op)?; + } }, } } diff --git a/src/common.rs b/src/common.rs index 3c807362..fc6693d1 100644 --- a/src/common.rs +++ b/src/common.rs @@ -27,6 +27,16 @@ impl Format { } } +/// Which vendor extensions to support. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[non_exhaustive] +pub enum Vendor { + /// A default set of extensions, including some common GNU extensions. + Default, + /// AAarch64 extensions. + AArch64, +} + /// Encoding parameters that are commonly used for multiple DWARF sections. /// /// This is intended to be small enough to pass by value. diff --git a/src/constants.rs b/src/constants.rs index a0a257a7..e58050ba 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -56,7 +56,10 @@ use core::fmt; // } // } macro_rules! dw { - ($(#[$meta:meta])* $struct_name:ident($struct_type:ty) { $($name:ident = $val:expr),+ $(,)? }) => { + ($(#[$meta:meta])* $struct_name:ident($struct_type:ty) + { $($name:ident = $val:expr),+ $(,)? } + $(, aliases { $($alias_name:ident = $alias_val:expr),+ $(,)? })? + ) => { $(#[$meta])* #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct $struct_name(pub $struct_type); @@ -64,6 +67,9 @@ macro_rules! dw { $( pub const $name: $struct_name = $struct_name($val); )+ + $($( + pub const $alias_name: $struct_name = $struct_name($alias_val); + )+)* impl $struct_name { pub fn static_string(&self) -> Option<&'static str> { @@ -182,6 +188,9 @@ DwCfa(u8) { DW_CFA_GNU_window_save = 0x2d, DW_CFA_GNU_args_size = 0x2e, DW_CFA_GNU_negative_offset_extended = 0x2f, +}, +aliases { + DW_CFA_AARCH64_negate_ra_state = 0x2d, }); dw!( diff --git a/src/read/cfi.rs b/src/read/cfi.rs index a80c8a0e..6ac019f1 100644 --- a/src/read/cfi.rs +++ b/src/read/cfi.rs @@ -8,7 +8,9 @@ use core::mem; use core::num::Wrapping; use super::util::{ArrayLike, ArrayVec}; -use crate::common::{DebugFrameOffset, EhFrameOffset, Encoding, Format, Register, SectionId}; +use crate::common::{ + DebugFrameOffset, EhFrameOffset, Encoding, Format, Register, SectionId, Vendor, +}; use crate::constants::{self, DwEhPe}; use crate::endianity::Endianity; use crate::read::{ @@ -34,6 +36,7 @@ pub struct DebugFrame { section: R, address_size: u8, segment_size: u8, + vendor: Vendor, } impl DebugFrame { @@ -52,6 +55,13 @@ impl DebugFrame { pub fn set_segment_size(&mut self, segment_size: u8) { self.segment_size = segment_size } + + /// Set the vendor extensions to use. + /// + /// This defaults to `Vendor::Default`. + pub fn set_vendor(&mut self, vendor: Vendor) { + self.vendor = vendor; + } } impl<'input, Endian> DebugFrame> @@ -95,6 +105,7 @@ impl From for DebugFrame { section, address_size: mem::size_of::() as u8, segment_size: 0, + vendor: Vendor::Default, } } } @@ -482,6 +493,7 @@ impl<'a, R: Reader + 'a> EhHdrTable<'a, R> { pub struct EhFrame { section: R, address_size: u8, + vendor: Vendor, } impl EhFrame { @@ -491,6 +503,13 @@ impl EhFrame { pub fn set_address_size(&mut self, address_size: u8) { self.address_size = address_size } + + /// Set the vendor extensions to use. + /// + /// This defaults to `Vendor::Default`. + pub fn set_vendor(&mut self, vendor: Vendor) { + self.vendor = vendor; + } } impl<'input, Endian> EhFrame> @@ -533,6 +552,7 @@ impl From for EhFrame { EhFrame { section, address_size: mem::size_of::() as u8, + vendor: Vendor::Default, } } } @@ -613,6 +633,9 @@ pub trait _UnwindSectionPrivate { /// The segment size to use if `has_address_and_segment_sizes` returns false. fn segment_size(&self) -> u8; + + /// The vendor extensions to use. + fn vendor(&self) -> Vendor; } /// A section holding unwind information: either `.debug_frame` or @@ -808,6 +831,10 @@ impl _UnwindSectionPrivate for DebugFrame { fn segment_size(&self) -> u8 { self.segment_size } + + fn vendor(&self) -> Vendor { + self.vendor + } } impl UnwindSection for DebugFrame { @@ -848,6 +875,10 @@ impl _UnwindSectionPrivate for EhFrame { fn segment_size(&self) -> u8 { 0 } + + fn vendor(&self) -> Vendor { + self.vendor + } } impl UnwindSection for EhFrame { @@ -1421,6 +1452,7 @@ impl CommonInformationEntry { address_size: self.address_size, section: section.section(), }, + vendor: section.vendor(), } } @@ -1764,6 +1796,7 @@ impl FrameDescriptionEntry { address_size: self.cie.address_size, section: section.section(), }, + vendor: section.vendor(), } } @@ -2400,6 +2433,18 @@ impl<'a, 'ctx, R: Reader, A: UnwindContextStorage> UnwindTable<'a, 'ctx, R, A self.ctx.row_mut().saved_args_size = size; } + // AArch64 extension. + NegateRaState => { + let register = crate::AArch64::RA_SIGN_STATE; + let value = match self.ctx.row().register(register) { + RegisterRule::Undefined => 0, + RegisterRule::Constant(value) => value, + _ => return Err(Error::CfiInstructionInInvalidContext), + }; + self.ctx + .set_register_rule(register, RegisterRule::Constant(value ^ 1))?; + } + // No operation. Nop => {} }; @@ -2769,6 +2814,7 @@ impl CfaRule { /// has been saved and the rule to find the value for the register in the /// previous frame." #[derive(Clone, Debug, PartialEq, Eq)] +#[non_exhaustive] pub enum RegisterRule { /// > A register that has this rule has no recoverable value in the previous /// > frame. (By convention, it is not preserved by a callee.) @@ -2801,6 +2847,9 @@ pub enum RegisterRule { /// "The rule is defined externally to this specification by the augmenter." Architectural, + + /// This is a pseudo-register with a constant value. + Constant(u64), } impl RegisterRule { @@ -2811,6 +2860,7 @@ impl RegisterRule { /// A parsed call frame instruction. #[derive(Clone, Debug, PartialEq, Eq)] +#[non_exhaustive] pub enum CallFrameInstruction { // 6.4.2.1 Row Creation Methods /// > 1. DW_CFA_set_loc @@ -3088,6 +3138,17 @@ pub enum CallFrameInstruction { size: u64, }, + /// > DW_CFA_AARCH64_negate_ra_state + /// > + /// > AArch64 Extension + /// > + /// > The DW_CFA_AARCH64_negate_ra_state operation negates bit[0] of the + /// > RA_SIGN_STATE pseudo-register. It does not take any operands. The + /// > DW_CFA_AARCH64_negate_ra_state must not be mixed with other DWARF Register + /// > Rule Instructions on the RA_SIGN_STATE pseudo-register in one Common + /// > Information Entry (CIE) and Frame Descriptor Entry (FDE) program sequence. + NegateRaState, + // 6.4.2.5 Padding Instruction /// > 1. DW_CFA_nop /// > @@ -3104,6 +3165,7 @@ impl CallFrameInstruction { input: &mut R, address_encoding: Option, parameters: &PointerEncodingParameters, + vendor: Vendor, ) -> Result> { let instruction = input.read_u8()?; let high_bits = instruction & CFI_INSTRUCTION_HIGH_BITS_MASK; @@ -3292,6 +3354,10 @@ impl CallFrameInstruction { Ok(CallFrameInstruction::ArgsSize { size }) } + constants::DW_CFA_AARCH64_negate_ra_state if vendor == Vendor::AArch64 => { + Ok(CallFrameInstruction::NegateRaState) + } + otherwise => Err(Error::UnknownCallFrameInstruction(otherwise)), } } @@ -3306,6 +3372,7 @@ pub struct CallFrameInstructionIter<'a, R: Reader> { input: R, address_encoding: Option, parameters: PointerEncodingParameters<'a, R>, + vendor: Vendor, } impl<'a, R: Reader> CallFrameInstructionIter<'a, R> { @@ -3315,8 +3382,12 @@ impl<'a, R: Reader> CallFrameInstructionIter<'a, R> { return Ok(None); } - match CallFrameInstruction::parse(&mut self.input, self.address_encoding, &self.parameters) - { + match CallFrameInstruction::parse( + &mut self.input, + self.address_encoding, + &self.parameters, + self.vendor, + ) { Ok(instruction) => Ok(Some(instruction)), Err(e) => { self.input.empty(); @@ -4436,7 +4507,7 @@ mod tests { address_size, section: &R::default(), }; - CallFrameInstruction::parse(input, None, parameters) + CallFrameInstruction::parse(input, None, parameters, Vendor::Default) } #[test] @@ -4549,7 +4620,12 @@ mod tests { section: &EndianSlice::new(&[], LittleEndian), }; assert_eq!( - CallFrameInstruction::parse(input, Some(constants::DW_EH_PE_textrel), parameters), + CallFrameInstruction::parse( + input, + Some(constants::DW_EH_PE_textrel), + parameters, + Vendor::Default + ), Ok(CallFrameInstruction::SetLoc { address: expected_addr, }) @@ -5008,6 +5084,27 @@ mod tests { assert_eq!(*input, EndianSlice::new(&expected_rest, LittleEndian)); } + #[test] + fn test_parse_cfi_instruction_negate_ra_state() { + let expected_rest = [1, 2, 3, 4]; + let section = Section::with_endian(Endian::Little) + .D8(constants::DW_CFA_AARCH64_negate_ra_state.0) + .append_bytes(&expected_rest); + let contents = section.get_contents().unwrap(); + let input = &mut EndianSlice::new(&contents, LittleEndian); + let parameters = &PointerEncodingParameters { + bases: &SectionBaseAddresses::default(), + func_base: None, + address_size: 8, + section: &EndianSlice::default(), + }; + assert_eq!( + CallFrameInstruction::parse(input, None, parameters, Vendor::AArch64), + Ok(CallFrameInstruction::NegateRaState) + ); + assert_eq!(*input, EndianSlice::new(&expected_rest, LittleEndian)); + } + #[test] fn test_parse_cfi_instruction_unknown_instruction() { let expected_rest = [1, 2, 3, 4]; @@ -5056,6 +5153,7 @@ mod tests { input, address_encoding: None, parameters, + vendor: Vendor::Default, }; assert_eq!( @@ -5093,6 +5191,7 @@ mod tests { input, address_encoding: None, parameters, + vendor: Vendor::Default, }; assert_eq!( @@ -5570,6 +5669,55 @@ mod tests { assert_eval(ctx, expected, cie, None, instructions); } + #[test] + fn test_eval_negate_ra_state() { + let cie = make_test_cie(); + let ctx = UnwindContext::new(); + let mut expected = ctx.clone(); + expected + .set_register_rule(crate::AArch64::RA_SIGN_STATE, RegisterRule::Constant(1)) + .unwrap(); + let instructions = [(Ok(false), CallFrameInstruction::NegateRaState)]; + assert_eval(ctx, expected, cie, None, instructions); + + let cie = make_test_cie(); + let ctx = UnwindContext::new(); + let mut expected = ctx.clone(); + expected + .set_register_rule(crate::AArch64::RA_SIGN_STATE, RegisterRule::Constant(0)) + .unwrap(); + let instructions = [ + (Ok(false), CallFrameInstruction::NegateRaState), + (Ok(false), CallFrameInstruction::NegateRaState), + ]; + assert_eval(ctx, expected, cie, None, instructions); + + // NegateRaState can't be used with other instructions. + let cie = make_test_cie(); + let ctx = UnwindContext::new(); + let mut expected = ctx.clone(); + expected + .set_register_rule( + crate::AArch64::RA_SIGN_STATE, + RegisterRule::Offset(cie.data_alignment_factor as i64), + ) + .unwrap(); + let instructions = [ + ( + Ok(false), + CallFrameInstruction::Offset { + register: crate::AArch64::RA_SIGN_STATE, + factored_offset: 1, + }, + ), + ( + Err(Error::CfiInstructionInInvalidContext), + CallFrameInstruction::NegateRaState, + ), + ]; + assert_eval(ctx, expected, cie, None, instructions); + } + #[test] fn test_eval_nop() { let cie = make_test_cie(); diff --git a/src/write/cfi.rs b/src/write/cfi.rs index 718cb69a..5e108f15 100644 --- a/src/write/cfi.rs +++ b/src/write/cfi.rs @@ -377,6 +377,7 @@ impl FrameDescriptionEntry { /// /// This may be a CFA definition, a register rule, or some other directive. #[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[non_exhaustive] pub enum CallFrameInstruction { /// Define the CFA rule to use the provided register and offset. Cfa(Register, i32), @@ -410,6 +411,9 @@ pub enum CallFrameInstruction { RestoreState, /// The size of the arguments that have been pushed onto the stack. ArgsSize(u32), + + /// AAarch64 extension: negate the `RA_SIGN_STATE` pseudo-register. + NegateRaState, } impl CallFrameInstruction { @@ -523,6 +527,9 @@ impl CallFrameInstruction { w.write_u8(constants::DW_CFA_GNU_args_size.0)?; w.write_uleb128(size.into())?; } + CallFrameInstruction::NegateRaState => { + w.write_u8(constants::DW_CFA_AARCH64_negate_ra_state.0)?; + } } Ok(()) } @@ -834,6 +841,7 @@ pub(crate) mod convert { read::CallFrameInstruction::ArgsSize { size } => { CallFrameInstruction::ArgsSize(size as u32) } + read::CallFrameInstruction::NegateRaState => CallFrameInstruction::NegateRaState, read::CallFrameInstruction::Nop => return Ok(None), })) } @@ -847,7 +855,7 @@ mod tests { use crate::arch::X86_64; use crate::read; use crate::write::EndianVec; - use crate::LittleEndian; + use crate::{LittleEndian, Vendor}; #[test] fn test_frame_table() { @@ -980,44 +988,61 @@ mod tests { (28 + 0x20280, CallFrameInstruction::ArgsSize(23)), ]; + let fde_instructions_aarch64 = [(0, CallFrameInstruction::NegateRaState)]; + for &version in &[1, 3, 4] { for &address_size in &[4, 8] { - for &format in &[Format::Dwarf32, Format::Dwarf64] { - let encoding = Encoding { - format, - version, - address_size, - }; - let mut frames = FrameTable::default(); - - let mut cie = CommonInformationEntry::new(encoding, 2, 8, X86_64::RA); - for i in &cie_instructions { - cie.add_instruction(i.clone()); - } - let cie_id = frames.add_cie(cie); + for &vendor in &[Vendor::Default, Vendor::AArch64] { + for &format in &[Format::Dwarf32, Format::Dwarf64] { + let encoding = Encoding { + format, + version, + address_size, + }; + let mut frames = FrameTable::default(); + + let mut cie = CommonInformationEntry::new(encoding, 2, 8, X86_64::RA); + for i in &cie_instructions { + cie.add_instruction(i.clone()); + } + let cie_id = frames.add_cie(cie); - let mut fde = FrameDescriptionEntry::new(Address::Constant(0x1000), 0x10); - for (o, i) in &fde_instructions { - fde.add_instruction(*o, i.clone()); - } - frames.add_fde(cie_id, fde); + let mut fde = FrameDescriptionEntry::new(Address::Constant(0x1000), 0x10); + for (o, i) in &fde_instructions { + fde.add_instruction(*o, i.clone()); + } + frames.add_fde(cie_id, fde); + + if vendor == Vendor::AArch64 { + let mut fde = + FrameDescriptionEntry::new(Address::Constant(0x2000), 0x10); + for (o, i) in &fde_instructions_aarch64 { + fde.add_instruction(*o, i.clone()); + } + frames.add_fde(cie_id, fde); + } - let mut debug_frame = DebugFrame::from(EndianVec::new(LittleEndian)); - frames.write_debug_frame(&mut debug_frame).unwrap(); + let mut debug_frame = DebugFrame::from(EndianVec::new(LittleEndian)); + frames.write_debug_frame(&mut debug_frame).unwrap(); - let mut read_debug_frame = - read::DebugFrame::new(debug_frame.slice(), LittleEndian); - read_debug_frame.set_address_size(address_size); - let frames = FrameTable::from(&read_debug_frame, &|address| { - Some(Address::Constant(address)) - }) - .unwrap(); + let mut read_debug_frame = + read::DebugFrame::new(debug_frame.slice(), LittleEndian); + read_debug_frame.set_address_size(address_size); + read_debug_frame.set_vendor(vendor); + let frames = FrameTable::from(&read_debug_frame, &|address| { + Some(Address::Constant(address)) + }) + .unwrap(); - assert_eq!( - &frames.cies.get_index(0).unwrap().instructions, - &cie_instructions - ); - assert_eq!(&frames.fdes[0].1.instructions, &fde_instructions); + assert_eq!( + &frames.cies.get_index(0).unwrap().instructions, + &cie_instructions + ); + assert_eq!(&frames.fdes[0].1.instructions, &fde_instructions); + if vendor == Vendor::AArch64 { + assert_eq!(&frames.fdes[1].1.instructions, &fde_instructions_aarch64); + } + } } } }