From 357e283227749060a0f99a29eee91fde002069be Mon Sep 17 00:00:00 2001 From: Justin Michaud Date: Sun, 6 Jul 2025 14:15:43 -0600 Subject: [PATCH] Add basic support for armhf fp-based unwinding In general, armhf platforms do not create frame chains using r7/r11. However, when using only thumb or arm mode, and compiling with frame pointers enabled, this does work. This patch adds very simple support for frame chain unwinding on armhf. --- .../aarch64/fp-basic-unwind-a64-jit.stack.bin | Bin 0 -> 135168 bytes fixtures/linux/aarch64/fp-basic-unwind-a64.cc | 76 +++++ .../aarch64/fp-basic-unwind-a64.stack.bin | Bin 0 -> 135168 bytes .../armhf/fp-basic-unwind-a32-jit.stack.bin | Bin 0 -> 135168 bytes fixtures/linux/armhf/fp-basic-unwind-a32.cc | 98 ++++++ .../linux/armhf/fp-basic-unwind-a32.stack.bin | Bin 0 -> 135168 bytes src/armhf/arch.rs | 10 + src/armhf/cache.rs | 30 ++ src/armhf/dwarf.rs | 39 +++ src/armhf/instruction_analysis.rs | 12 + src/armhf/macho.rs | 21 ++ src/armhf/mod.rs | 15 + src/armhf/pe.rs | 19 ++ src/armhf/unwind_rule.rs | 103 +++++++ src/armhf/unwinder.rs | 66 ++++ src/armhf/unwindregs.rs | 66 ++++ src/lib.rs | 12 + src/macho.rs | 2 + src/pe.rs | 2 + tests/integration_tests/linux.rs | 286 ++++++++++++++++++ 20 files changed, 857 insertions(+) create mode 100644 fixtures/linux/aarch64/fp-basic-unwind-a64-jit.stack.bin create mode 100644 fixtures/linux/aarch64/fp-basic-unwind-a64.cc create mode 100644 fixtures/linux/aarch64/fp-basic-unwind-a64.stack.bin create mode 100644 fixtures/linux/armhf/fp-basic-unwind-a32-jit.stack.bin create mode 100644 fixtures/linux/armhf/fp-basic-unwind-a32.cc create mode 100644 fixtures/linux/armhf/fp-basic-unwind-a32.stack.bin create mode 100644 src/armhf/arch.rs create mode 100644 src/armhf/cache.rs create mode 100644 src/armhf/dwarf.rs create mode 100644 src/armhf/instruction_analysis.rs create mode 100644 src/armhf/macho.rs create mode 100644 src/armhf/mod.rs create mode 100644 src/armhf/pe.rs create mode 100644 src/armhf/unwind_rule.rs create mode 100644 src/armhf/unwinder.rs create mode 100644 src/armhf/unwindregs.rs diff --git a/fixtures/linux/aarch64/fp-basic-unwind-a64-jit.stack.bin b/fixtures/linux/aarch64/fp-basic-unwind-a64-jit.stack.bin new file mode 100644 index 0000000000000000000000000000000000000000..9e49cd609eb03077f397a911c18eff202168eadf GIT binary patch literal 135168 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 literal 0 HcmV?d00001 diff --git a/fixtures/linux/aarch64/fp-basic-unwind-a64.cc b/fixtures/linux/aarch64/fp-basic-unwind-a64.cc new file mode 100644 index 0000000..ec35620 --- /dev/null +++ b/fixtures/linux/aarch64/fp-basic-unwind-a64.cc @@ -0,0 +1,76 @@ +// clear && clang++ -std=c++23 fp-basic-unwind-a64.cc -o fp-basic-unwind-a64 && lldb ./fp-basic-unwind-a64 +// A little demo program demonstrating unwinding a jit region without debug information + +/* + +Dumping the stack using lldb: + +bt +p/x $pc +image lookup -a `$pc` +p/x ((void***) $fp)[0][1] +image lookup -a `((void***) $fp)[0][1]` +p/x ((void****) $fp)[0][0][1] +image lookup -a `((void****) $fp)[0][0][1]` +p/x ((void*****) $fp)[0][0][0][1] +image lookup -a `((void*****) $fp)[0][0][0][1]` +p/x ((void******) $fp)[0][0][0][0][1] +image lookup -a `((void******) $fp)[0][0][0][0][1]` + +p/x $sp +p/x ((void******) $fp)[0][0][0][0] # Last stack frame + +image list +image dump sections + +# To get stack bounds: +(gdb) info proc mapping + +memory read --outfile ./fp-basic-unwind-a64.stack.bin 0xfffffffdf000 0x1000000000000 --binary --force + + */ + +#include +#include +#include +#include +#include + +extern "C" void breakpoint_mock() +{ + __asm__ volatile ( + "brk #0\n" + ); +} + +extern "C" void baseline_mock(uintptr_t baseline_mock_2, uintptr_t breakpoint_mock); +__asm__ ( + "baseline_mock:" "\n" + " stp fp, lr, [sp, #-16]!" "\n" + " mov fp, sp" "\n" + " sub sp, fp, #96" "\n" + " movz x16, 0xBEEF" "\n" + " stur x16, [sp]" "\n" + " blr x0" "\n" + "baseline_mock_2:" "\n" + " stp fp, lr, [sp, #-16]!" "\n" + " mov fp, sp" "\n" + " sub sp, fp, #512" "\n" + " movz x16, 0xBFFF" "\n" + " stur x16, [sp]" "\n" + " blr x1" "\n" +); + +int main(void) +{ + void* jit = mmap(NULL, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (jit == (void *) -1) + return 1; + + printf("Have native stack %p, jit %p\n", __builtin_frame_address(0), jit); + + // baseline_mock((uintptr_t)baseline_mock + 6 * 4, (uintptr_t)breakpoint_mock); + std::memcpy(jit, (void*) baseline_mock, 1024); + + ((void (*)(uintptr_t, uintptr_t))jit)((uintptr_t)jit + 6 * 4, (uintptr_t)breakpoint_mock); +} diff --git a/fixtures/linux/aarch64/fp-basic-unwind-a64.stack.bin b/fixtures/linux/aarch64/fp-basic-unwind-a64.stack.bin new file mode 100644 index 0000000000000000000000000000000000000000..a7b6df0ae9fb8c027239938ba396ce014a6ad22c GIT binary patch literal 135168 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 DZ?!o{ literal 0 HcmV?d00001 diff --git a/fixtures/linux/armhf/fp-basic-unwind-a32-jit.stack.bin b/fixtures/linux/armhf/fp-basic-unwind-a32-jit.stack.bin new file mode 100644 index 0000000000000000000000000000000000000000..2c9a60627ecbe3c9433322800db4c73e61571eda GIT binary patch literal 135168 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 H=fnRm^$%9l literal 0 HcmV?d00001 diff --git a/fixtures/linux/armhf/fp-basic-unwind-a32.cc b/fixtures/linux/armhf/fp-basic-unwind-a32.cc new file mode 100644 index 0000000..c37f50d --- /dev/null +++ b/fixtures/linux/armhf/fp-basic-unwind-a32.cc @@ -0,0 +1,98 @@ +// clear && clang++ -g -std=c++23 fp-basic-unwind-a32.cc -o fp-basic-unwind-a32 -mthumb -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer && lldb ./fp-basic-unwind-a32 +// A little demo program demonstrating unwinding a jit region without debug information + +/* + +Dumping the stack using lldb: + +bt +p/x (uintptr_t)$pc - 0x00400000 +image lookup -a `$pc` +p/x ((uintptr_t*) $r7)[1] - 0x00400000 +image lookup -a `((void**) $r7)[1]` +p/x ((uintptr_t**) $r7)[0][1] - 0x00400000 +image lookup -a `((void***) $r7)[0][1]` +p/x ((uintptr_t***) $r7)[0][0][1] - 0x00400000 +image lookup -a `((void****) $r7)[0][0][1]` +p/x ((uintptr_t****) $r7)[0][0][0][1] - 0xf7c8a000 +image lookup -a `((void*****) $r7)[0][0][0][1]` +p/x ((uintptr_t*****) $r7)[0][0][0][0][1] +image lookup -a `((void******) $r7)[0][0][0][0][1]` + +p/x $sp +p/x ((void******) $r7)[0][0][0][0] # Last stack frame + +image list +image dump sections + +# To get stack bounds: +(gdb) info proc mapping + +memory read --outfile ./fp-basic-unwind-a32.stack.bin 0xfffcf000 0xffff0000 --binary --force + +p/x 0xffff0000-$r7 +p/x 0xffff0000-$sp +p/x $pc-0x00400000 +p/x $lr-0x00400000 + + */ + +#include +#include +#include +#include +#include + +__attribute__((target("thumb"))) +extern "C" void breakpoint_mock() +{ + __asm__ volatile ( + ".thumb\n" + ".thumb_func\n" + "nop\n" + "nop\n" + "nop\n" + "bkpt #0\n" + "nop\n" + "nop\n" + "nop\n" + "nop\n" + ); +} + +__attribute__((target("thumb"))) +extern "C" void baseline_mock(uint8_t* baseline_mock_2, uint8_t* breakpoint_mock); +__asm__ ( + ".thumb" "\n" + ".thumb_func" "\n" + "baseline_mock:" "\n" + " push.w {r7, lr}" "\n" + " mov.w r7, sp" "\n" + " sub.w sp, #0x20" "\n" + " mov r2, 0xBEEF" "\n" + " str.w r2, [sp, #4]" "\n" + " blx r0" "\n" + ".thumb" "\n" + ".thumb_func" "\n" + "baseline_mock_2:" "\n" + " push.w {r7, lr}" "\n" + " mov.w r7, sp" "\n" + " sub.w sp, #0x28" "\n" + " mov r2, 0xBEEF" "\n" + " str.w r2, [sp, #4]" "\n" + " blx r1" "\n" +); + +int main(void) +{ + void* jit = mmap(NULL, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (jit == (void *) -1) + return 1; + + printf("Have native stack %p, jit %p\n", __builtin_frame_address(0), jit); + + // baseline_mock((uint8_t*)baseline_mock + 22, (uint8_t*)breakpoint_mock); + std::memcpy(jit, (void*)((uint8_t*) baseline_mock - 1), 1024); + + ((void (*)(uint8_t*, uint8_t*)) ((uint8_t*)jit + 1))((uint8_t*)jit + 22 + 1, (uint8_t*)breakpoint_mock); +} diff --git a/fixtures/linux/armhf/fp-basic-unwind-a32.stack.bin b/fixtures/linux/armhf/fp-basic-unwind-a32.stack.bin new file mode 100644 index 0000000000000000000000000000000000000000..aa10249577935416bb614068994a95ff8670575c GIT binary patch literal 135168 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 VfqiQxf5N1}PTFLrJ*=IB{~wjRK&1cx literal 0 HcmV?d00001 diff --git a/src/armhf/arch.rs b/src/armhf/arch.rs new file mode 100644 index 0000000..bb71baf --- /dev/null +++ b/src/armhf/arch.rs @@ -0,0 +1,10 @@ +use super::unwind_rule::UnwindRuleArmhf; +use super::unwindregs::UnwindRegsArmhf; +use crate::arch::Arch; + +/// The Armhf CPU architecture. +pub struct ArchArmhf; +impl Arch for ArchArmhf { + type UnwindRule = UnwindRuleArmhf; + type UnwindRegs = UnwindRegsArmhf; +} diff --git a/src/armhf/cache.rs b/src/armhf/cache.rs new file mode 100644 index 0000000..1dbca3e --- /dev/null +++ b/src/armhf/cache.rs @@ -0,0 +1,30 @@ +use super::unwind_rule::*; +use crate::cache::*; + +/// The unwinder cache type for [`UnwinderArmhf`](super::UnwinderArmhf). +pub struct CacheArmhf(pub Cache); + +impl CacheArmhf { + /// Create a new cache. + pub fn new() -> Self { + Self(Cache::new()) + } +} + +impl CacheArmhf

{ + /// Create a new cache. + pub fn new_in() -> Self { + Self(Cache::new()) + } + + /// Returns a snapshot of the cache usage statistics. + pub fn stats(&self) -> CacheStats { + self.0.rule_cache.stats() + } +} + +impl Default for CacheArmhf

{ + fn default() -> Self { + Self::new_in() + } +} diff --git a/src/armhf/dwarf.rs b/src/armhf/dwarf.rs new file mode 100644 index 0000000..86ad2c2 --- /dev/null +++ b/src/armhf/dwarf.rs @@ -0,0 +1,39 @@ +use gimli::{ + Encoding, EvaluationStorage, Reader, Register, UnwindContextStorage, UnwindSection, + UnwindTableRow, +}; + +use super::{arch::ArchArmhf, unwind_rule::UnwindRuleArmhf, unwindregs::UnwindRegsArmhf}; + +use crate::unwind_result::UnwindResult; + +use crate::dwarf::{DwarfUnwindRegs, DwarfUnwinderError, DwarfUnwinding}; + +impl DwarfUnwindRegs for UnwindRegsArmhf { + fn get(&self, _: Register) -> Option { + None + } +} + +impl DwarfUnwinding for ArchArmhf { + fn unwind_frame( + _: &impl UnwindSection, + _: &UnwindTableRow, + _: Encoding, + _: &mut Self::UnwindRegs, + _: bool, + _: &mut F, + ) -> Result, DwarfUnwinderError> + where + F: FnMut(u64) -> Result, + R: Reader, + UCS: UnwindContextStorage, + ES: EvaluationStorage, + { + Err(DwarfUnwinderError::DidNotAdvance) + } + + fn rule_if_uncovered_by_fde() -> Self::UnwindRule { + UnwindRuleArmhf::NoOpIfFirstFrameOtherwiseFp + } +} diff --git a/src/armhf/instruction_analysis.rs b/src/armhf/instruction_analysis.rs new file mode 100644 index 0000000..c15dbc6 --- /dev/null +++ b/src/armhf/instruction_analysis.rs @@ -0,0 +1,12 @@ +use super::arch::ArchArmhf; +use crate::instruction_analysis::InstructionAnalysis; + +impl InstructionAnalysis for ArchArmhf { + fn rule_from_prologue_analysis(_: &[u8], _: usize) -> Option { + None + } + + fn rule_from_epilogue_analysis(_: &[u8], _: usize) -> Option { + None + } +} diff --git a/src/armhf/macho.rs b/src/armhf/macho.rs new file mode 100644 index 0000000..9b9159a --- /dev/null +++ b/src/armhf/macho.rs @@ -0,0 +1,21 @@ +use super::arch::ArchArmhf; +use super::unwind_rule::UnwindRuleArmhf; +use crate::macho::{CompactUnwindInfoUnwinderError, CompactUnwindInfoUnwinding, CuiUnwindResult}; +use macho_unwind_info::Function; + +impl CompactUnwindInfoUnwinding for ArchArmhf { + fn unwind_frame( + _: Function, + _: bool, + _: usize, + _: Option<&[u8]>, + ) -> Result, CompactUnwindInfoUnwinderError> { + Err(CompactUnwindInfoUnwinderError::ArmhfUnsupported) + } + + fn rule_for_stub_helper( + _: u32, + ) -> Result, CompactUnwindInfoUnwinderError> { + Ok(CuiUnwindResult::ExecRule(UnwindRuleArmhf::NoOp)) + } +} diff --git a/src/armhf/mod.rs b/src/armhf/mod.rs new file mode 100644 index 0000000..e1d5d3f --- /dev/null +++ b/src/armhf/mod.rs @@ -0,0 +1,15 @@ +mod arch; +mod cache; +mod dwarf; +mod instruction_analysis; +mod macho; +mod pe; +mod unwind_rule; +mod unwinder; +mod unwindregs; + +pub use arch::*; +pub use cache::*; +pub use unwind_rule::*; +pub use unwinder::*; +pub use unwindregs::*; diff --git a/src/armhf/pe.rs b/src/armhf/pe.rs new file mode 100644 index 0000000..08de17d --- /dev/null +++ b/src/armhf/pe.rs @@ -0,0 +1,19 @@ +use super::arch::ArchArmhf; +use crate::pe::{PeSections, PeUnwinderError, PeUnwinding}; +use crate::unwind_result::UnwindResult; + +impl PeUnwinding for ArchArmhf { + fn unwind_frame( + _sections: PeSections, + _address: u32, + _regs: &mut Self::UnwindRegs, + _is_first_frame: bool, + _read_stack: &mut F, + ) -> Result, PeUnwinderError> + where + F: FnMut(u64) -> Result, + D: core::ops::Deref, + { + Err(PeUnwinderError::ArmhfUnsupported) + } +} diff --git a/src/armhf/unwind_rule.rs b/src/armhf/unwind_rule.rs new file mode 100644 index 0000000..f18e8e9 --- /dev/null +++ b/src/armhf/unwind_rule.rs @@ -0,0 +1,103 @@ +use super::unwindregs::UnwindRegsArmhf; +use crate::error::Error; + +use crate::unwind_rule::UnwindRule; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum UnwindRuleArmhf { + /// (sp, fp, lr) = (sp, fp, lr) + /// Only possible for the first frame. Subsequent frames must get the + /// return address from somewhere other than the lr register to avoid + /// infinite loops. + NoOp, + /// (sp, fp, lr) = (undefined, *fp, *(fp + 4)) + /// This is only useful if the target program is compiled with frame pointers, + /// and never switches between thumb and arm mode. ARM frame pointers do not + /// typically form frame chains otherwise. + UseFramePointer, + NoOpIfFirstFrameOtherwiseFp, +} + +impl UnwindRule for UnwindRuleArmhf { + type UnwindRegs = UnwindRegsArmhf; + + fn rule_for_stub_functions() -> Self { + UnwindRuleArmhf::NoOp + } + fn rule_for_function_start() -> Self { + UnwindRuleArmhf::NoOp + } + fn fallback_rule() -> Self { + UnwindRuleArmhf::UseFramePointer + } + + fn exec( + self, + is_first_frame: bool, + regs: &mut UnwindRegsArmhf, + read_stack: &mut F, + ) -> Result, Error> + where + F: FnMut(u64) -> Result, + { + let lr = regs.lr(); + let sp = regs.sp(); + let fp = regs.fp(); + + let (new_lr, new_sp, new_fp) = match self { + UnwindRuleArmhf::NoOp => { + if !is_first_frame { + return Err(Error::DidNotAdvance); + } + (lr, sp, fp) + } + UnwindRuleArmhf::UseFramePointer => { + // Do a frame pointer stack walk. See this case in aarch64 for an explanation. + // *fp is the caller's frame pointer, and *(fp + 4) is the return address. + // sp is undefined. + let fp = regs.fp(); + let new_sp = fp.checked_add(0).ok_or(Error::IntegerOverflow)?; + let new_lr = read_stack(fp + 4).map_err(|_| Error::CouldNotReadStack(fp + 4))?; + let new_fp = read_stack(fp).map_err(|_| Error::CouldNotReadStack(fp))?; + if new_fp == 0 { + return Ok(None); + } + if new_fp <= fp || new_sp <= sp { + return Err(Error::FramepointerUnwindingMovedBackwards); + } + (new_lr, new_sp, new_fp) + } + + UnwindRuleArmhf::NoOpIfFirstFrameOtherwiseFp => { + if is_first_frame { + (lr, sp, fp) + } else { + let fp = regs.fp(); + let new_sp = fp.checked_add(0).ok_or(Error::IntegerOverflow)?; + let new_lr = + read_stack(fp + 4).map_err(|_| Error::CouldNotReadStack(fp + 4))?; + let new_fp = read_stack(fp).map_err(|_| Error::CouldNotReadStack(fp))?; + if new_fp == 0 { + return Ok(None); + } + if new_fp <= fp || new_sp <= sp { + return Err(Error::FramepointerUnwindingMovedBackwards); + } + (new_lr, new_sp, new_fp) + } + } + }; + let return_address = new_lr; + if return_address == 0 { + return Ok(None); + } + if !is_first_frame && new_sp == sp { + return Err(Error::DidNotAdvance); + } + regs.set_lr(new_lr); + regs.set_sp(new_sp); + regs.set_fp(new_fp); + + Ok(Some(return_address)) + } +} diff --git a/src/armhf/unwinder.rs b/src/armhf/unwinder.rs new file mode 100644 index 0000000..eabf2be --- /dev/null +++ b/src/armhf/unwinder.rs @@ -0,0 +1,66 @@ +use core::ops::Deref; + +use crate::{ + unwinder::UnwinderInternal, AllocationPolicy, Error, FrameAddress, MayAllocateDuringUnwind, + Module, Unwinder, +}; + +use super::{ArchArmhf, CacheArmhf, UnwindRegsArmhf}; + +/// The unwinder for the Armhf CPU architecture. Use the [`Unwinder`] trait for unwinding. +/// +/// Type arguments: +/// +/// - `D`: The type for unwind section data in the modules. See [`Module`]. +/// - `P`: The [`AllocationPolicy`]. +pub struct UnwinderArmhf(UnwinderInternal); + +impl Default for UnwinderArmhf { + fn default() -> Self { + Self::new() + } +} + +impl Clone for UnwinderArmhf { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl UnwinderArmhf { + /// Create an unwinder for a process. + pub fn new() -> Self { + Self(UnwinderInternal::new()) + } +} + +impl, P: AllocationPolicy> Unwinder for UnwinderArmhf { + type UnwindRegs = UnwindRegsArmhf; + type Cache = CacheArmhf

; + type Module = Module; + + fn add_module(&mut self, module: Module) { + self.0.add_module(module); + } + + fn remove_module(&mut self, module_address_range_start: u64) { + self.0.remove_module(module_address_range_start); + } + + fn max_known_code_address(&self) -> u64 { + self.0.max_known_code_address() + } + + fn unwind_frame( + &self, + address: FrameAddress, + regs: &mut UnwindRegsArmhf, + cache: &mut CacheArmhf

, + read_stack: &mut F, + ) -> Result, Error> + where + F: FnMut(u64) -> Result, + { + self.0.unwind_frame(address, regs, &mut cache.0, read_stack) + } +} diff --git a/src/armhf/unwindregs.rs b/src/armhf/unwindregs.rs new file mode 100644 index 0000000..4bd5f09 --- /dev/null +++ b/src/armhf/unwindregs.rs @@ -0,0 +1,66 @@ +use core::fmt::Debug; + +use crate::display_utils::HexNum; + +/// The registers used for unwinding on Armhf. We only need lr (x14), sp (x13), +/// and fp (x11 or x7). +#[derive(Clone, Copy, PartialEq, Eq)] +pub struct UnwindRegsArmhf { + lr: u64, + sp: u64, + fp: u64, +} + +impl UnwindRegsArmhf { + /// Create a set of unwind register values and do not apply any pointer + /// authentication stripping. + pub fn new(lr: u64, sp: u64, fp: u64) -> Self { + Self { lr, sp, fp } + } + + /// Get the stack pointer value. + #[inline(always)] + pub fn sp(&self) -> u64 { + self.sp + } + + /// Set the stack pointer value. + #[inline(always)] + pub fn set_sp(&mut self, sp: u64) { + self.sp = sp + } + + /// Get the frame pointer value (x29). + #[inline(always)] + pub fn fp(&self) -> u64 { + self.fp + } + + /// Set the frame pointer value (x29). + #[inline(always)] + pub fn set_fp(&mut self, fp: u64) { + self.fp = fp + } + + /// Get the lr register value. + #[inline(always)] + pub fn lr(&self) -> u64 { + self.lr + } + + /// Set the lr register value. + #[inline(always)] + pub fn set_lr(&mut self, lr: u64) { + self.lr = lr + } +} + +impl Debug for UnwindRegsArmhf { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("UnwindRegsArmhf") + .field("lr", &HexNum(self.lr)) + .field("sp", &HexNum(self.sp)) + .field("fp", &HexNum(self.fp)) + .finish() + } +} diff --git a/src/lib.rs b/src/lib.rs index 737235c..181933f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -134,6 +134,8 @@ mod unwinder; /// Types for unwinding on the aarch64 CPU architecture. pub mod aarch64; +/// Types for unwinding on the armhf CPU architecture. +pub mod armhf; /// Types for unwinding on the x86_64 CPU architecture. pub mod x86_64; @@ -164,3 +166,13 @@ pub type UnwindRegsNative = x86_64::UnwindRegsX86_64; /// The unwinder type for the native CPU architecture. #[cfg(target_arch = "x86_64")] pub type UnwinderNative = x86_64::UnwinderX86_64; + +/// The unwinder cache for the native CPU architecture. +#[cfg(target_arch = "arm")] +pub type CacheNative

= armhf::CacheArmhf

; +/// The unwind registers type for the native CPU architecture. +#[cfg(target_arch = "arm")] +pub type UnwindRegsNative = armhf::UnwindRegsArmhf; +/// The unwinder type for the native CPU architecture. +#[cfg(target_arch = "arm")] +pub type UnwinderNative = armhf::UnwinderArmhf; diff --git a/src/macho.rs b/src/macho.rs index ef60c6e..df84454 100644 --- a/src/macho.rs +++ b/src/macho.rs @@ -19,6 +19,7 @@ pub enum CompactUnwindInfoUnwinderError { StackSizeDoesNotFit, StubFunctionCannotBeCaller, InvalidFrameless, + ArmhfUnsupported, } impl core::fmt::Display for CompactUnwindInfoUnwinderError { @@ -37,6 +38,7 @@ impl core::fmt::Display for CompactUnwindInfoUnwinderError { Self::StackSizeDoesNotFit => write!(f, "Stack size does not fit into the rule representation"), Self::StubFunctionCannotBeCaller => write!(f, "A caller had its address in the __stubs section"), Self::InvalidFrameless => write!(f, "Encountered invalid unwind entry"), + Self::ArmhfUnsupported => write!(f, "Armhf is not supported"), } } } diff --git a/src/pe.rs b/src/pe.rs index bba6cf1..cc68096 100644 --- a/src/pe.rs +++ b/src/pe.rs @@ -8,6 +8,7 @@ pub enum PeUnwinderError { MissingStackData(Option), UnwindInfoParseError, Aarch64Unsupported, + ArmhfUnsupported, } impl core::fmt::Display for PeUnwinderError { @@ -28,6 +29,7 @@ impl core::fmt::Display for PeUnwinderError { } Self::UnwindInfoParseError => write!(f, "failed to parse UnwindInfo"), Self::Aarch64Unsupported => write!(f, "AArch64 is not yet supported"), + Self::ArmhfUnsupported => write!(f, "Armhf is not yet supported"), } } } diff --git a/tests/integration_tests/linux.rs b/tests/integration_tests/linux.rs index ac2ff28..6829932 100644 --- a/tests/integration_tests/linux.rs +++ b/tests/integration_tests/linux.rs @@ -1,6 +1,8 @@ +use std::io::Read; use std::path::Path; use framehop::aarch64::*; +use framehop::armhf::*; use framehop::x86_64::*; use framehop::FrameAddress; use framehop::Unwinder; @@ -534,3 +536,287 @@ fn test_root_func_aarch64_old_glibc() { ); assert_eq!(res, Ok(None)); } + +#[test] +fn fp_basic_unwind_a64() { + use framehop::MayAllocateDuringUnwind; + + let mut cache = CacheAarch64::<_>::new(); + let mut unwinder = UnwinderAarch64::, MayAllocateDuringUnwind>::new(); + + let mut stack_bytes = Vec::new(); + let mut file = std::fs::File::open( + &Path::new(env!("CARGO_MANIFEST_DIR")) + .join("fixtures/linux/aarch64/fp-basic-unwind-a64.stack.bin"), + ) + .unwrap(); + file.read_to_end(&mut stack_bytes).unwrap(); + assert!(stack_bytes.len() % 8 == 0); + let stack = stack_bytes + .chunks(8) + .map(|x| x.try_into().unwrap()) + .map(u64::from_le_bytes) + .collect::>(); + + let module = framehop::Module::new( + "basic-a64".to_string(), + 0x0000aaaaaaaa0000..0x0000aaaaaaac0048, + 0x0000aaaaaaaa0000, + framehop::ExplicitModuleSectionInfo { + ..Default::default() + }, + ); + unwinder.add_module(module); + + let pc = 0x0000aaaaaaaa0000 + 0x0000000000000858; + let lr = 0x0000aaaaaaaa0000 + 0x0000000000000858; + let sp = 0x1000000000000 - 0x0000000000000c50; + let fp = 0x1000000000000 - 0x0000000000000a50; + let mut read_stack = |addr| { + assert!(addr % 8 == 0); + assert!(addr <= 0x1000000000000); + let offset = (0x1000000000000 - addr) as usize / 8; + assert!(offset < stack.len()); + stack.get(stack.len() - offset).cloned().ok_or(()) + }; + + use framehop::Unwinder; + let mut iter = unwinder.iter_frames( + pc, + UnwindRegsAarch64::new(lr, sp, fp), + &mut cache, + &mut read_stack, + ); + + let mut frames = Vec::new(); + while let Ok(Some(frame)) = iter.next() { + frames.push(frame); + } + + assert_eq!( + frames, + vec![ + FrameAddress::from_instruction_pointer(0x0000aaaaaaaa0000 + 0x0000000000000858), + FrameAddress::from_return_address(0x0000aaaaaaaa0000 + 0x0000000000000840).unwrap(), + FrameAddress::from_return_address(0x0000aaaaaaaa0000 + 0x00000000000008e8).unwrap(), + FrameAddress::from_return_address(0x0000fffff7a60000 + 0x00000000000284c4).unwrap(), + FrameAddress::from_return_address(0x0000fffff7a60000 + 0x0000000000028598).unwrap(), + ] + ); +} + +#[test] +fn fp_basic_unwind_a64_jit() { + use framehop::MayAllocateDuringUnwind; + + let mut cache = CacheAarch64::<_>::new(); + let mut unwinder = UnwinderAarch64::, MayAllocateDuringUnwind>::new(); + + let mut stack_bytes = Vec::new(); + let mut file = std::fs::File::open( + &Path::new(env!("CARGO_MANIFEST_DIR")) + .join("fixtures/linux/aarch64/fp-basic-unwind-a64-jit.stack.bin"), + ) + .unwrap(); + file.read_to_end(&mut stack_bytes).unwrap(); + assert!(stack_bytes.len() % 8 == 0); + let stack = stack_bytes + .chunks(8) + .map(|x| x.try_into().unwrap()) + .map(u64::from_le_bytes) + .collect::>(); + + let module = framehop::Module::new( + "basic-a64".to_string(), + 0x0000aaaaaaaa0000..0x0000aaaaaaac0048, + 0x0000aaaaaaaa0000, + framehop::ExplicitModuleSectionInfo { + ..Default::default() + }, + ); + unwinder.add_module(module); + + let pc = 0x0000aaaaaaaa0000 + 0x0000000000000898; + let lr = 0xfffff7ff1000 + 0x000000000000030; + let sp = 0x1000000000000 - 0x0000000000000c50; + let fp = 0x1000000000000 - 0x0000000000000a50; + let mut read_stack = |addr| { + assert!(addr % 8 == 0); + assert!(addr <= 0x1000000000000); + let offset = (0x1000000000000 - addr) as usize / 8; + assert!(offset < stack.len()); + stack.get(stack.len() - offset).cloned().ok_or(()) + }; + + use framehop::Unwinder; + let mut iter = unwinder.iter_frames( + pc, + UnwindRegsAarch64::new(lr, sp, fp), + &mut cache, + &mut read_stack, + ); + + let mut frames = Vec::new(); + while let Ok(Some(frame)) = iter.next() { + frames.push(frame); + } + + println!( + "{:?}", + frames + .iter() + .map(|frame| format!("{:x}", frame.address())) + .collect::>() + ); + assert_eq!( + frames, + vec![ + FrameAddress::from_instruction_pointer(0x0000aaaaaaaa0000 + 0x0000000000000898), + FrameAddress::from_return_address(0xfffff7ff1000 + 0x0000000000000018).unwrap(), + FrameAddress::from_return_address(0x0000aaaaaaaa0000 + 0x0000000000000934).unwrap(), + FrameAddress::from_return_address(0x0000fffff7a60000 + 0x00000000000284c4).unwrap(), + FrameAddress::from_return_address(0x0000fffff7a60000 + 0x0000000000028598).unwrap(), + ] + ); +} + +#[test] +fn fp_basic_unwind_a32() { + use framehop::MayAllocateDuringUnwind; + + let mut cache = CacheArmhf::<_>::new(); + let mut unwinder = UnwinderArmhf::, MayAllocateDuringUnwind>::new(); + + let mut stack_bytes = Vec::new(); + let mut file = std::fs::File::open( + &Path::new(env!("CARGO_MANIFEST_DIR")) + .join("fixtures/linux/armhf/fp-basic-unwind-a32.stack.bin"), + ) + .unwrap(); + file.read_to_end(&mut stack_bytes).unwrap(); + assert!(stack_bytes.len() % 4 == 0); + let stack = stack_bytes + .chunks(4) + .map(|x| x.try_into().unwrap()) + .map(u32::from_le_bytes) + .collect::>(); + + let module = framehop::Module::new( + "basic-a32".to_string(), + 0x00400000..0x00400000, + 0x00400000, + framehop::ExplicitModuleSectionInfo { + ..Default::default() + }, + ); + unwinder.add_module(module); + + let pc = 0x00400000 + 0x0000060e; + let lr = 0x00400000 + 0x00000609; + let sp = 0xffff0000 - 0x00000938; + let fp = 0xffff0000 - 0x00000910; + let mut read_stack = |addr| { + assert!(addr % 4 == 0); + assert!(addr < 0xffff0000); + let offset = ((0xffff0000 - addr) / 4) as usize; + assert!(offset < stack.len()); + stack + .get(stack.len() - offset) + .cloned() + .ok_or(()) + .map(|x| x as u64) + }; + + use framehop::Unwinder; + let mut iter = unwinder.iter_frames( + pc, + UnwindRegsArmhf::new(lr, sp, fp), + &mut cache, + &mut read_stack, + ); + + let mut frames = Vec::new(); + while let Ok(Some(frame)) = iter.next() { + frames.push(frame); + } + + assert_eq!( + frames, + vec![ + FrameAddress::from_instruction_pointer(0x00400000 + 0x0000060e), + FrameAddress::from_return_address(0x00400000 + 0x000005f3).unwrap(), + FrameAddress::from_return_address(0x00400000 + 0x0000066d).unwrap(), + ] + ); +} + +#[test] +fn fp_basic_unwind_a32_jit() { + use framehop::MayAllocateDuringUnwind; + + let mut cache = CacheArmhf::<_>::new(); + let mut unwinder = UnwinderArmhf::, MayAllocateDuringUnwind>::new(); + + let mut stack_bytes = Vec::new(); + let mut file = std::fs::File::open( + &Path::new(env!("CARGO_MANIFEST_DIR")) + .join("fixtures/linux/armhf/fp-basic-unwind-a32-jit.stack.bin"), + ) + .unwrap(); + file.read_to_end(&mut stack_bytes).unwrap(); + assert!(stack_bytes.len() % 4 == 0); + let stack = stack_bytes + .chunks(4) + .map(|x| x.try_into().unwrap()) + .map(u32::from_le_bytes) + .collect::>(); + + let module = framehop::Module::new( + "basic-a32".to_string(), + 0x00400000..0x00400000, + 0x00400000, + framehop::ExplicitModuleSectionInfo { + ..Default::default() + }, + ); + unwinder.add_module(module); + + let pc = 0x00400000 + 0x0000060e; + let lr = 0xf7fcf000 + 0x0000002d; + let sp = 0xffff0000 - 0x00000930; + let fp = 0xffff0000 - 0x00000908; + let mut read_stack = |addr| { + assert!(addr % 4 == 0); + assert!(addr <= 0xffff0000); + let offset = (0xffff0000 - addr) as usize / 4; + assert!(offset < stack.len()); + stack + .get(stack.len() - offset) + .cloned() + .ok_or(()) + .map(|x| x as u64) + }; + + use framehop::Unwinder; + let mut iter = unwinder.iter_frames( + pc, + UnwindRegsArmhf::new(lr, sp, fp), + &mut cache, + &mut read_stack, + ); + + let mut frames = Vec::new(); + while let Ok(Some(frame)) = iter.next() { + println!("{:x}", frame.address()); + frames.push(frame); + } + + assert_eq!( + frames, + vec![ + FrameAddress::from_instruction_pointer(0x00400000 + 0x0000060e), + FrameAddress::from_return_address(0xf7fcf000 + 0x00000017).unwrap(), + FrameAddress::from_return_address(0x00400000 + 0x00000677).unwrap(), + ] + ); +}