framehop/aarch64/
unwindregs.rs

1use core::fmt::Debug;
2
3use crate::display_utils::HexNum;
4
5/// The registers used for unwinding on Aarch64. We only need lr (x30), sp (x31),
6/// and fp (x29).
7///
8/// We also have a [`PtrAuthMask`] which allows stripping off the pointer authentication
9/// hash bits from the return address when unwinding through libraries which use pointer
10/// authentication, e.g. in system libraries on macOS.
11#[derive(Clone, Copy, PartialEq, Eq)]
12pub struct UnwindRegsAarch64 {
13    lr_mask: PtrAuthMask,
14    lr: u64,
15    sp: u64,
16    fp: u64,
17}
18
19/// Aarch64 CPUs support special instructions which interpret pointers as pair
20/// of the pointer address and an encrypted hash: The address is stored in the
21/// lower bits and the hash in the high bits. These are called "authenticated"
22/// pointers. Special instructions exist to verify pointers before dereferencing
23/// them.
24///
25/// Return address can be such authenticated pointers. To return to an
26/// authenticated return address, the "retab" instruction is used instead of
27/// the regular "ret" instruction.
28///
29/// Stack walkers need to strip the encrypted hash from return addresses because
30/// they need the raw code address.
31///
32/// On macOS arm64, system libraries compiled with the arm64e target use pointer
33/// pointer authentication for return addresses.
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
35pub struct PtrAuthMask(pub u64);
36
37impl PtrAuthMask {
38    /// Create a no-op mask which treats all bits of the pointer as address bits,
39    /// so no bits are stripped.
40    pub fn new_no_strip() -> Self {
41        Self(u64::MAX)
42    }
43
44    /// Create a mask for 24 bits hash + 40 bits pointer. This appears to be
45    /// what macOS arm64e uses. It is unclear whether we can rely on this or
46    /// whether it can change.
47    ///
48    /// On macOS arm64, this mask can be applied to both authenticated pointers
49    /// and to non-authenticated pointers without data loss; non-authenticated
50    /// don't appear to use the top 24 bits (they're always zero).
51    pub fn new_24_40() -> Self {
52        Self(u64::MAX >> 24)
53    }
54
55    /// Deduce a mask based on the highest known address. The leading zero bits
56    /// in this address will be reserved for the hash.
57    pub fn from_max_known_address(address: u64) -> Self {
58        Self(u64::MAX >> address.leading_zeros())
59    }
60
61    /// Apply the mask to the given pointer.
62    #[inline(always)]
63    pub fn strip_ptr_auth(&self, ptr: u64) -> u64 {
64        ptr & self.0
65    }
66}
67
68impl UnwindRegsAarch64 {
69    /// Create a set of unwind register values and do not apply any pointer
70    /// authentication stripping.
71    pub fn new(lr: u64, sp: u64, fp: u64) -> Self {
72        Self {
73            lr_mask: PtrAuthMask::new_no_strip(),
74            lr,
75            sp,
76            fp,
77        }
78    }
79
80    /// Create a set of unwind register values with the given mask for return
81    /// address pointer authentication stripping.
82    pub fn new_with_ptr_auth_mask(
83        code_ptr_auth_mask: PtrAuthMask,
84        lr: u64,
85        sp: u64,
86        fp: u64,
87    ) -> Self {
88        Self {
89            lr_mask: code_ptr_auth_mask,
90            lr: code_ptr_auth_mask.strip_ptr_auth(lr),
91            sp,
92            fp,
93        }
94    }
95
96    /// Get the [`PtrAuthMask`] which we apply to the `lr` value.
97    #[inline(always)]
98    pub fn lr_mask(&self) -> PtrAuthMask {
99        self.lr_mask
100    }
101
102    /// Get the stack pointer value.
103    #[inline(always)]
104    pub fn sp(&self) -> u64 {
105        self.sp
106    }
107
108    /// Set the stack pointer value.
109    #[inline(always)]
110    pub fn set_sp(&mut self, sp: u64) {
111        self.sp = sp
112    }
113
114    /// Get the frame pointer value (x29).
115    #[inline(always)]
116    pub fn fp(&self) -> u64 {
117        self.fp
118    }
119
120    /// Set the frame pointer value (x29).
121    #[inline(always)]
122    pub fn set_fp(&mut self, fp: u64) {
123        self.fp = fp
124    }
125
126    /// Get the lr register value.
127    #[inline(always)]
128    pub fn lr(&self) -> u64 {
129        self.lr
130    }
131
132    /// Set the lr register value.
133    #[inline(always)]
134    pub fn set_lr(&mut self, lr: u64) {
135        self.lr = self.lr_mask.strip_ptr_auth(lr)
136    }
137}
138
139impl Debug for UnwindRegsAarch64 {
140    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
141        f.debug_struct("UnwindRegsAarch64")
142            .field("lr", &HexNum(self.lr))
143            .field("sp", &HexNum(self.sp))
144            .field("fp", &HexNum(self.fp))
145            .finish()
146    }
147}
148
149#[cfg(test)]
150mod test {
151    use crate::aarch64::PtrAuthMask;
152
153    #[test]
154    fn test() {
155        assert_eq!(PtrAuthMask::new_24_40().0, u64::MAX >> 24);
156        assert_eq!(PtrAuthMask::new_24_40().0, (1 << 40) - 1);
157        assert_eq!(
158            PtrAuthMask::from_max_known_address(0x0000aaaab54f7000).0,
159            0x0000ffffffffffff
160        );
161        assert_eq!(
162            PtrAuthMask::from_max_known_address(0x0000ffffa3206000).0,
163            0x0000ffffffffffff
164        );
165        assert_eq!(
166            PtrAuthMask::from_max_known_address(0xffffffffc05a9000).0,
167            0xffffffffffffffff
168        );
169        assert_eq!(
170            PtrAuthMask::from_max_known_address(0x000055ba9f07e000).0,
171            0x00007fffffffffff
172        );
173        assert_eq!(
174            PtrAuthMask::from_max_known_address(0x00007f76b8019000).0,
175            0x00007fffffffffff
176        );
177        assert_eq!(
178            PtrAuthMask::from_max_known_address(0x000000022a3ccff7).0,
179            0x00000003ffffffff
180        );
181    }
182}