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}