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 0000000..9e49cd6 Binary files /dev/null and b/fixtures/linux/aarch64/fp-basic-unwind-a64-jit.stack.bin differ 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 0000000..a7b6df0 Binary files /dev/null and b/fixtures/linux/aarch64/fp-basic-unwind-a64.stack.bin differ 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 0000000..2c9a606 Binary files /dev/null and b/fixtures/linux/armhf/fp-basic-unwind-a32-jit.stack.bin differ 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 0000000..aa10249 Binary files /dev/null and b/fixtures/linux/armhf/fp-basic-unwind-a32.stack.bin differ 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(), + ] + ); +}