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