From 9752a037be1e6829a8f2d271332dff26facbbd4b Mon Sep 17 00:00:00 2001 From: Stenzek Date: Tue, 7 May 2024 01:37:56 +1000 Subject: [PATCH] HostSys: Simplify page fault handler installation And include whether it was a write or a read access. --- common/HostSys.h | 24 ++--- common/Linux/LnxHostSys.cpp | 172 ++++++++++++++++++++-------------- common/Windows/WinHostSys.cpp | 56 +++++------ pcsx2/vtlb.cpp | 18 ++-- 4 files changed, 139 insertions(+), 131 deletions(-) diff --git a/common/HostSys.h b/common/HostSys.h index 988429c49c..9fc0aaf908 100644 --- a/common/HostSys.h +++ b/common/HostSys.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team // SPDX-License-Identifier: LGPL-3.0+ #pragma once @@ -10,6 +10,8 @@ #include #include +class Error; + // -------------------------------------------------------------------------------------- // PageProtectionMode // -------------------------------------------------------------------------------------- @@ -83,14 +85,6 @@ static __fi PageProtectionMode PageAccess_Any() return PageProtectionMode().All(); } -struct PageFaultInfo -{ - uptr pc; - uptr addr; -}; - -using PageFaultHandler = bool(*)(const PageFaultInfo& info); - // -------------------------------------------------------------------------------------- // HostSys // -------------------------------------------------------------------------------------- @@ -111,12 +105,6 @@ namespace HostSys extern void* MapSharedMemory(void* handle, size_t offset, void* baseaddr, size_t size, const PageProtectionMode& mode); extern void UnmapSharedMemory(void* baseaddr, size_t size); - /// Installs the specified page fault handler. Only one handler can be active at once. - bool InstallPageFaultHandler(PageFaultHandler handler); - - /// Removes the page fault handler. handler is only specified to check against the active callback. - void RemovePageFaultHandler(PageFaultHandler handler); - /// JIT write protect for Apple Silicon. Needs to be called prior to writing to any RWX pages. #if !defined(__APPLE__) || !defined(_M_ARM64) // clang-format -off @@ -137,6 +125,12 @@ namespace HostSys #endif } +namespace PageFaultHandler +{ + bool HandlePageFault(uptr pc, uptr addr, bool is_write); + bool Install(Error* error); +} // namespace PageFaultHandler + class SharedMemoryMappingArea { public: diff --git a/common/Linux/LnxHostSys.cpp b/common/Linux/LnxHostSys.cpp index a01ce9b998..c10ebdc14d 100644 --- a/common/Linux/LnxHostSys.cpp +++ b/common/Linux/LnxHostSys.cpp @@ -17,9 +17,10 @@ #include "fmt/core.h" -#include "common/BitUtils.h" #include "common/Assertions.h" +#include "common/BitUtils.h" #include "common/Console.h" +#include "common/Error.h" #include "common/HostSys.h" // Apple uses the MAP_ANON define instead of MAP_ANONYMOUS, but they mean @@ -38,8 +39,8 @@ #endif static std::recursive_mutex s_exception_handler_mutex; -static PageFaultHandler s_exception_handler_callback; -static bool s_in_exception_handler; +static bool s_in_exception_handler = false; +static bool s_exception_handler_installed = true; #ifdef __APPLE__ #include @@ -54,6 +55,43 @@ static struct sigaction s_old_sigbus_action; static struct sigaction s_old_sigsegv_action; #endif +#ifdef __aarch64__ +[[maybe_unused]] static bool IsStoreInstruction(uptr ptr) +{ + u32 bits; + std::memcpy(&bits, reinterpret_cast(pc), sizeof(bits)); + + // Based on vixl's disassembler Instruction::IsStore(). + // if (Mask(LoadStoreAnyFMask) != LoadStoreAnyFixed) + if ((bits & 0x0a000000) != 0x08000000) + return false; + + // if (Mask(LoadStorePairAnyFMask) == LoadStorePairAnyFixed) + if ((bits & 0x3a000000) == 0x28000000) + { + // return Mask(LoadStorePairLBit) == 0 + return (bits & (1 << 22)) == 0; + } + + switch (bits & 0xC4C00000) + { + case 0x00000000: // STRB_w + case 0x40000000: // STRH_w + case 0x80000000: // STR_w + case 0xC0000000: // STR_x + case 0x04000000: // STR_b + case 0x44000000: // STR_h + case 0x84000000: // STR_s + case 0xC4000000: // STR_d + case 0x04800000: // STR_q + return true; + + default: + return false; + } +} +#endif + static void CallExistingSignalHandler(int signal, siginfo_t* siginfo, void* ctx) { #if defined(__aarch64__) @@ -81,7 +119,7 @@ static void CallExistingSignalHandler(int signal, siginfo_t* siginfo, void* ctx) } // Linux implementation of SIGSEGV handler. Bind it using sigaction(). -static void SysPageFaultSignalFilter(int signal, siginfo_t* siginfo, void* ctx) +static void SysPageFaultSignalFilter(int signal, siginfo_t* info, void* ctx) { // Executing the handler concurrently from multiple threads wouldn't go down well. std::unique_lock lock(s_exception_handler_mutex); @@ -90,37 +128,44 @@ static void SysPageFaultSignalFilter(int signal, siginfo_t* siginfo, void* ctx) if (s_in_exception_handler) { lock.unlock(); - CallExistingSignalHandler(signal, siginfo, ctx); + CallExistingSignalHandler(signal, info, ctx); return; } - // Note: Use of stdio functions isn't safe here. Avoid console logs, assertions, file logs, - // or just about anything else useful. However, that's really only a concern if the signal - // occurred within those functions. The logging which we do only happens when the exception - // occurred within JIT code. - -#if defined(__APPLE__) && defined(__x86_64__) - void* const exception_pc = reinterpret_cast(static_cast(ctx)->uc_mcontext->__ss.__rip); -#elif defined(__FreeBSD__) && defined(__x86_64__) - void* const exception_pc = reinterpret_cast(static_cast(ctx)->uc_mcontext.mc_rip); -#elif defined(__x86_64__) - void* const exception_pc = reinterpret_cast(static_cast(ctx)->uc_mcontext.gregs[REG_RIP]); -#elif defined(__aarch64__) - #ifndef __APPLE__ - void* const exception_pc = reinterpret_cast(static_cast(ctx)->uc_mcontext.pc); - #else - void* const exception_pc = reinterpret_cast(static_cast(ctx)->uc_mcontext->__ss.__pc); +#if defined(__linux__) + const uptr exception_address = reinterpret_cast(info->si_addr); + #if defined(__x86_64__) + const uptr exception_pc = static_cast(static_cast(ctx)->uc_mcontext.gregs[REG_RIP]); + const bool is_write = (static_cast(ctx)->uc_mcontext.gregs[REG_ERR] & 2) != 0; + #elif defined(__aarch64__) + const uptr exception_pc = static_cast(static_cast(ctx)->uc_mcontext.pc); + const bool is_write = IsStoreInstruction(exception_pc); + #endif +#elif defined(__APPLE__) + #if defined(__x86_64__) + const uptr exception_pc = static_cast(static_cast(ctx)->uc_mcontext->__ss.__rip); + const uptr exception_address = static_cast(static_cast(ctx)->uc_mcontext->__es.__faultvaddr); + const bool is_write = (static_cast(ctx)->uc_mcontext->__es.__err & 2) != 0; + #elif defined(__aarch64__) + const uptr exception_address = static_cast(static_cast(ctx)->uc_mcontext->__es.__far); + const uptr exception_pc = static_cast(static_cast(ctx)->uc_mcontext->__ss.__pc); + const bool is_write = IsStoreInstruction(exception_pc); + #endif +#elif defined(__FreeBSD__) + #if defined(__x86_64__) + const uptr exception_address = static_cast(static_cast(ctx)->uc_mcontext.mc_addr); + const uptr exception_pc = static_cast(static_cast(ctx)->uc_mcontext.mc_rip); + const bool is_write = (static_cast(ctx)->uc_mcontext.mc_err & 2) != 0; + #elif defined(__aarch64__) + const uptr exception_address = static_cast(static_cast(ctx)->uc_mcontext->__es.__far); + const uptr exception_pc = static_cast(static_cast(ctx)->uc_mcontext->__ss.__pc); + const bool is_write = IsStoreInstruction(exception_pc); #endif -#else - void* const exception_pc = nullptr; #endif - const PageFaultInfo pfi{ - reinterpret_cast(exception_pc), reinterpret_cast(siginfo->si_addr) & ~static_cast(__pagemask)}; - s_in_exception_handler = true; - const bool handled = s_exception_handler_callback(pfi); + const bool handled = PageFaultHandler::HandlePageFault(exception_pc, exception_address, is_write); s_in_exception_handler = false; @@ -130,60 +175,45 @@ static void SysPageFaultSignalFilter(int signal, siginfo_t* siginfo, void* ctx) // Call old signal handler, which will likely dump core. lock.unlock(); - CallExistingSignalHandler(signal, siginfo, ctx); + CallExistingSignalHandler(signal, info, ctx); } -bool HostSys::InstallPageFaultHandler(PageFaultHandler handler) +bool PageFaultHandler::Install(Error* error) { std::unique_lock lock(s_exception_handler_mutex); - pxAssertRel(!s_exception_handler_callback, "A page fault handler is already registered."); - if (!s_exception_handler_callback) - { - struct sigaction sa; - - sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_SIGINFO; - sa.sa_sigaction = SysPageFaultSignalFilter; -#ifdef __linux__ - // Don't block the signal from executing recursively, we want to fire the original handler. - sa.sa_flags |= SA_NODEFER; -#endif -#if defined(__APPLE__) || defined(__aarch64__) - // MacOS uses SIGBUS for memory permission violations, as well as SIGSEGV on ARM64. - if (sigaction(SIGBUS, &sa, &s_old_sigbus_action) != 0) - return false; -#endif -#if !defined(__APPLE__) || defined(__aarch64__) - if (sigaction(SIGSEGV, &sa, &s_old_sigsegv_action) != 0) - return false; -#endif -#if defined(__APPLE__) && defined(__aarch64__) - // Stops LLDB getting in a EXC_BAD_ACCESS loop when passing page faults to PCSX2. - task_set_exception_ports(mach_task_self(), EXC_MASK_BAD_ACCESS, MACH_PORT_NULL, EXCEPTION_DEFAULT, 0); -#endif - } - - s_exception_handler_callback = handler; - return true; -} - -void HostSys::RemovePageFaultHandler(PageFaultHandler handler) -{ - std::unique_lock lock(s_exception_handler_mutex); - pxAssertRel(!s_exception_handler_callback || s_exception_handler_callback == handler, - "Not removing the same handler previously registered."); - if (!s_exception_handler_callback) - return; - - s_exception_handler_callback = nullptr; + pxAssertRel(!s_exception_handler_installed, "Page fault handler has already been installed."); struct sigaction sa; + + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_SIGINFO; + sa.sa_sigaction = SysPageFaultSignalFilter; +#ifdef __linux__ + // Don't block the signal from executing recursively, we want to fire the original handler. + sa.sa_flags |= SA_NODEFER; +#endif #if defined(__APPLE__) || defined(__aarch64__) - sigaction(SIGBUS, &s_old_sigbus_action, &sa); + // MacOS uses SIGBUS for memory permission violations, as well as SIGSEGV on ARM64. + if (sigaction(SIGBUS, &sa, &s_old_sigbus_action) != 0) + { + Error::SetErrno(error, "sigaction() for SIGSEGV failed: ", errno); + return false; + } #endif #if !defined(__APPLE__) || defined(__aarch64__) - sigaction(SIGSEGV, &s_old_sigsegv_action, &sa); + if (sigaction(SIGSEGV, &sa, &s_old_sigsegv_action) != 0) + { + Error::SetErrno(error, "sigaction() for SIGBUS failed: ", errno); + return false; + } #endif +#if defined(__APPLE__) && defined(__aarch64__) + // Stops LLDB getting in a EXC_BAD_ACCESS loop when passing page faults to PCSX2. + task_set_exception_ports(mach_task_self(), EXC_MASK_BAD_ACCESS, MACH_PORT_NULL, EXCEPTION_DEFAULT, 0); +#endif + + s_exception_handler_installed = true; + return true; } static __ri uint LinuxProt(const PageProtectionMode& mode) diff --git a/common/Windows/WinHostSys.cpp b/common/Windows/WinHostSys.cpp index f66d3e2d0c..8278f7ebc3 100644 --- a/common/Windows/WinHostSys.cpp +++ b/common/Windows/WinHostSys.cpp @@ -1,15 +1,16 @@ -// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team +// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team // SPDX-License-Identifier: LGPL-3.0+ #if defined(_WIN32) -#include "common/BitUtils.h" -#include "common/RedtapeWindows.h" -#include "common/Console.h" #include "common/HostSys.h" -#include "common/StringUtil.h" #include "common/AlignedMalloc.h" #include "common/Assertions.h" +#include "common/BitUtils.h" +#include "common/Console.h" +#include "common/Error.h" +#include "common/RedtapeWindows.h" +#include "common/StringUtil.h" #include "fmt/core.h" #include "fmt/format.h" @@ -17,9 +18,8 @@ #include static std::recursive_mutex s_exception_handler_mutex; -static PageFaultHandler s_exception_handler_callback; -static void* s_exception_handler_handle; -static bool s_in_exception_handler; +static bool s_in_exception_handler = false; +static bool s_exception_handler_installed = true; long __stdcall SysPageFaultExceptionFilter(EXCEPTION_POINTERS* eps) { @@ -35,53 +35,39 @@ long __stdcall SysPageFaultExceptionFilter(EXCEPTION_POINTERS* eps) return EXCEPTION_CONTINUE_SEARCH; #if defined(_M_AMD64) - void* const exception_pc = reinterpret_cast(eps->ContextRecord->Rip); + const uptr exception_pc = static_cast(eps->ContextRecord->Rip); #elif defined(_M_ARM64) - void* const exception_pc = reinterpret_cast(eps->ContextRecord->Pc); -#else - void* const exception_pc = nullptr; + const uptr exception_pc = static_cast(eps->ContextRecord->Pc); #endif - const PageFaultInfo pfi{(uptr)exception_pc, (uptr)eps->ExceptionRecord->ExceptionInformation[1]}; + const uptr exception_addr = static_cast(eps->ExceptionRecord->ExceptionInformation[1]); + const bool is_write = (eps->ExceptionRecord->ExceptionInformation[0] == 1); s_in_exception_handler = true; - const bool handled = s_exception_handler_callback(pfi); + const bool handled = PageFaultHandler::HandlePageFault(exception_pc, exception_addr, is_write); s_in_exception_handler = false; return handled ? EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_CONTINUE_SEARCH; } -bool HostSys::InstallPageFaultHandler(PageFaultHandler handler) +bool PageFaultHandler::Install(Error* error) { std::unique_lock lock(s_exception_handler_mutex); - pxAssertRel(!s_exception_handler_callback, "A page fault handler is already registered."); - if (!s_exception_handler_handle) + pxAssertRel(!s_exception_handler_installed, "Page fault handler has already been installed."); + + PVOID handle = AddVectoredExceptionHandler(1, SysPageFaultExceptionFilter); + if (!handle) { - s_exception_handler_handle = AddVectoredExceptionHandler(TRUE, SysPageFaultExceptionFilter); - if (!s_exception_handler_handle) - return false; + Error::SetWin32(error, "AddVectoredExceptionHandler() failed: ", GetLastError()); + return false; } - s_exception_handler_callback = handler; + s_exception_handler_installed = true; return true; } -void HostSys::RemovePageFaultHandler(PageFaultHandler handler) -{ - std::unique_lock lock(s_exception_handler_mutex); - pxAssertRel(!s_exception_handler_callback || s_exception_handler_callback == handler, - "Not removing the same handler previously registered."); - s_exception_handler_callback = nullptr; - - if (s_exception_handler_handle) - { - RemoveVectoredExceptionHandler(s_exception_handler_handle); - s_exception_handler_handle = {}; - } -} - static DWORD ConvertToWinApi(const PageProtectionMode& mode) { DWORD winmode = PAGE_NOACCESS; diff --git a/pcsx2/vtlb.cpp b/pcsx2/vtlb.cpp index e5c5b4aad0..f255f49b45 100644 --- a/pcsx2/vtlb.cpp +++ b/pcsx2/vtlb.cpp @@ -25,6 +25,7 @@ #include "VMManager.h" #include "common/BitUtils.h" +#include "common/Error.h" #include "fmt/core.h" @@ -43,8 +44,6 @@ using namespace vtlb_private; namespace vtlb_private { alignas(64) MapData vtlbdata; - - static bool PageFaultHandler(const PageFaultInfo& info); } // namespace vtlb_private static vtlbHandler vtlbHandlerCount = 0; @@ -1304,9 +1303,10 @@ bool vtlb_Core_Alloc() DevCon.WriteLn(Color_StrongGreen, "Fastmem area: %p - %p", vtlbdata.fastmem_base, vtlbdata.fastmem_base + (FASTMEM_AREA_SIZE - 1)); - if (!HostSys::InstallPageFaultHandler(&vtlb_private::PageFaultHandler)) + Error error; + if (!PageFaultHandler::Install(&error)) { - Host::ReportErrorAsync("Error", "Failed to install page fault handler."); + Host::ReportErrorAsync("Failed to install page fault handler.", error.GetDescription()); return false; } @@ -1332,8 +1332,6 @@ void vtlb_Alloc_Ppmap() void vtlb_Core_Free() { - HostSys::RemovePageFaultHandler(&vtlb_private::PageFaultHandler); - vtlbdata.vmap = nullptr; vtlbdata.ppmap = nullptr; @@ -1456,12 +1454,12 @@ static __fi void mmap_ClearCpuBlock(uint offset) Cpu->Clear(m_PageProtectInfo[rampage].ReverseRamMap, __pagesize); } -bool vtlb_private::PageFaultHandler(const PageFaultInfo& info) +bool PageFaultHandler::HandlePageFault(uptr pc, uptr addr, bool is_write) { pxAssert(eeMem); u32 vaddr; - if (CHECK_FASTMEM && vtlb_GetGuestAddress(info.addr, &vaddr)) + if (CHECK_FASTMEM && vtlb_GetGuestAddress(addr, &vaddr)) { // this was inside the fastmem area. check if it's a code page // fprintf(stderr, "Fault on fastmem %p vaddr %08X\n", info.addr, vaddr); @@ -1477,13 +1475,13 @@ bool vtlb_private::PageFaultHandler(const PageFaultInfo& info) else { // fprintf(stderr, "Trying backpatching vaddr %08X\n", vaddr); - return vtlb_BackpatchLoadStore(info.pc, info.addr); + return vtlb_BackpatchLoadStore(pc, addr); } } else { // get bad virtual address - uptr offset = info.addr - (uptr)eeMem->Main; + uptr offset = addr - (uptr)eeMem->Main; if (offset >= Ps2MemSize::MainRam) return false;