Skip to content

Commit 10d2fb7

Browse files
committed
Add named AArch64 FP register view
1 parent 413d3c6 commit 10d2fb7

5 files changed

Lines changed: 120 additions & 18 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ iOS executable pages are code-signed. In normal (non-jailbreak) runtime environm
235235
- On `x86_64`, RIP-relative execute-original patch points are still unsupported (for example `lea` / `mov` using `[rip + disp]`).
236236
- `instrument_no_original(...)` skips original instruction unless callback changes control-flow register (`pc`/`rip`).
237237
- `HookContext` exposes FP/SIMD state in `ctx.fpregs`:
238-
- `aarch64`: `fpregs.v[0..31]` / `fpsr` / `fpcr`
238+
- `aarch64`: `fpregs.v[0..31]` or `fpregs.regs.named.v0..v31`, plus `fpsr` / `fpcr`
239239
- `x86_64`: x87 `st`, `xmm[0..15]`, `mxcsr`; Linux also maps `ymm` high halves when the signal frame carries AVX XSAVE state
240240
- `prepatched::*` APIs assume the address is already trap-patched offline (`brk`/`int3`) and do not write executable pages at runtime.
241241
- `prepatched::instrument(...)` needs original opcode metadata to execute original instruction (on `aarch64`, preload with `prepatched::cache_original_opcode(...)`).

examples/inline_hook_fpregs/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ extern "C" fn replace_in_callback(_address: u64, ctx: *mut HookContext) {
2525
unsafe {
2626
#[cfg(target_arch = "aarch64")]
2727
{
28-
(*ctx).fpregs.v[0] = u128::from_le_bytes(encode_i32x4([42, 43, 44, 45]));
28+
(*ctx).fpregs.regs.named.v0 = u128::from_le_bytes(encode_i32x4([42, 43, 44, 45]));
2929
}
3030

3131
#[cfg(all(target_arch = "x86_64", target_os = "macos"))]

src/context.rs

Lines changed: 98 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
//! This is also where FP/SIMD state is normalized across Darwin and Linux, including
1212
//! Linux x86_64 AVX high halves and Linux AArch64 FPSIMD extension records.
1313
14+
#[cfg(target_arch = "aarch64")]
15+
use std::ops::{Deref, DerefMut};
16+
1417
#[cfg(target_arch = "aarch64")]
1518
#[repr(C)]
1619
#[derive(Copy, Clone)]
@@ -59,12 +62,74 @@ pub union XRegisters {
5962
#[cfg(target_arch = "aarch64")]
6063
#[repr(C)]
6164
#[derive(Copy, Clone)]
62-
pub struct FpRegisters {
65+
pub struct VRegistersNamed {
66+
pub v0: u128,
67+
pub v1: u128,
68+
pub v2: u128,
69+
pub v3: u128,
70+
pub v4: u128,
71+
pub v5: u128,
72+
pub v6: u128,
73+
pub v7: u128,
74+
pub v8: u128,
75+
pub v9: u128,
76+
pub v10: u128,
77+
pub v11: u128,
78+
pub v12: u128,
79+
pub v13: u128,
80+
pub v14: u128,
81+
pub v15: u128,
82+
pub v16: u128,
83+
pub v17: u128,
84+
pub v18: u128,
85+
pub v19: u128,
86+
pub v20: u128,
87+
pub v21: u128,
88+
pub v22: u128,
89+
pub v23: u128,
90+
pub v24: u128,
91+
pub v25: u128,
92+
pub v26: u128,
93+
pub v27: u128,
94+
pub v28: u128,
95+
pub v29: u128,
96+
pub v30: u128,
97+
pub v31: u128,
98+
}
99+
100+
#[cfg(target_arch = "aarch64")]
101+
#[repr(C)]
102+
#[derive(Copy, Clone)]
103+
pub union VRegisters {
63104
pub v: [u128; 32],
105+
pub named: VRegistersNamed,
106+
}
107+
108+
#[cfg(target_arch = "aarch64")]
109+
#[repr(C)]
110+
#[derive(Copy, Clone)]
111+
pub struct FpRegisters {
112+
pub regs: VRegisters,
64113
pub fpsr: u32,
65114
pub fpcr: u32,
66115
}
67116

117+
#[cfg(target_arch = "aarch64")]
118+
impl Deref for FpRegisters {
119+
type Target = VRegisters;
120+
121+
fn deref(&self) -> &Self::Target {
122+
&self.regs
123+
}
124+
}
125+
126+
#[cfg(target_arch = "aarch64")]
127+
impl DerefMut for FpRegisters {
128+
fn deref_mut(&mut self) -> &mut Self::Target {
129+
&mut self.regs
130+
}
131+
}
132+
68133
#[cfg(target_arch = "aarch64")]
69134
#[repr(C)]
70135
#[derive(Copy, Clone)]
@@ -169,7 +234,11 @@ pub unsafe fn remap_ctx(uc: *mut libc::ucontext_t) -> *mut HookContext {
169234
// registers directly into `HookContext`.
170235
let mut fpregs = zeroed_fpregs();
171236
unsafe {
172-
std::ptr::copy_nonoverlapping(ns.__v.as_ptr().cast::<u128>(), fpregs.v.as_mut_ptr(), 32);
237+
std::ptr::copy_nonoverlapping(
238+
ns.__v.as_ptr().cast::<u128>(),
239+
fpregs.regs.v.as_mut_ptr(),
240+
32,
241+
);
173242
}
174243
fpregs.fpsr = ns.__fpsr;
175244
fpregs.fpcr = ns.__fpcr;
@@ -212,7 +281,7 @@ pub unsafe fn write_back_ctx(uc: *mut libc::ucontext_t, ctx: *mut HookContext) {
212281

213282
unsafe {
214283
std::ptr::copy_nonoverlapping(
215-
ctx.fpregs.v.as_ptr(),
284+
ctx.fpregs.regs.v.as_ptr(),
216285
ns.__v.as_mut_ptr().cast::<u128>(),
217286
32,
218287
);
@@ -349,7 +418,7 @@ pub unsafe fn remap_ctx(uc: *mut libc::ucontext_t) -> *mut HookContext {
349418
// `HookContext`; FP/SIMD state simply remains zero-initialized in that case.
350419
if let Some(fpsimd) = unsafe { linux_aarch64_fpsimd_context(mcontext) } {
351420
let fpsimd = unsafe { &*fpsimd };
352-
fpregs.v = fpsimd.vregs;
421+
fpregs.regs.v = fpsimd.vregs;
353422
fpregs.fpsr = fpsimd.fpsr;
354423
fpregs.fpcr = fpsimd.fpcr;
355424
}
@@ -389,7 +458,7 @@ pub unsafe fn write_back_ctx(uc: *mut libc::ucontext_t, ctx: *mut HookContext) {
389458

390459
if let Some(fpsimd) = unsafe { linux_aarch64_fpsimd_context(mcontext) } {
391460
let fpsimd = unsafe { &mut *fpsimd };
392-
fpsimd.vregs = ctx.fpregs.v;
461+
fpsimd.vregs = ctx.fpregs.regs.v;
393462
fpsimd.fpsr = ctx.fpregs.fpsr;
394463
fpsimd.fpcr = ctx.fpregs.fpcr;
395464
}
@@ -812,3 +881,27 @@ pub unsafe fn free_ctx(ctx: *mut HookContext) {
812881
let _ = unsafe { Box::from_raw(ctx) };
813882
}
814883
}
884+
885+
#[cfg(all(test, target_arch = "aarch64"))]
886+
mod tests {
887+
use super::{FpRegisters, VRegisters};
888+
889+
#[test]
890+
fn aarch64_fpreg_named_and_array_views_alias() {
891+
let mut fpregs = FpRegisters {
892+
regs: VRegisters { v: [0; 32] },
893+
fpsr: 0,
894+
fpcr: 0,
895+
};
896+
897+
unsafe {
898+
fpregs.named.v0 = 0x11;
899+
fpregs.named.v31 = 0x22;
900+
assert_eq!(fpregs.v[0], 0x11);
901+
assert_eq!(fpregs.v[31], 0x22);
902+
903+
fpregs.v[1] = 0x33;
904+
assert_eq!(fpregs.named.v1, 0x33);
905+
}
906+
}
907+
}

src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@ mod trampoline;
3737
#[cfg(all(target_arch = "x86_64", any(target_os = "linux", target_os = "macos")))]
3838
pub use context::{FpRegisters, HookContext, InstrumentCallback};
3939
#[cfg(target_arch = "aarch64")]
40-
pub use context::{FpRegisters, HookContext, InstrumentCallback, XRegisters, XRegistersNamed};
40+
pub use context::{
41+
FpRegisters, HookContext, InstrumentCallback, VRegisters, VRegistersNamed, XRegisters,
42+
XRegistersNamed,
43+
};
4144
pub use error::SigHookError;
4245

4346
/// Replaces one machine instruction at `address` with `new_opcode`.

src/replay.rs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -548,16 +548,20 @@ fn write_w(ctx: &mut HookContext, reg: u8, value: u32) {
548548
/// Storing the 32-bit value as `u128` naturally leaves the upper 96 bits cleared,
549549
/// which matches AArch64 scalar FP load semantics.
550550
fn write_s(ctx: &mut HookContext, reg: u8, value: u32) {
551-
if (reg as usize) < ctx.fpregs.v.len() {
552-
ctx.fpregs.v[reg as usize] = value as u128;
551+
if (reg as usize) < 32 {
552+
unsafe {
553+
ctx.fpregs.v[reg as usize] = value as u128;
554+
}
553555
}
554556
}
555557

556558
#[inline]
557559
/// Writes a scalar `dN` result into `vN`, clearing the high 64 bits.
558560
fn write_d(ctx: &mut HookContext, reg: u8, value: u64) {
559-
if (reg as usize) < ctx.fpregs.v.len() {
560-
ctx.fpregs.v[reg as usize] = value as u128;
561+
if (reg as usize) < 32 {
562+
unsafe {
563+
ctx.fpregs.v[reg as usize] = value as u128;
564+
}
561565
}
562566
}
563567

@@ -567,8 +571,10 @@ fn write_d(ctx: &mut HookContext, reg: u8, value: u64) {
567571
/// `HookContext` stores vector registers as `u128`, so replay converts the raw bytes
568572
/// into the host integer representation once at the boundary.
569573
fn write_q(ctx: &mut HookContext, reg: u8, value: [u8; 16]) {
570-
if (reg as usize) < ctx.fpregs.v.len() {
571-
ctx.fpregs.v[reg as usize] = u128::from_le_bytes(value);
574+
if (reg as usize) < 32 {
575+
unsafe {
576+
ctx.fpregs.v[reg as usize] = u128::from_le_bytes(value);
577+
}
572578
}
573579
}
574580

@@ -637,7 +643,7 @@ unsafe fn read_u128_bytes(address: u64) -> [u8; 16] {
637643
#[cfg(test)]
638644
mod tests {
639645
use super::{ReplayPlan, apply_replay_plan, decode_replay_plan};
640-
use crate::context::{FpRegisters, HookContext, XRegisters};
646+
use crate::context::{FpRegisters, HookContext, VRegisters, XRegisters};
641647

642648
fn empty_ctx() -> HookContext {
643649
HookContext {
@@ -647,7 +653,7 @@ mod tests {
647653
cpsr: 0,
648654
pad: 0,
649655
fpregs: FpRegisters {
650-
v: [0; 32],
656+
regs: VRegisters { v: [0; 32] },
651657
fpsr: 0,
652658
fpcr: 0,
653659
},
@@ -818,7 +824,7 @@ mod tests {
818824
0x5000,
819825
4,
820826
));
821-
assert_eq!(ctx.fpregs.v[6], double as u128);
827+
assert_eq!(unsafe { ctx.fpregs.v[6] }, double as u128);
822828
assert_eq!(ctx.pc, 0x5004);
823829

824830
assert!(apply_replay_plan(
@@ -830,7 +836,7 @@ mod tests {
830836
0x6000,
831837
4,
832838
));
833-
assert_eq!(ctx.fpregs.v[7], u128::from_le_bytes(quad));
839+
assert_eq!(unsafe { ctx.fpregs.v[7] }, u128::from_le_bytes(quad));
834840
assert_eq!(ctx.pc, 0x6004);
835841
}
836842

0 commit comments

Comments
 (0)