diff --git a/Utilities/Thread.cpp b/Utilities/Thread.cpp index 5b89868293..f4591ee19b 100644 --- a/Utilities/Thread.cpp +++ b/Utilities/Thread.cpp @@ -11,6 +11,10 @@ #include #include +#ifdef ARCH_ARM64 +#include "Emu/CPU/Backends/AArch64/AArch64Signal.h" +#endif + #ifdef _WIN32 #include #include @@ -1929,6 +1933,20 @@ static void signal_handler(int /*sig*/, siginfo_t* info, void* uct) noexcept #elif defined(ARCH_ARM64) const bool is_executing = uptr(info->si_addr) == uptr(RIP(context)); const u32 insn = is_executing ? 0 : *reinterpret_cast(RIP(context)); + +#ifdef __linux__ + // Current CPU state decoder is reverse-engineered from the linux kernel and may not work on other platforms. + const auto decoded_reason = aarch64::decode_fault_reason(context); + const bool is_writing = (decoded_reason == aarch64::fault_reason::data_write); + + if (decoded_reason != aarch64::fault_reason::data_write && + decoded_reason != aarch64::fault_reason::data_read) + { + // We don't expect other classes of exceptions during normal executions + sig_log.warning("Unexpected fault. Reason: %d", static_cast(decoded_reason)); + } + +#else const bool is_writing = (insn & 0xbfff0000) == 0x0c000000 || // STR , [, #] (store word with immediate offset) (insn & 0xbfe00000) == 0x0c800000 || // STP , , [, #] (store pair of registers with immediate offset) @@ -1941,8 +1959,9 @@ static void signal_handler(int /*sig*/, siginfo_t* info, void* uct) noexcept (insn & 0x3fe00000) == 0x3c800000 || // STUR , [, #] (store unprivileged register with immediate offset) (insn & 0x3fe00000) == 0x3ca00000 || // STR , [, #] (store SIMD/FP register with immediate offset) (insn & 0x3a400000) == 0x28000000 || // STP , , [, #] (store pair of registers with immediate offset) - (insn & 0xad000000) == 0xad000000 || // STP , , [, #] (store SIMD/FP 128-bit register pair with immediate offset) - (insn & 0xad000000) == 0xad000000; // STP , , [, #] (store SIMD/FP 64-bit register pair with immediate offset) + (insn & 0xbf000000) == 0xad000000 || // STP , , [, #] (store SIMD/FP 128-bit register pair with immediate offset) + (insn & 0xbf000000) == 0x6d000000; // STP , , [, #] (store SIMD/FP 64-bit register pair with immediate offset) +#endif #else #error "signal_handler not implemented" diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index e732e1cf53..8933c2a884 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -393,6 +393,7 @@ if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|aarch64") target_sources(rpcs3_emu PRIVATE CPU/Backends/AArch64/AArch64ASM.cpp CPU/Backends/AArch64/AArch64JIT.cpp + CPU/Backends/AArch64/AArch64Signal.cpp ) endif() diff --git a/rpcs3/Emu/CPU/Backends/AArch64/AArch64Signal.cpp b/rpcs3/Emu/CPU/Backends/AArch64/AArch64Signal.cpp new file mode 100644 index 0000000000..c93b9b2b43 --- /dev/null +++ b/rpcs3/Emu/CPU/Backends/AArch64/AArch64Signal.cpp @@ -0,0 +1,83 @@ +#include +#include "AArch64Signal.h" + +namespace aarch64 +{ + constexpr u32 ESR_CTX_MAGIC = 0x45535201; + + // Some of the EC codes we care about + enum class EL1_exception_class + { + undefined = 0, + + instr_abort_0 = 32, // PAGE_FAULT - Execute, change in EL + instr_abort_1 = 33, // PAGE_FAULT - Execute, same EL + data_abort_0 = 36, // PAGE_FAULT - Generic, causing change in EL (e.g kernel sig handler back to EL0) + data_abort_1 = 37, // PAGE_FAULT - Generic, no change in EL, e.g EL1 driver fault + + illegal_execution = 14, // BUS_ERROR + unaligned_pc = 34, // BUS_ERROR + unaligned_sp = 38, // BUS_ERROR + + breakpoint = 60, // BRK + }; + + const aarch64_esr_ctx* find_EL1_esr_context(const ucontext_t* ctx) + { + u32 offset = 0; + const auto& mctx = ctx->uc_mcontext; + + while ((offset + 4) < sizeof(mctx.__reserved)) + { + auto head = reinterpret_cast(&mctx.__reserved[offset]); + if (!head->magic) + { + // End of linked list + return nullptr; + } + + if (head->magic == ESR_CTX_MAGIC) + { + return reinterpret_cast(head); + } + + offset += head->size; + } + + return nullptr; + } + + fault_reason decode_fault_reason(const ucontext_t* uctx) + { + auto esr_ctx = find_EL1_esr_context(uctx); + if (!esr_ctx) { + return fault_reason::undefined; + } + + // We don't really care about most of the register fields, but we can check for a few things. + const auto exception_class = (esr_ctx->esr >> 26) & 0b111111; + switch (static_cast(exception_class)) + { + case EL1_exception_class::breakpoint: + // Debug break + return fault_reason::breakpoint; + case EL1_exception_class::illegal_execution: + case EL1_exception_class::unaligned_pc: + case EL1_exception_class::unaligned_sp: + return fault_reason::illegal_instruction; + case EL1_exception_class::instr_abort_0: + case EL1_exception_class::instr_abort_1: + return fault_reason::instruction_execute; + case EL1_exception_class::data_abort_0: + case EL1_exception_class::data_abort_1: + // Page fault + break; + default: + return fault_reason::undefined; + } + + // Check direction bit + const auto direction = (esr_ctx->esr >> 6u) & 1u; + return direction ? fault_reason::data_write : fault_reason::data_read; + } +} diff --git a/rpcs3/Emu/CPU/Backends/AArch64/AArch64Signal.h b/rpcs3/Emu/CPU/Backends/AArch64/AArch64Signal.h new file mode 100644 index 0000000000..5cf5b9288f --- /dev/null +++ b/rpcs3/Emu/CPU/Backends/AArch64/AArch64Signal.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include + +namespace aarch64 +{ + // Some renamed kernel definitions, we don't need to include kernel headers directly +#pragma pack(push, 1) + + struct aarch64_cpu_ctx_block + { + u32 magic; + u32 size; + }; + + struct aarch64_esr_ctx + { + aarch64_cpu_ctx_block head; + u64 esr; // Exception syndrome register + }; + +#pragma pack(pop) + + // Fault reason + enum class fault_reason + { + undefined = 0, + data_read, + data_write, + instruction_execute, + illegal_instruction, + breakpoint + }; + + fault_reason decode_fault_reason(const ucontext_t* uctx); +}