When debugserver thread_get_state/thread_set_state's registers from an inferior, if the inferior is arm64e, debugserver must also be built arm64e, and debugserver passes the values through a series of macros provided by the kernel to authorize & clear auth bits off of the values that thread_get_state provides. When the inferior process has crashed -- jumping through an improperly signed function pointer, or jumped to invalid memory, the pc value will fail to authenticate in these kernel macros. On M2 era Mac hardware, this auth failure results in debugserver crashing.
We don't need to authenticate sp/pc/fp/lr, we only need to clear the auth bits from the address values. This patch replaces the kernel macro accesses after thread_get_state to do that. The macros like __darwin_arm_thread_state64_get_pc() are gated on __has_feature(ptrauth_calls) && defined(__LP64__), and in the case where we have ptrauth_calls, the register context structure in <mach/arm/_structs.h> are void * instead of uint64_t, so I needed to add a reinterpret cast of those values before clearing them.
It would probably be better to move my checks of __has_feature(ptrauth_calls) && defined(__LP64__) into this clear_pac_bits() function and call it unconditionally, instead of testing at all of the caller sites. (these two tests are distinguishing between arm64_32 v. arm64 v. arm64e)
In the case of thread_set_state, we will still use the kernel provided macros -- in this case, we are passing unsigned addresses and the signing will never fail.
With this patch, we still trigger the warning that the program has halted because of a PAC auth failure and show the most relevant pc value to explain it; no change in behavior there.
A more C++ and thread safe way of doing this is: