use core::marker::PhantomData; use alloc::vec::Vec; use gimli::{ CfaRule, CieOrFde, DebugFrame, EhFrame, EhFrameHdr, Encoding, EndianSlice, Evaluation, EvaluationResult, EvaluationStorage, Expression, LittleEndian, Location, ParsedEhFrameHdr, Reader, ReaderOffset, Register, RegisterRule, UnwindContext, UnwindContextStorage, UnwindOffset, UnwindSection, UnwindTableRow, Value, }; pub(crate) use gimli::BaseAddresses; use crate::{arch::Arch, unwind_result::UnwindResult, ModuleSectionInfo}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum DwarfUnwinderError { FdeFromOffsetFailed(gimli::Error), UnwindInfoForAddressFailed(gimli::Error), StackPointerMovedBackwards, DidNotAdvance, CouldNotRecoverCfa, CouldNotRecoverReturnAddress, CouldNotRecoverFramePointer, } impl core::fmt::Display for DwarfUnwinderError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { Self::FdeFromOffsetFailed(err) => { write!(f, "Could not get the FDE for the supplied offset: {err}") } Self::UnwindInfoForAddressFailed(err) => write!( f, "Could not find DWARF unwind info for the requested address: {err}" ), Self::StackPointerMovedBackwards => write!(f, "Stack pointer moved backwards"), Self::DidNotAdvance => write!(f, "Did not advance"), Self::CouldNotRecoverCfa => write!(f, "Could not recover the CFA"), Self::CouldNotRecoverReturnAddress => write!(f, "Could not recover the return address"), Self::CouldNotRecoverFramePointer => write!(f, "Could not recover the frame pointer"), } } } #[cfg(feature = "std")] impl std::error::Error for DwarfUnwinderError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Self::FdeFromOffsetFailed(e) => Some(e), Self::UnwindInfoForAddressFailed(e) => Some(e), _ => None, } } } #[derive(Clone, Debug)] pub enum ConversionError { CfaIsExpression, CfaIsOffsetFromUnknownRegister, ReturnAddressRuleWithUnexpectedOffset, ReturnAddressRuleWasWeird, SpOffsetDoesNotFit, RegisterNotStoredRelativeToCfa, RestoringFpButNotLr, LrStorageOffsetDoesNotFit, FpStorageOffsetDoesNotFit, SpOffsetFromFpDoesNotFit, FramePointerRuleDoesNotRestoreLr, FramePointerRuleDoesNotRestoreFp, FramePointerRuleDoesNotRestoreBp, FramePointerRuleHasStrangeBpOffset, } pub trait DwarfUnwinding: Arch { fn unwind_frame( section: &impl UnwindSection, unwind_info: &UnwindTableRow, encoding: Encoding, regs: &mut Self::UnwindRegs, is_first_frame: bool, read_stack: &mut F, ) -> Result, DwarfUnwinderError> where F: FnMut(u64) -> Result, R: Reader, UCS: UnwindContextStorage, ES: EvaluationStorage; fn rule_if_uncovered_by_fde() -> Self::UnwindRule; } pub enum UnwindSectionType { EhFrame, DebugFrame, } pub struct DwarfUnwinder<'a, R, A, UCS> where R: Reader, A: DwarfUnwinding, UCS: UnwindContextStorage, { unwind_section_data: R, unwind_section_type: UnwindSectionType, eh_frame_hdr: Option>>, unwind_context: &'a mut UnwindContext, base_svma: u64, bases: BaseAddresses, _arch: PhantomData, } impl<'a, R, A, UCS> DwarfUnwinder<'a, R, A, UCS> where R: Reader, A: DwarfUnwinding, UCS: UnwindContextStorage, { pub fn new( unwind_section_data: R, unwind_section_type: UnwindSectionType, eh_frame_hdr_data: Option<&'a [u8]>, unwind_context: &'a mut UnwindContext, bases: BaseAddresses, base_svma: u64, ) -> Self { let eh_frame_hdr = match eh_frame_hdr_data { Some(eh_frame_hdr_data) => { let hdr = EhFrameHdr::new(eh_frame_hdr_data, unwind_section_data.endian()); match hdr.parse(&bases, 8) { Ok(hdr) => Some(hdr), Err(_) => None, } } None => None, }; Self { unwind_section_data, unwind_section_type, eh_frame_hdr, unwind_context, bases, base_svma, _arch: PhantomData, } } pub fn get_fde_offset_for_relative_address(&self, rel_lookup_address: u32) -> Option { let lookup_svma = self.base_svma + rel_lookup_address as u64; let eh_frame_hdr = self.eh_frame_hdr.as_ref()?; let table = eh_frame_hdr.table()?; let fde_ptr = table.lookup(lookup_svma, &self.bases).ok()?; let fde_offset = table.pointer_to_offset(fde_ptr).ok()?; fde_offset.0.into_u64().try_into().ok() } pub fn unwind_frame_with_fde( &mut self, regs: &mut A::UnwindRegs, is_first_frame: bool, rel_lookup_address: u32, fde_offset: u32, read_stack: &mut F, ) -> Result, DwarfUnwinderError> where F: FnMut(u64) -> Result, ES: EvaluationStorage, { let lookup_svma = self.base_svma + rel_lookup_address as u64; let unwind_section_data = self.unwind_section_data.clone(); match self.unwind_section_type { UnwindSectionType::EhFrame => { let mut eh_frame = EhFrame::from(unwind_section_data); eh_frame.set_address_size(8); let unwind_info = self.unwind_info_for_fde(&eh_frame, lookup_svma, fde_offset); if let Err(DwarfUnwinderError::UnwindInfoForAddressFailed(_)) = unwind_info { return Ok(UnwindResult::ExecRule(A::rule_if_uncovered_by_fde())); } let (unwind_info, encoding) = unwind_info?; A::unwind_frame::( &eh_frame, unwind_info, encoding, regs, is_first_frame, read_stack, ) } UnwindSectionType::DebugFrame => { let mut debug_frame = DebugFrame::from(unwind_section_data); debug_frame.set_address_size(8); let unwind_info = self.unwind_info_for_fde(&debug_frame, lookup_svma, fde_offset); if let Err(DwarfUnwinderError::UnwindInfoForAddressFailed(_)) = unwind_info { return Ok(UnwindResult::ExecRule(A::rule_if_uncovered_by_fde())); } let (unwind_info, encoding) = unwind_info?; A::unwind_frame::( &debug_frame, unwind_info, encoding, regs, is_first_frame, read_stack, ) } } } fn unwind_info_for_fde>( &mut self, unwind_section: &US, lookup_svma: u64, fde_offset: u32, ) -> Result<(&UnwindTableRow, Encoding), DwarfUnwinderError> { let fde = unwind_section.fde_from_offset( &self.bases, US::Offset::from(R::Offset::from_u32(fde_offset)), US::cie_from_offset, ); let fde = fde.map_err(DwarfUnwinderError::FdeFromOffsetFailed)?; let encoding = fde.cie().encoding(); let unwind_info: &UnwindTableRow<_, _> = fde .unwind_info_for_address( unwind_section, &self.bases, self.unwind_context, lookup_svma, ) .map_err(DwarfUnwinderError::UnwindInfoForAddressFailed)?; Ok((unwind_info, encoding)) } } pub(crate) fn base_addresses_for_sections( section_info: &mut impl ModuleSectionInfo, ) -> BaseAddresses { let mut start_addr = |names: &[&[u8]]| -> u64 { names .iter() .find_map(|name| section_info.section_svma_range(name)) .map(|r| r.start) .unwrap_or_default() }; BaseAddresses::default() .set_eh_frame(start_addr(&[b"__eh_frame", b".eh_frame"])) .set_eh_frame_hdr(start_addr(&[b"__eh_frame_hdr", b".eh_frame_hdr"])) .set_text(start_addr(&[b"__text", b".text"])) .set_got(start_addr(&[b"__got", b".got"])) } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum DwarfCfiIndexError { Gimli(gimli::Error), CouldNotSubtractBaseAddress, RelativeAddressTooBig, FdeOffsetTooBig, } impl core::fmt::Display for DwarfCfiIndexError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { Self::Gimli(e) => write!(f, "EhFrame processing failed: {e}"), Self::CouldNotSubtractBaseAddress => { write!(f, "Could not subtract base address to create relative pc") } Self::RelativeAddressTooBig => write!(f, "Relative address did not fit into u32"), Self::FdeOffsetTooBig => write!(f, "FDE offset did not fit into u32"), } } } impl From for DwarfCfiIndexError { fn from(e: gimli::Error) -> Self { Self::Gimli(e) } } #[cfg(feature = "std")] impl std::error::Error for DwarfCfiIndexError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Self::Gimli(e) => Some(e), _ => None, } } } /// A binary search table for eh_frame FDEs. We generate this whenever a module /// without eh_frame_hdr is added. pub struct DwarfCfiIndex { /// Contains the initial address for every FDE, relative to the base address. /// This vector is sorted so that it can be used for binary search. /// It has the same length as `fde_offsets`. sorted_fde_pc_starts: Vec, /// Contains the FDE offset for every FDE. The FDE at offset `fde_offsets[i]` /// has a PC range which starts at `sorted_fde_pc_starts[i]`. fde_offsets: Vec, } impl DwarfCfiIndex { pub fn try_new( unwind_section: US, bases: BaseAddresses, base_svma: u64, ) -> Result where R: Reader, R::Offset: TryInto, US: UnwindSection, { let mut fde_pc_and_offset = Vec::new(); let mut cur_cie = None; let mut entries_iter = unwind_section.entries(&bases); while let Some(entry) = entries_iter.next()? { let fde = match entry { CieOrFde::Cie(cie) => { cur_cie = Some(cie); continue; } CieOrFde::Fde(partial_fde) => { partial_fde.parse(|unwind_section, bases, cie_offset| { if let Some(cie) = &cur_cie { if cie.offset() == >::into(cie_offset) { return Ok(cie.clone()); } } let cie = unwind_section.cie_from_offset(bases, cie_offset); if let Ok(cie) = &cie { cur_cie = Some(cie.clone()); } cie })? } }; let pc = fde.initial_address(); let relative_pc = pc .checked_sub(base_svma) .ok_or(DwarfCfiIndexError::CouldNotSubtractBaseAddress)?; let relative_pc = u32::try_from(relative_pc) .map_err(|_| DwarfCfiIndexError::RelativeAddressTooBig)?; let fde_offset = >::try_into(fde.offset()) .map_err(|_| DwarfCfiIndexError::FdeOffsetTooBig)?; fde_pc_and_offset.push((relative_pc, fde_offset)); } fde_pc_and_offset.sort_by_key(|(pc, _)| *pc); let sorted_fde_pc_starts = fde_pc_and_offset.iter().map(|(pc, _)| *pc).collect(); let fde_offsets = fde_pc_and_offset.into_iter().map(|(_, fde)| fde).collect(); Ok(Self { sorted_fde_pc_starts, fde_offsets, }) } pub fn try_new_eh_frame( eh_frame_data: &[u8], section_info: &mut impl ModuleSectionInfo, ) -> Result { let bases = base_addresses_for_sections(section_info); let mut eh_frame = EhFrame::from(EndianSlice::new(eh_frame_data, LittleEndian)); eh_frame.set_address_size(8); Self::try_new(eh_frame, bases, section_info.base_svma()) } pub fn try_new_debug_frame( debug_frame_data: &[u8], section_info: &mut impl ModuleSectionInfo, ) -> Result { let bases = base_addresses_for_sections(section_info); let mut debug_frame = DebugFrame::from(EndianSlice::new(debug_frame_data, LittleEndian)); debug_frame.set_address_size(8); Self::try_new(debug_frame, bases, section_info.base_svma()) } pub fn fde_offset_for_relative_address(&self, rel_lookup_address: u32) -> Option { let i = match self.sorted_fde_pc_starts.binary_search(&rel_lookup_address) { Err(0) => return None, Ok(i) => i, Err(i) => i - 1, }; Some(self.fde_offsets[i]) } } pub trait DwarfUnwindRegs { fn get(&self, register: Register) -> Option; } pub fn eval_cfa_rule>( section: &impl UnwindSection, rule: &CfaRule, encoding: Encoding, regs: &UR, ) -> Option { match rule { CfaRule::RegisterAndOffset { register, offset } => { let val = regs.get(*register)?; u64::try_from(i64::try_from(val).ok()?.checked_add(*offset)?).ok() } CfaRule::Expression(expr) => { let expr = expr.get(section).ok()?; eval_expr::(expr, encoding, regs) } } } fn eval_expr>( expr: Expression, encoding: Encoding, regs: &UR, ) -> Option { let mut eval = Evaluation::::new_in(expr.0, encoding); let mut result = eval.evaluate().ok()?; loop { match result { EvaluationResult::Complete => break, EvaluationResult::RequiresRegister { register, .. } => { let value = regs.get(register)?; result = eval.resume_with_register(Value::Generic(value as _)).ok()?; } _ => return None, } } let x = &eval.as_result().last()?.location; if let Location::Address { address } = x { Some(*address) } else { None } } pub fn eval_register_rule( section: &impl UnwindSection, rule: RegisterRule, cfa: u64, encoding: Encoding, val: u64, regs: &UR, read_stack: &mut F, ) -> Option where R: Reader, F: FnMut(u64) -> Result, UR: DwarfUnwindRegs, S: EvaluationStorage, { match rule { RegisterRule::Undefined => None, RegisterRule::SameValue => Some(val), RegisterRule::Offset(offset) => { let cfa_plus_offset = u64::try_from(i64::try_from(cfa).ok()?.checked_add(offset)?).ok()?; read_stack(cfa_plus_offset).ok() } RegisterRule::ValOffset(offset) => { u64::try_from(i64::try_from(cfa).ok()?.checked_add(offset)?).ok() } RegisterRule::Register(register) => regs.get(register), RegisterRule::Expression(expr) => { let expr = expr.get(section).ok()?; let val = eval_expr::(expr, encoding, regs)?; read_stack(val).ok() } RegisterRule::ValExpression(expr) => { let expr = expr.get(section).ok()?; eval_expr::(expr, encoding, regs) } RegisterRule::Architectural => { // Unimplemented // TODO: Find out what the architectural rules for x86_64 and for aarch64 are, if any. None } _ => None, } }