jasonmolenda created this revision. jasonmolenda added a reviewer: JDevlieghere. jasonmolenda added a project: LLDB. Herald added a subscriber: kristof.beyls. Herald added a project: All. jasonmolenda requested review of this revision. Herald added a subscriber: lldb-commits.
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 <https://reviews.llvm.org/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. Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D136620 Files: lldb/tools/debugserver/source/DNB.cpp lldb/tools/debugserver/source/DNB.h lldb/tools/debugserver/source/MacOSX/arm64/DNBArchImplARM64.cpp lldb/tools/debugserver/source/RNBRemote.cpp
Index: lldb/tools/debugserver/source/RNBRemote.cpp =================================================================== --- lldb/tools/debugserver/source/RNBRemote.cpp +++ lldb/tools/debugserver/source/RNBRemote.cpp @@ -4781,24 +4781,6 @@ return g_host_cputype != 0; } -static bool GetAddressingBits(uint32_t &addressing_bits) { - static uint32_t g_addressing_bits = 0; - static bool g_tried_addressing_bits_syscall = false; - if (g_tried_addressing_bits_syscall == false) { - size_t len = sizeof (uint32_t); - if (::sysctlbyname("machdep.virtual_address_size", - &g_addressing_bits, &len, NULL, 0) != 0) { - g_addressing_bits = 0; - } - } - g_tried_addressing_bits_syscall = true; - addressing_bits = g_addressing_bits; - if (addressing_bits > 0) - return true; - else - return false; -} - rnb_err_t RNBRemote::HandlePacket_qHostInfo(const char *p) { std::ostringstream strm; @@ -4812,7 +4794,7 @@ } uint32_t addressing_bits = 0; - if (GetAddressingBits(addressing_bits)) { + if (DNBGetAddressingBits(addressing_bits)) { strm << "addressing_bits:" << std::dec << addressing_bits << ';'; } Index: lldb/tools/debugserver/source/MacOSX/arm64/DNBArchImplARM64.cpp =================================================================== --- lldb/tools/debugserver/source/MacOSX/arm64/DNBArchImplARM64.cpp +++ lldb/tools/debugserver/source/MacOSX/arm64/DNBArchImplARM64.cpp @@ -94,11 +94,30 @@ uint32_t DNBArchMachARM64::GetCPUType() { return CPU_TYPE_ARM64; } +static uint64_t clear_pac_bits(uint64_t value) { + uint32_t addressing_bits = 0; + if (!DNBGetAddressingBits(addressing_bits) || addressing_bits == 0) + return value; + + uint64_t mask = ((1ULL << addressing_bits) - 1); + + // Normally PAC bit clearing needs to check b55 and either set the + // non-addressing bits, or clear them. But the register values we + // get from thread_get_state on an arm64e process don't follow this + // convention?, at least when there's been a PAC auth failure in + // the inferior. + // Userland processes are always in low memory, so this + // hardcoding b55 == 0 PAC stripping behavior here. + + return value & mask; // high bits cleared to 0 +} + uint64_t DNBArchMachARM64::GetPC(uint64_t failValue) { // Get program counter if (GetGPRState(false) == KERN_SUCCESS) -#if defined(__LP64__) - return arm_thread_state64_get_pc(m_state.context.gpr); +#if __has_feature(ptrauth_calls) && defined(__LP64__) + return clear_pac_bits( + reinterpret_cast<uint64_t>(m_state.context.gpr.__opaque_pc)); #else return m_state.context.gpr.__pc; #endif @@ -128,8 +147,9 @@ uint64_t DNBArchMachARM64::GetSP(uint64_t failValue) { // Get stack pointer if (GetGPRState(false) == KERN_SUCCESS) -#if defined(__LP64__) - return arm_thread_state64_get_sp(m_state.context.gpr); +#if __has_feature(ptrauth_calls) && defined(__LP64__) + return clear_pac_bits( + reinterpret_cast<uint64_t>(m_state.context.gpr.__opaque_sp)); #else return m_state.context.gpr.__sp; #endif @@ -150,16 +170,20 @@ if (DNBLogEnabledForAny(LOG_THREAD)) { uint64_t *x = &m_state.context.gpr.__x[0]; -#if defined(__LP64__) - uint64_t log_fp = arm_thread_state64_get_fp(m_state.context.gpr); - uint64_t log_lr = arm_thread_state64_get_lr(m_state.context.gpr); - uint64_t log_sp = arm_thread_state64_get_sp(m_state.context.gpr); - uint64_t log_pc = arm_thread_state64_get_pc(m_state.context.gpr); +#if __has_feature(ptrauth_calls) && defined(__LP64__) + uint64_t log_fp = clear_pac_bits( + reinterpret_cast<uint64_t>(m_state.context.gpr.__opaque_fp)); + uint64_t log_lr = clear_pac_bits( + reinterpret_cast<uint64_t>(m_state.context.gpr.__opaque_lr)); + uint64_t log_sp = clear_pac_bits( + reinterpret_cast<uint64_t>(m_state.context.gpr.__opaque_sp)); + uint64_t log_pc = clear_pac_bits( + reinterpret_cast<uint64_t>(m_state.context.gpr.__opaque_pc)); #else - uint64_t log_fp = m_state.context.gpr.__fp; - uint64_t log_lr = m_state.context.gpr.__lr; - uint64_t log_sp = m_state.context.gpr.__sp; - uint64_t log_pc = m_state.context.gpr.__pc; + uint64_t log_fp = m_state.context.gpr.__fp; + uint64_t log_lr = m_state.context.gpr.__lr; + uint64_t log_sp = m_state.context.gpr.__sp; + uint64_t log_pc = m_state.context.gpr.__pc; #endif DNBLogThreaded( "thread_get_state(0x%4.4x, %u, &gpr, %u) => 0x%8.8x (count = %u) regs" @@ -617,8 +641,9 @@ return err.Status(); } -#if defined(__LP64__) - uint64_t pc = arm_thread_state64_get_pc (m_state.context.gpr); +#if __has_feature(ptrauth_calls) && defined(__LP64__) + uint64_t pc = clear_pac_bits( + reinterpret_cast<uint64_t>(m_state.context.gpr.__opaque_pc)); #else uint64_t pc = m_state.context.gpr.__pc; #endif @@ -1992,20 +2017,10 @@ switch (set) { case e_regSetGPR: if (reg <= gpr_pc) { -#if defined(__LP64__) - if (reg == gpr_pc) - value->value.uint64 = arm_thread_state64_get_pc (m_state.context.gpr); - else if (reg == gpr_lr) - value->value.uint64 = arm_thread_state64_get_lr (m_state.context.gpr); - else if (reg == gpr_sp) - value->value.uint64 = arm_thread_state64_get_sp (m_state.context.gpr); - else if (reg == gpr_fp) - value->value.uint64 = arm_thread_state64_get_fp (m_state.context.gpr); + if (reg == gpr_pc || reg == gpr_lr || reg == gpr_sp || reg == gpr_fp) + value->value.uint64 = clear_pac_bits(m_state.context.gpr.__x[reg]); else - value->value.uint64 = m_state.context.gpr.__x[reg]; -#else - value->value.uint64 = m_state.context.gpr.__x[reg]; -#endif + value->value.uint64 = m_state.context.gpr.__x[reg]; return true; } else if (reg == gpr_cpsr) { value->value.uint32 = m_state.context.gpr.__cpsr; Index: lldb/tools/debugserver/source/DNB.h =================================================================== --- lldb/tools/debugserver/source/DNB.h +++ lldb/tools/debugserver/source/DNB.h @@ -245,4 +245,6 @@ /// \return true if debugserver is running in translation /// (is an x86_64 process on arm64) bool DNBDebugserverIsTranslated(); + +bool DNBGetAddressingBits(uint32_t &addressing_bits); #endif Index: lldb/tools/debugserver/source/DNB.cpp =================================================================== --- lldb/tools/debugserver/source/DNB.cpp +++ lldb/tools/debugserver/source/DNB.cpp @@ -1824,3 +1824,21 @@ return false; return ret == 1; } + +bool DNBGetAddressingBits(uint32_t &addressing_bits) { + static uint32_t g_addressing_bits = 0; + static bool g_tried_addressing_bits_syscall = false; + if (g_tried_addressing_bits_syscall == false) { + size_t len = sizeof(uint32_t); + if (::sysctlbyname("machdep.virtual_address_size", &g_addressing_bits, &len, + NULL, 0) != 0) { + g_addressing_bits = 0; + } + } + g_tried_addressing_bits_syscall = true; + addressing_bits = g_addressing_bits; + if (addressing_bits > 0) + return true; + else + return false; +}
_______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits