Common: Tidy up signal handlers

Move MacOS into its own file.
Fix assertion failure crash dumping.
This commit is contained in:
Stenzek 2024-05-29 20:30:53 +10:00 committed by Connor McLaughlin
parent f2e4a5e780
commit d48f527d6d
18 changed files with 521 additions and 511 deletions

View File

@ -69,7 +69,7 @@ else()
find_package(Wayland REQUIRED Egl) find_package(Wayland REQUIRED Egl)
endif() endif()
find_package(Libbacktrace) find_package(Libbacktrace REQUIRED)
find_package(PkgConfig REQUIRED) find_package(PkgConfig REQUIRED)
pkg_check_modules(DBUS REQUIRED dbus-1) pkg_check_modules(DBUS REQUIRED dbus-1)
endif() endif()

View File

@ -44,15 +44,6 @@ target_sources(common PRIVATE
emitter/movs.cpp emitter/movs.cpp
emitter/simd.cpp emitter/simd.cpp
emitter/x86emitter.cpp emitter/x86emitter.cpp
Darwin/DarwinThreads.cpp
Darwin/DarwinMisc.cpp
Darwin/DarwinSemaphore.cpp
Linux/LnxHostSys.cpp
Linux/LnxThreads.cpp
Linux/LnxMisc.cpp
Windows/WinThreads.cpp
Windows/WinHostSys.cpp
Windows/WinMisc.cpp
) )
# x86emitter headers # x86emitter headers
@ -124,34 +115,32 @@ target_sources(common PRIVATE
emitter/legacy_types.h emitter/legacy_types.h
emitter/x86emitter.h emitter/x86emitter.h
emitter/x86types.h emitter/x86types.h
Darwin/DarwinMisc.h
) )
set_source_files_properties(PrecompiledHeader.cpp PROPERTIES HEADER_FILE_ONLY TRUE)
if(USE_VTUNE)
target_link_libraries(common PUBLIC Vtune::Vtune)
endif()
if(WIN32) if(WIN32)
enable_language(ASM_MASM) enable_language(ASM_MASM)
target_sources(common PRIVATE FastJmp.asm)
target_link_libraries(common PUBLIC WIL::WIL winmm pathcch)
target_sources(common PRIVATE target_sources(common PRIVATE
CrashHandler.cpp
CrashHandler.h
FastJmp.asm FastJmp.asm
HTTPDownloaderWinHTTP.cpp HTTPDownloaderWinHTTP.cpp
HTTPDownloaderWinHTTP.h HTTPDownloaderWinHTTP.h
StackWalker.cpp StackWalker.cpp
StackWalker.h StackWalker.h
Windows/WinThreads.cpp
Windows/WinHostSys.cpp
Windows/WinMisc.cpp
) )
endif() target_link_libraries(common PUBLIC
WIL::WIL
if(APPLE) winmm
pathcch
)
elseif(APPLE)
target_sources(common PRIVATE target_sources(common PRIVATE
CocoaTools.mm CocoaTools.mm
CocoaTools.h CocoaTools.h
Darwin/DarwinThreads.cpp
Darwin/DarwinMisc.cpp
Darwin/DarwinMisc.h
) )
target_compile_options(common PRIVATE -fobjc-arc) target_compile_options(common PRIVATE -fobjc-arc)
target_link_options(common PRIVATE -fobjc-link-runtime) target_link_options(common PRIVATE -fobjc-link-runtime)
@ -159,15 +148,27 @@ if(APPLE)
"-framework Foundation" "-framework Foundation"
"-framework IOKit" "-framework IOKit"
) )
else()
target_sources(common PRIVATE
Linux/LnxHostSys.cpp
Linux/LnxThreads.cpp
Linux/LnxMisc.cpp
)
target_include_directories(common PRIVATE
${DBUS_INCLUDE_DIRS}
)
target_link_libraries(common PRIVATE
${DBUS_LINK_LIBRARIES}
libbacktrace::libbacktrace
X11::X11
X11::Xrandr
)
endif() endif()
if(UNIX AND NOT APPLE) set_source_files_properties(PrecompiledHeader.cpp PROPERTIES HEADER_FILE_ONLY TRUE)
target_include_directories(common PRIVATE ${DBUS_INCLUDE_DIRS})
target_link_libraries(common PRIVATE ${DBUS_LINK_LIBRARIES} X11::X11 X11::Xrandr) if(USE_VTUNE)
if(TARGET libbacktrace::libbacktrace) target_link_libraries(common PUBLIC Vtune::Vtune)
target_compile_definitions(common PRIVATE "HAS_LIBBACKTRACE=1")
target_link_libraries(common PRIVATE libbacktrace::libbacktrace)
endif()
endif() endif()
if (USE_GCC AND CMAKE_INTERPROCEDURAL_OPTIMIZATION) if (USE_GCC AND CMAKE_INTERPROCEDURAL_OPTIMIZATION)
@ -181,7 +182,9 @@ if(NOT WIN32)
HTTPDownloaderCurl.cpp HTTPDownloaderCurl.cpp
HTTPDownloaderCurl.h HTTPDownloaderCurl.h
) )
target_link_libraries(common PRIVATE CURL::libcurl) target_link_libraries(common PRIVATE
CURL::libcurl
)
endif() endif()
target_link_libraries(common PRIVATE target_link_libraries(common PRIVATE

View File

@ -56,23 +56,31 @@ static bool WriteMinidump(HMODULE hDbgHelp, HANDLE hFile, HANDLE hProcess, DWORD
PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
PMINIDUMP_CALLBACK_INFORMATION CallbackParam); PMINIDUMP_CALLBACK_INFORMATION CallbackParam);
PFNMINIDUMPWRITEDUMP minidump_write_dump = hDbgHelp ? PFNMINIDUMPWRITEDUMP minidump_write_dump =
reinterpret_cast<PFNMINIDUMPWRITEDUMP>(GetProcAddress(hDbgHelp, "MiniDumpWriteDump")) : hDbgHelp ? reinterpret_cast<PFNMINIDUMPWRITEDUMP>(GetProcAddress(hDbgHelp, "MiniDumpWriteDump")) : nullptr;
nullptr;
if (!minidump_write_dump) if (!minidump_write_dump)
return false; return false;
MINIDUMP_EXCEPTION_INFORMATION mei; MINIDUMP_EXCEPTION_INFORMATION mei = {};
PMINIDUMP_EXCEPTION_INFORMATION mei_ptr = nullptr;
if (exception) if (exception)
{ {
mei.ThreadId = thread_id; mei.ThreadId = thread_id;
mei.ExceptionPointers = exception; mei.ExceptionPointers = exception;
mei.ClientPointers = FALSE; mei.ClientPointers = FALSE;
mei_ptr = &mei; return minidump_write_dump(hProcess, process_id, hFile, type, &mei, nullptr, nullptr);
} }
return minidump_write_dump(hProcess, process_id, hFile, type, mei_ptr, nullptr, nullptr); __try
{
RaiseException(EXCEPTION_INVALID_HANDLE, 0, 0, nullptr);
}
__except (WriteMinidump(hDbgHelp, hFile, GetCurrentProcess(), GetCurrentProcessId(), GetCurrentThreadId(),
GetExceptionInformation(), type),
EXCEPTION_EXECUTE_HANDLER)
{
}
return true;
} }
static std::wstring s_write_directory; static std::wstring s_write_directory;
@ -168,7 +176,7 @@ void CrashHandler::WriteDumpForCaller()
WriteMinidumpAndCallstack(nullptr); WriteMinidumpAndCallstack(nullptr);
} }
#elif defined(HAS_LIBBACKTRACE) #elif !defined(__APPLE__)
#include "FileSystem.h" #include "FileSystem.h"
@ -194,16 +202,13 @@ namespace CrashHandler
static void FreeBuffer(BacktraceBuffer* buf); static void FreeBuffer(BacktraceBuffer* buf);
static void AppendToBuffer(BacktraceBuffer* buf, const char* format, ...); static void AppendToBuffer(BacktraceBuffer* buf, const char* format, ...);
static int BacktraceFullCallback(void* data, uintptr_t pc, const char* filename, int lineno, const char* function); static int BacktraceFullCallback(void* data, uintptr_t pc, const char* filename, int lineno, const char* function);
static void CallExistingSignalHandler(int signal, siginfo_t* siginfo, void* ctx); static void LogCallstack(int signal, const void* exception_pc);
static void CrashSignalHandler(int signal, siginfo_t* siginfo, void* ctx);
static std::recursive_mutex s_crash_mutex; static std::recursive_mutex s_crash_mutex;
static bool s_in_signal_handler = false; static bool s_in_signal_handler = false;
static backtrace_state* s_backtrace_state = nullptr; static backtrace_state* s_backtrace_state = nullptr;
static struct sigaction s_old_sigbus_action; } // namespace CrashHandler
static struct sigaction s_old_sigsegv_action;
}
const char* CrashHandler::GetSignalName(int signal_no) const char* CrashHandler::GetSignalName(int signal_no)
{ {
@ -214,7 +219,7 @@ const char* CrashHandler::GetSignalName(int signal_no)
case SIGSEGV: return "SIGSEGV"; case SIGSEGV: return "SIGSEGV";
case SIGBUS: return "SIGBUS"; case SIGBUS: return "SIGBUS";
default: return "UNKNOWN"; default: return "UNKNOWN";
// clang-format on // clang-format on
} }
} }
@ -253,7 +258,8 @@ void CrashHandler::AppendToBuffer(BacktraceBuffer* buf, const char* format, ...)
va_end(ap); va_end(ap);
} }
int CrashHandler::BacktraceFullCallback(void* data, uintptr_t pc, const char* filename, int lineno, const char* function) int CrashHandler::BacktraceFullCallback(void* data, uintptr_t pc, const char* filename, int lineno,
const char* function)
{ {
BacktraceBuffer* buf = static_cast<BacktraceBuffer*>(data); BacktraceBuffer* buf = static_cast<BacktraceBuffer*>(data);
AppendToBuffer(buf, " %016p", pc); AppendToBuffer(buf, " %016p", pc);
@ -261,34 +267,36 @@ int CrashHandler::BacktraceFullCallback(void* data, uintptr_t pc, const char* fi
AppendToBuffer(buf, " %s", function); AppendToBuffer(buf, " %s", function);
if (filename) if (filename)
AppendToBuffer(buf, " [%s:%d]", filename, lineno); AppendToBuffer(buf, " [%s:%d]", filename, lineno);
AppendToBuffer(buf, "\n"); AppendToBuffer(buf, "\n");
return 0; return 0;
} }
void CrashHandler::CallExistingSignalHandler(int signal, siginfo_t* siginfo, void* ctx) void CrashHandler::LogCallstack(int signal, const void* exception_pc)
{ {
const struct sigaction& sa = (signal == SIGBUS) ? s_old_sigbus_action : s_old_sigsegv_action; BacktraceBuffer buf;
if (sa.sa_flags & SA_SIGINFO) AllocateBuffer(&buf);
{ if (signal != 0 || exception_pc)
sa.sa_sigaction(signal, siginfo, ctx); AppendToBuffer(&buf, "*************** Unhandled %s at %p ***************\n", GetSignalName(signal), exception_pc);
} else
else if (sa.sa_handler == SIG_DFL) AppendToBuffer(&buf, "*******************************************************************\n");
{
// Re-raising the signal would just queue it, and since we'd restore the handler back to us, const int rc = backtrace_full(s_backtrace_state, 0, BacktraceFullCallback, nullptr, &buf);
// we'd end up right back here again. So just abort, because that's probably what it'd do anyway. if (rc != 0)
abort(); AppendToBuffer(&buf, " backtrace_full() failed: %d\n");
}
else if (sa.sa_handler != SIG_IGN) AppendToBuffer(&buf, "*******************************************************************\n");
{
sa.sa_handler(signal); if (buf.used > 0)
} write(STDERR_FILENO, buf.buffer, buf.used);
FreeBuffer(&buf);
} }
void CrashHandler::CrashSignalHandler(int signal, siginfo_t* siginfo, void* ctx) void CrashHandler::CrashSignalHandler(int signal, siginfo_t* siginfo, void* ctx)
{ {
std::unique_lock lock(s_crash_mutex); std::unique_lock lock(s_crash_mutex);
// If we crash somewhere in libbacktrace, don't bother trying again. // If we crash somewhere in libbacktrace, don't bother trying again.
if (!s_in_signal_handler) if (!s_in_signal_handler)
{ {
@ -304,27 +312,17 @@ void CrashHandler::CrashSignalHandler(int signal, siginfo_t* siginfo, void* ctx)
void* const exception_pc = nullptr; void* const exception_pc = nullptr;
#endif #endif
BacktraceBuffer buf; LogCallstack(signal, exception_pc);
AllocateBuffer(&buf);
AppendToBuffer(&buf, "*************** Unhandled %s at %p ***************\n", GetSignalName(signal), exception_pc);
const int rc = backtrace_full(s_backtrace_state, 0, BacktraceFullCallback, nullptr, &buf);
if (rc != 0)
AppendToBuffer(&buf, " backtrace_full() failed: %d\n");
AppendToBuffer(&buf, "*******************************************************************\n");
if (buf.used > 0)
write(STDERR_FILENO, buf.buffer, buf.used);
FreeBuffer(&buf);
s_in_signal_handler = false; s_in_signal_handler = false;
} }
// Chances are we're not going to have anything else to call, but just in case.
lock.unlock(); lock.unlock();
CallExistingSignalHandler(signal, siginfo, ctx);
// We can't continue from here. Just bail out and dump core.
std::fputs("Aborting application.\n", stderr);
std::fflush(stderr);
std::abort();
} }
bool CrashHandler::Install() bool CrashHandler::Install()
@ -333,15 +331,15 @@ bool CrashHandler::Install()
s_backtrace_state = backtrace_create_state(progpath.empty() ? nullptr : progpath.c_str(), 0, nullptr, nullptr); s_backtrace_state = backtrace_create_state(progpath.empty() ? nullptr : progpath.c_str(), 0, nullptr, nullptr);
if (!s_backtrace_state) if (!s_backtrace_state)
return false; return false;
struct sigaction sa; struct sigaction sa;
sigemptyset(&sa.sa_mask); sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_SIGINFO | SA_NODEFER; sa.sa_flags = SA_SIGINFO | SA_NODEFER;
sa.sa_sigaction = CrashSignalHandler; sa.sa_sigaction = CrashSignalHandler;
if (sigaction(SIGBUS, &sa, &s_old_sigbus_action) != 0) if (sigaction(SIGBUS, &sa, nullptr) != 0)
return false; return false;
if (sigaction(SIGSEGV, &sa, &s_old_sigsegv_action) != 0) if (sigaction(SIGSEGV, &sa, nullptr) != 0)
return false; return false;
return true; return true;
@ -353,6 +351,7 @@ void CrashHandler::SetWriteDirectory(std::string_view dump_directory)
void CrashHandler::WriteDumpForCaller() void CrashHandler::WriteDumpForCaller()
{ {
LogCallstack(0, nullptr);
} }
#else #else
@ -370,4 +369,12 @@ void CrashHandler::WriteDumpForCaller()
{ {
} }
void CrashHandler::CrashSignalHandler(int signal, siginfo_t* siginfo, void* ctx)
{
// We can't continue from here. Just bail out and dump core.
std::fputs("Aborting application.\n", stderr);
std::fflush(stderr);
std::abort();
}
#endif #endif

View File

@ -3,9 +3,18 @@
#include <string_view> #include <string_view>
#ifndef _WIN32
#include <csignal>
#endif
namespace CrashHandler namespace CrashHandler
{ {
bool Install(); bool Install();
void SetWriteDirectory(std::string_view dump_directory); void SetWriteDirectory(std::string_view dump_directory);
void WriteDumpForCaller(); void WriteDumpForCaller();
#ifndef _WIN32
// Allow crash handler to be invoked from a signal.
void CrashSignalHandler(int signal, siginfo_t* siginfo, void* ctx);
#endif
} // namespace CrashHandler } // namespace CrashHandler

View File

@ -1,11 +1,18 @@
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team // SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+ // SPDX-License-Identifier: LGPL-3.0+
#if defined(__APPLE__) #include "common/Assertions.h"
#include "common/BitUtils.h"
#include "common/Console.h"
#include "common/CrashHandler.h"
#include "common/Darwin/DarwinMisc.h" #include "common/Darwin/DarwinMisc.h"
#include "common/Error.h"
#include "common/Pcsx2Types.h"
#include "common/Threading.h"
#include "common/WindowInfo.h"
#include "common/HostSys.h" #include "common/HostSys.h"
#include <csignal>
#include <cstring> #include <cstring>
#include <cstdlib> #include <cstdlib>
#include <optional> #include <optional>
@ -17,17 +24,11 @@
#include <mach/mach_port.h> #include <mach/mach_port.h>
#include <mach/mach_time.h> #include <mach/mach_time.h>
#include <mach/mach_vm.h> #include <mach/mach_vm.h>
#include <mach/task.h>
#include <mach/vm_map.h> #include <mach/vm_map.h>
#include <mutex>
#include <IOKit/pwr_mgt/IOPMLib.h> #include <IOKit/pwr_mgt/IOPMLib.h>
#include "common/Assertions.h"
#include "common/BitUtils.h"
#include "common/Console.h"
#include "common/Pcsx2Types.h"
#include "common/HostSys.h"
#include "common/Threading.h"
#include "common/WindowInfo.h"
// Darwin (OSX) is a bit different from Linux when requesting properties of // Darwin (OSX) is a bit different from Linux when requesting properties of
// the OS because of its BSD/Mach heritage. Helpfully, most of this code // the OS because of its BSD/Mach heritage. Helpfully, most of this code
// should translate pretty well to other *BSD systems. (e.g.: the sysctl(3) // should translate pretty well to other *BSD systems. (e.g.: the sysctl(3)
@ -399,6 +400,116 @@ void HostSys::EndCodeWrite()
pthread_jit_write_protect_np(1); pthread_jit_write_protect_np(1);
} }
[[maybe_unused]] static bool IsStoreInstruction(const void* ptr)
{
u32 bits;
std::memcpy(&bits, ptr, 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 // _M_ARM64
namespace PageFaultHandler
{
static void SignalHandler(int sig, siginfo_t* info, void* ctx);
static std::recursive_mutex s_exception_handler_mutex;
static bool s_in_exception_handler = false;
static bool s_installed = false;
} // namespace PageFaultHandler
void PageFaultHandler::SignalHandler(int sig, siginfo_t* info, void* ctx)
{
#if defined(_M_X86)
void* const exception_address =
reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext->__es.__faultvaddr);
void* const exception_pc = reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext->__ss.__rip);
const bool is_write = (static_cast<ucontext_t*>(ctx)->uc_mcontext->__es.__err & 2) != 0;
#elif defined(_M_ARM64)
void* const exception_address = reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext->__es.__far);
void* const exception_pc = reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext->__ss.__pc);
const bool is_write = IsStoreInstruction(exception_pc);
#endif #endif
// Executing the handler concurrently from multiple threads wouldn't go down well.
s_exception_handler_mutex.lock();
// Prevent recursive exception filtering.
HandlerResult result = HandlerResult::ExecuteNextHandler;
if (!s_in_exception_handler)
{
s_in_exception_handler = true;
result = HandlePageFault(exception_pc, exception_address, is_write);
s_in_exception_handler = false;
}
s_exception_handler_mutex.unlock();
// Resumes execution right where we left off (re-executes instruction that caused the SIGSEGV).
if (result == HandlerResult::ContinueExecution)
return;
// We couldn't handle it. Pass it off to the crash dumper.
CrashHandler::CrashSignalHandler(sig, info, ctx);
}
bool PageFaultHandler::Install(Error* error)
{
std::unique_lock lock(s_exception_handler_mutex);
pxAssertRel(!s_installed, "Page fault handler has already been installed.");
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = SignalHandler;
// MacOS uses SIGBUS for memory permission violations, as well as SIGSEGV on ARM64.
if (sigaction(SIGBUS, &sa, nullptr) != 0)
{
Error::SetErrno(error, "sigaction() for SIGBUS failed: ", errno);
return false;
}
#ifdef _M_ARM64
if (sigaction(SIGSEGV, &sa, nullptr) != 0)
{
Error::SetErrno(error, "sigaction() for SIGSEGV failed: ", errno);
return false;
}
#endif #endif
// Allow us to ignore faults when running under lldb.
task_set_exception_ports(mach_task_self(), EXC_MASK_BAD_ACCESS, MACH_PORT_NULL, EXCEPTION_DEFAULT, 0);
s_installed = true;
return true;
}

View File

@ -1,71 +0,0 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#if defined(__APPLE__)
#include <cstdio>
#include <cassert> // assert
#include <pthread.h> // pthread_setcancelstate()
#include <sys/time.h> // gettimeofday()
#include <mach/mach.h>
#include <mach/task.h> // semaphore_create() and semaphore_destroy()
#include <mach/semaphore.h> // semaphore_*()
#include <mach/mach_error.h> // mach_error_string()
#include <mach/mach_time.h> // mach_absolute_time()
#include "common/Threading.h"
// --------------------------------------------------------------------------------------
// Semaphore Implementation for Darwin/OSX
//
// Sadly, Darwin/OSX needs its own implementation of Semaphores instead of
// relying on phtreads, because OSX unnamed semaphore (the best kind)
// support is very poor.
//
// This implementation makes use of Mach primitives instead. These are also
// what Grand Central Dispatch (GCD) is based on, as far as I understand:
// http://newosxbook.com/articles/GCD.html.
//
// --------------------------------------------------------------------------------------
static void MACH_CHECK(kern_return_t mach_retval)
{
if (mach_retval != KERN_SUCCESS)
{
fprintf(stderr, "mach error: %s", mach_error_string(mach_retval));
assert(mach_retval == KERN_SUCCESS);
}
}
Threading::KernelSemaphore::KernelSemaphore()
{
MACH_CHECK(semaphore_create(mach_task_self(), &m_sema, SYNC_POLICY_FIFO, 0));
}
Threading::KernelSemaphore::~KernelSemaphore()
{
MACH_CHECK(semaphore_destroy(mach_task_self(), m_sema));
}
void Threading::KernelSemaphore::Post()
{
MACH_CHECK(semaphore_signal(m_sema));
}
void Threading::KernelSemaphore::Wait()
{
MACH_CHECK(semaphore_wait(m_sema));
}
bool Threading::KernelSemaphore::TryWait()
{
mach_timespec_t time = {};
kern_return_t res = semaphore_timedwait(m_sema, time);
if (res == KERN_OPERATION_TIMED_OUT)
return false;
MACH_CHECK(res);
return true;
}
#endif

View File

@ -1,19 +1,24 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team // SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+ // SPDX-License-Identifier: LGPL-3.0+
#if defined(__APPLE__)
#include <sched.h>
#include <pthread.h>
#include <unistd.h>
#include <mach/mach_init.h>
#include <mach/thread_act.h>
#include <mach/mach_port.h>
#include "common/PrecompiledHeader.h"
#include "common/Threading.h" #include "common/Threading.h"
#include "common/Assertions.h" #include "common/Assertions.h"
#include <cstdio>
#include <cassert> // assert
#include <sched.h>
#include <sys/time.h> // gettimeofday()
#include <pthread.h>
#include <unistd.h>
#include <mach/mach.h>
#include <mach/mach_error.h> // mach_error_string()
#include <mach/mach_init.h>
#include <mach/mach_port.h>
#include <mach/mach_time.h> // mach_absolute_time()
#include <mach/semaphore.h> // semaphore_*()
#include <mach/task.h> // semaphore_create() and semaphore_destroy()
#include <mach/thread_act.h>
// Note: assuming multicore is safer because it forces the interlocked routines to use // Note: assuming multicore is safer because it forces the interlocked routines to use
// the LOCK prefix. The prefix works on single core CPUs fine (but is slow), but not // the LOCK prefix. The prefix works on single core CPUs fine (but is slow), but not
// having the LOCK prefix is very bad indeed. // having the LOCK prefix is very bad indeed.
@ -89,6 +94,58 @@ u64 Threading::GetThreadCpuTime()
return us; return us;
} }
// --------------------------------------------------------------------------------------
// Semaphore Implementation for Darwin/OSX
//
// Sadly, Darwin/OSX needs its own implementation of Semaphores instead of
// relying on phtreads, because OSX unnamed semaphore (the best kind)
// support is very poor.
//
// This implementation makes use of Mach primitives instead. These are also
// what Grand Central Dispatch (GCD) is based on, as far as I understand:
// http://newosxbook.com/articles/GCD.html.
//
// --------------------------------------------------------------------------------------
static void MACH_CHECK(kern_return_t mach_retval)
{
if (mach_retval != KERN_SUCCESS)
{
fprintf(stderr, "mach error: %s", mach_error_string(mach_retval));
assert(mach_retval == KERN_SUCCESS);
}
}
Threading::KernelSemaphore::KernelSemaphore()
{
MACH_CHECK(semaphore_create(mach_task_self(), &m_sema, SYNC_POLICY_FIFO, 0));
}
Threading::KernelSemaphore::~KernelSemaphore()
{
MACH_CHECK(semaphore_destroy(mach_task_self(), m_sema));
}
void Threading::KernelSemaphore::Post()
{
MACH_CHECK(semaphore_signal(m_sema));
}
void Threading::KernelSemaphore::Wait()
{
MACH_CHECK(semaphore_wait(m_sema));
}
bool Threading::KernelSemaphore::TryWait()
{
mach_timespec_t time = {};
kern_return_t res = semaphore_timedwait(m_sema, time);
if (res == KERN_OPERATION_TIMED_OUT)
return false;
MACH_CHECK(res);
return true;
}
Threading::ThreadHandle::ThreadHandle() = default; Threading::ThreadHandle::ThreadHandle() = default;
Threading::ThreadHandle::ThreadHandle(const ThreadHandle& handle) Threading::ThreadHandle::ThreadHandle(const ThreadHandle& handle)
@ -220,5 +277,3 @@ void Threading::SetNameOfCurrentThread(const char* name)
{ {
pthread_setname_np(name); pthread_setname_np(name);
} }
#endif

View File

@ -123,12 +123,18 @@ namespace HostSys
#else #else
void FlushInstructionCache(void* address, u32 size); void FlushInstructionCache(void* address, u32 size);
#endif #endif
} } // namespace HostSys
namespace PageFaultHandler namespace PageFaultHandler
{ {
bool HandlePageFault(uptr pc, uptr addr, bool is_write); enum class HandlerResult
bool Install(Error* error); {
ContinueExecution,
ExecuteNextHandler,
};
HandlerResult HandlePageFault(void* exception_pc, void* fault_address, bool is_write);
bool Install(Error* error = nullptr);
} // namespace PageFaultHandler } // namespace PageFaultHandler
class SharedMemoryMappingArea class SharedMemoryMappingArea

View File

@ -1,229 +1,30 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team // SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+ // SPDX-License-Identifier: LGPL-3.0+
#if defined(__APPLE__)
#define _XOPEN_SOURCE
#endif
#if !defined(_WIN32)
#include <cstdio>
#include <sys/mman.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <mutex>
#include "fmt/core.h"
#include "common/Assertions.h" #include "common/Assertions.h"
#include "common/BitUtils.h" #include "common/BitUtils.h"
#include "common/Console.h" #include "common/Console.h"
#include "common/CrashHandler.h"
#include "common/Error.h" #include "common/Error.h"
#include "common/HostSys.h" #include "common/HostSys.h"
// Apple uses the MAP_ANON define instead of MAP_ANONYMOUS, but they mean #include <cstdio>
// the same thing. #include <csignal>
#if defined(__APPLE__) && !defined(MAP_ANONYMOUS) #include <cerrno>
#define MAP_ANONYMOUS MAP_ANON #include <fcntl.h>
#endif #include <mutex>
#include <sys/mman.h>
#include <ucontext.h>
#include <unistd.h>
#include "fmt/core.h"
// FreeBSD does not have MAP_FIXED_NOREPLACE, but does have MAP_EXCL. // FreeBSD does not have MAP_FIXED_NOREPLACE, but does have MAP_EXCL.
// MAP_FIXED combined with MAP_EXCL behaves like MAP_FIXED_NOREPLACE. // MAP_FIXED combined with MAP_EXCL behaves like MAP_FIXED_NOREPLACE.
#if defined(__FreeBSD__) && !defined(MAP_FIXED_NOREPLACE) #if defined(__FreeBSD__) && !defined(MAP_FIXED_NOREPLACE)
#define MAP_FIXED_NOREPLACE MAP_FIXED | MAP_EXCL #define MAP_FIXED_NOREPLACE (MAP_FIXED | MAP_EXCL)
#endif #endif
#include <cerrno>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#ifndef __APPLE__
#include <ucontext.h>
#endif
static std::recursive_mutex s_exception_handler_mutex;
static bool s_in_exception_handler = false;
static bool s_exception_handler_installed = false;
#ifdef __APPLE__
#include <mach/task.h>
#include <mach/mach_init.h>
#include <mach/mach_port.h>
#endif
#if defined(__APPLE__) || defined(__aarch64__)
static struct sigaction s_old_sigbus_action;
#endif
#if !defined(__APPLE__) || defined(__aarch64__)
static struct sigaction s_old_sigsegv_action;
#endif
#ifdef __aarch64__
[[maybe_unused]] static bool IsStoreInstruction(uptr ptr)
{
u32 bits;
std::memcpy(&bits, reinterpret_cast<const void*>(ptr), 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__)
const struct sigaction& sa = (signal == SIGBUS) ? s_old_sigbus_action : s_old_sigsegv_action;
#elif defined(__APPLE__)
const struct sigaction& sa = s_old_sigbus_action;
#else
const struct sigaction& sa = s_old_sigsegv_action;
#endif
if (sa.sa_flags & SA_SIGINFO)
{
sa.sa_sigaction(signal, siginfo, ctx);
}
else if (sa.sa_handler == SIG_DFL)
{
// Re-raising the signal would just queue it, and since we'd restore the handler back to us,
// we'd end up right back here again. So just abort, because that's probably what it'd do anyway.
abort();
}
else if (sa.sa_handler != SIG_IGN)
{
sa.sa_handler(signal);
}
}
// Linux implementation of SIGSEGV handler. Bind it using sigaction().
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);
// Prevent recursive exception filtering.
if (s_in_exception_handler)
{
lock.unlock();
CallExistingSignalHandler(signal, info, ctx);
return;
}
#if defined(__linux__)
const uptr exception_address = reinterpret_cast<uptr>(info->si_addr);
#if defined(__x86_64__)
const uptr exception_pc = static_cast<uptr>(static_cast<ucontext_t*>(ctx)->uc_mcontext.gregs[REG_RIP]);
const bool is_write = (static_cast<ucontext_t*>(ctx)->uc_mcontext.gregs[REG_ERR] & 2) != 0;
#elif defined(__aarch64__)
const uptr exception_pc = static_cast<uptr>(static_cast<ucontext_t*>(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<uptr>(static_cast<ucontext_t*>(ctx)->uc_mcontext->__ss.__rip);
const uptr exception_address = static_cast<uptr>(static_cast<ucontext_t*>(ctx)->uc_mcontext->__es.__faultvaddr);
const bool is_write = (static_cast<ucontext_t*>(ctx)->uc_mcontext->__es.__err & 2) != 0;
#elif defined(__aarch64__)
const uptr exception_address = static_cast<uptr>(static_cast<ucontext_t*>(ctx)->uc_mcontext->__es.__far);
const uptr exception_pc = static_cast<uptr>(static_cast<ucontext_t*>(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<uptr>(static_cast<ucontext_t*>(ctx)->uc_mcontext.mc_addr);
const uptr exception_pc = static_cast<uptr>(static_cast<ucontext_t*>(ctx)->uc_mcontext.mc_rip);
const bool is_write = (static_cast<ucontext_t*>(ctx)->uc_mcontext.mc_err & 2) != 0;
#elif defined(__aarch64__)
const uptr exception_address = static_cast<uptr>(static_cast<ucontext_t*>(ctx)->uc_mcontext->__es.__far);
const uptr exception_pc = static_cast<uptr>(static_cast<ucontext_t*>(ctx)->uc_mcontext->__ss.__pc);
const bool is_write = IsStoreInstruction(exception_pc);
#endif
#endif
s_in_exception_handler = true;
const bool handled = PageFaultHandler::HandlePageFault(exception_pc, exception_address, is_write);
s_in_exception_handler = false;
// Resumes execution right where we left off (re-executes instruction that caused the SIGSEGV).
if (handled)
return;
// Call old signal handler, which will likely dump core.
lock.unlock();
CallExistingSignalHandler(signal, info, ctx);
}
bool PageFaultHandler::Install(Error* error)
{
std::unique_lock lock(s_exception_handler_mutex);
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__)
// 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__)
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;
}
#ifndef __APPLE__
static __ri uint LinuxProt(const PageProtectionMode& mode) static __ri uint LinuxProt(const PageProtectionMode& mode)
{ {
u32 lnxmode = 0; u32 lnxmode = 0;
@ -251,11 +52,6 @@ void* HostSys::Mmap(void* base, size_t size, const PageProtectionMode& mode)
if (base) if (base)
flags |= MAP_FIXED_NOREPLACE; flags |= MAP_FIXED_NOREPLACE;
#if defined(__APPLE__) && defined(_M_ARM64)
if (mode.CanExecute())
flags |= MAP_JIT;
#endif
void* res = mmap(base, size, prot, flags, -1, 0); void* res = mmap(base, size, prot, flags, -1, 0);
if (res == MAP_FAILED) if (res == MAP_FAILED)
return nullptr; return nullptr;
@ -338,15 +134,6 @@ void HostSys::UnmapSharedMemory(void* baseaddr, size_t size)
pxFailRel("Failed to unmap shared memory"); pxFailRel("Failed to unmap shared memory");
} }
#ifdef _M_ARM64
void HostSys::FlushInstructionCache(void* address, u32 size)
{
__builtin___clear_cache(reinterpret_cast<char*>(address), reinterpret_cast<char*>(address) + size);
}
#endif
SharedMemoryMappingArea::SharedMemoryMappingArea(u8* base_ptr, size_t size, size_t num_pages) SharedMemoryMappingArea::SharedMemoryMappingArea(u8* base_ptr, size_t size, size_t num_pages)
: m_base_ptr(base_ptr) : m_base_ptr(base_ptr)
, m_size(size) , m_size(size)
@ -400,6 +187,137 @@ bool SharedMemoryMappingArea::Unmap(void* map_base, size_t map_size)
return true; return true;
} }
#endif // __APPLE__ namespace PageFaultHandler
{
static std::recursive_mutex s_exception_handler_mutex;
static bool s_in_exception_handler = false;
static bool s_installed = false;
} // namespace PageFaultHandler
#ifdef _M_ARM64
void HostSys::FlushInstructionCache(void* address, u32 size)
{
__builtin___clear_cache(reinterpret_cast<char*>(address), reinterpret_cast<char*>(address) + size);
}
[[maybe_unused]] static bool IsStoreInstruction(const void* ptr)
{
u32 bits;
std::memcpy(&bits, ptr, 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 // _M_ARM64
namespace PageFaultHandler
{
static void SignalHandler(int sig, siginfo_t* info, void* ctx);
} // namespace PageFaultHandler
void PageFaultHandler::SignalHandler(int sig, siginfo_t* info, void* ctx)
{
#if defined(__linux__)
void* const exception_address = reinterpret_cast<void*>(info->si_addr);
#if defined(_M_X86)
void* const exception_pc = reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext.gregs[REG_RIP]);
const bool is_write = (static_cast<ucontext_t*>(ctx)->uc_mcontext.gregs[REG_ERR] & 2) != 0;
#elif defined(_M_ARM64)
void* const exception_pc = reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext.pc);
const bool is_write = IsStoreInstruction(exception_pc);
#endif
#elif defined(__FreeBSD__)
#if defined(_M_X86)
void* const exception_address = reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext.mc_addr);
void* const exception_pc = reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext.mc_rip);
const bool is_write = (static_cast<ucontext_t*>(ctx)->uc_mcontext.mc_err & 2) != 0;
#elif defined(_M_ARM64)
void* const exception_address = reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext->__es.__far);
void* const exception_pc = reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext->__ss.__pc);
const bool is_write = IsStoreInstruction(exception_pc);
#endif
#endif #endif
// Executing the handler concurrently from multiple threads wouldn't go down well.
s_exception_handler_mutex.lock();
// Prevent recursive exception filtering.
HandlerResult result = HandlerResult::ExecuteNextHandler;
if (!s_in_exception_handler)
{
s_in_exception_handler = true;
result = HandlePageFault(exception_pc, exception_address, is_write);
s_in_exception_handler = false;
}
s_exception_handler_mutex.unlock();
// Resumes execution right where we left off (re-executes instruction that caused the SIGSEGV).
if (result == HandlerResult::ContinueExecution)
return;
// We couldn't handle it. Pass it off to the crash dumper.
CrashHandler::CrashSignalHandler(sig, info, ctx);
}
bool PageFaultHandler::Install(Error* error)
{
std::unique_lock lock(s_exception_handler_mutex);
pxAssertRel(!s_installed, "Page fault handler has already been installed.");
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_SIGINFO | SA_NODEFER;
sa.sa_sigaction = SignalHandler;
if (sigaction(SIGSEGV, &sa, nullptr) != 0)
{
Error::SetErrno(error, "sigaction() for SIGSEGV failed: ", errno);
return false;
}
#ifdef _M_ARM64
// We can get SIGBUS on ARM64.
if (sigaction(SIGBUS, &sa, nullptr) != 0)
{
Error::SetErrno(error, "sigaction() for SIGBUS failed: ", errno);
return false;
}
#endif
s_installed = true;
return true;
}

View File

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team // SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+ // SPDX-License-Identifier: LGPL-3.0+
#if !defined(_WIN32) && !defined(__APPLE__)
#include "common/Pcsx2Types.h" #include "common/Pcsx2Types.h"
#include "common/Console.h" #include "common/Console.h"
#include "common/HostSys.h" #include "common/HostSys.h"
@ -215,5 +213,3 @@ void Threading::SleepUntil(u64 ticks)
ts.tv_nsec = static_cast<long>(ticks % 1000000000ULL); ts.tv_nsec = static_cast<long>(ticks % 1000000000ULL);
clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &ts, nullptr); clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &ts, nullptr);
} }
#endif

View File

@ -1,11 +1,13 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team // SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+ // SPDX-License-Identifier: LGPL-3.0+
#if !defined(_WIN32) && !defined(__APPLE__)
#ifndef _GNU_SOURCE #ifndef _GNU_SOURCE
#define _GNU_SOURCE #define _GNU_SOURCE
#endif #endif
#include "common/Threading.h"
#include "common/Assertions.h"
#include <memory> #include <memory>
#include <pthread.h> #include <pthread.h>
@ -20,20 +22,10 @@
#include <sys/syscall.h> #include <sys/syscall.h>
#define gettid() syscall(SYS_gettid) #define gettid() syscall(SYS_gettid)
#endif #endif
#else
#elif defined(__unix__)
#include <pthread_np.h> #include <pthread_np.h>
#endif #endif
#include "common/Threading.h"
#include "common/Assertions.h"
#if !defined(__unix__)
#pragma message("LnxThreads.cpp should only be compiled by projects or makefiles targeted at Linux/BSD distros.")
#else
// Note: assuming multicore is safer because it forces the interlocked routines to use // Note: assuming multicore is safer because it forces the interlocked routines to use
// the LOCK prefix. The prefix works on single core CPUs fine (but is slow), but not // the LOCK prefix. The prefix works on single core CPUs fine (but is slow), but not
// having the LOCK prefix is very bad indeed. // having the LOCK prefix is very bad indeed.
@ -354,6 +346,3 @@ void Threading::SetNameOfCurrentThread(const char* name)
pthread_set_name_np(pthread_self(), name); pthread_set_name_np(pthread_self(), name);
#endif #endif
} }
#endif
#endif

View File

@ -137,7 +137,7 @@ void Threading::WorkSema::Reset()
m_state = STATE_RUNNING_0; m_state = STATE_RUNNING_0;
} }
#if !defined(__APPLE__) // macOS implementations are in DarwinSemaphore #if !defined(__APPLE__) // macOS implementations are in DarwinThreads
Threading::KernelSemaphore::KernelSemaphore() Threading::KernelSemaphore::KernelSemaphore()
{ {

View File

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team // SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+ // SPDX-License-Identifier: LGPL-3.0+
#if defined(_WIN32)
#include "common/HostSys.h" #include "common/HostSys.h"
#include "common/AlignedMalloc.h" #include "common/AlignedMalloc.h"
#include "common/Assertions.h" #include "common/Assertions.h"
@ -17,57 +15,6 @@
#include <mutex> #include <mutex>
static std::recursive_mutex s_exception_handler_mutex;
static bool s_in_exception_handler = false;
static bool s_exception_handler_installed = false;
long __stdcall SysPageFaultExceptionFilter(EXCEPTION_POINTERS* eps)
{
// Executing the handler concurrently from multiple threads wouldn't go down well.
std::unique_lock lock(s_exception_handler_mutex);
// Prevent recursive exception filtering.
if (s_in_exception_handler)
return EXCEPTION_CONTINUE_SEARCH;
// Only interested in page faults.
if (eps->ExceptionRecord->ExceptionCode != EXCEPTION_ACCESS_VIOLATION)
return EXCEPTION_CONTINUE_SEARCH;
#if defined(_M_AMD64)
const uptr exception_pc = static_cast<uptr>(eps->ContextRecord->Rip);
#elif defined(_M_ARM64)
const uptr exception_pc = static_cast<uptr>(eps->ContextRecord->Pc);
#endif
const uptr exception_addr = static_cast<uptr>(eps->ExceptionRecord->ExceptionInformation[1]);
const bool is_write = (eps->ExceptionRecord->ExceptionInformation[0] == 1);
s_in_exception_handler = true;
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 PageFaultHandler::Install(Error* error)
{
std::unique_lock lock(s_exception_handler_mutex);
pxAssertRel(!s_exception_handler_installed, "Page fault handler has already been installed.");
PVOID handle = AddVectoredExceptionHandler(1, SysPageFaultExceptionFilter);
if (!handle)
{
Error::SetWin32(error, "AddVectoredExceptionHandler() failed: ", GetLastError());
return false;
}
s_exception_handler_installed = true;
return true;
}
static DWORD ConvertToWinApi(const PageProtectionMode& mode) static DWORD ConvertToWinApi(const PageProtectionMode& mode)
{ {
DWORD winmode = PAGE_NOACCESS; DWORD winmode = PAGE_NOACCESS;
@ -309,7 +256,7 @@ bool SharedMemoryMappingArea::Unmap(void* map_base, size_t map_size)
// combine placeholders before and the range we're unmapping, i.e. to the left // combine placeholders before and the range we're unmapping, i.e. to the left
if (!VirtualFreeEx(GetCurrentProcess(), OffsetPointer(left_it->first), if (!VirtualFreeEx(GetCurrentProcess(), OffsetPointer(left_it->first),
left_it->second - left_it->first, MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS)) left_it->second - left_it->first, MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS))
{ {
pxFail("Failed to coalesce placeholders left for unmap"); pxFail("Failed to coalesce placeholders left for unmap");
} }
@ -341,4 +288,60 @@ bool SharedMemoryMappingArea::Unmap(void* map_base, size_t map_size)
return true; return true;
} }
namespace PageFaultHandler
{
static LONG ExceptionHandler(PEXCEPTION_POINTERS exi);
static std::recursive_mutex s_exception_handler_mutex;
static bool s_in_exception_handler = false;
static bool s_installed = false;
} // namespace PageFaultHandler
LONG PageFaultHandler::ExceptionHandler(PEXCEPTION_POINTERS exi)
{
// Executing the handler concurrently from multiple threads wouldn't go down well.
std::unique_lock lock(s_exception_handler_mutex);
// Prevent recursive exception filtering.
if (s_in_exception_handler)
return EXCEPTION_CONTINUE_SEARCH;
// Only interested in page faults.
if (exi->ExceptionRecord->ExceptionCode != EXCEPTION_ACCESS_VIOLATION)
return EXCEPTION_CONTINUE_SEARCH;
#if defined(_M_X86)
void* const exception_pc = reinterpret_cast<void*>(exi->ContextRecord->Rip);
#elif defined(_M_ARM64)
void* const exception_pc = reinterpret_cast<void*>(exi->ContextRecord->Pc);
#else
void* const exception_pc = nullptr;
#endif #endif
void* const exception_address = reinterpret_cast<void*>(exi->ExceptionRecord->ExceptionInformation[1]);
const bool is_write = exi->ExceptionRecord->ExceptionInformation[0] == 1;
s_in_exception_handler = true;
const HandlerResult handled = HandlePageFault(exception_pc, exception_address, is_write);
s_in_exception_handler = false;
return (handled == HandlerResult::ContinueExecution) ? EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_CONTINUE_SEARCH;
}
bool PageFaultHandler::Install(Error* error)
{
std::unique_lock lock(s_exception_handler_mutex);
pxAssertRel(!s_installed, "Page fault handler has already been installed.");
PVOID handle = AddVectoredExceptionHandler(1, ExceptionHandler);
if (!handle)
{
Error::SetWin32(error, "AddVectoredExceptionHandler() failed: ", GetLastError());
return false;
}
s_installed = true;
return true;
}

View File

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team // SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+ // SPDX-License-Identifier: LGPL-3.0+
#if defined(_WIN32)
#include "common/FileSystem.h" #include "common/FileSystem.h"
#include "common/HostSys.h" #include "common/HostSys.h"
#include "common/RedtapeWindows.h" #include "common/RedtapeWindows.h"
@ -126,4 +124,3 @@ void Threading::SleepUntil(u64 ticks)
} }
} }
#endif

View File

@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team // SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+ // SPDX-License-Identifier: LGPL-3.0+
#if defined(_WIN32)
#include "common/Threading.h" #include "common/Threading.h"
#include "common/Assertions.h" #include "common/Assertions.h"
#include "common/RedtapeWindows.h" #include "common/RedtapeWindows.h"
@ -265,5 +263,3 @@ void Threading::SetNameOfCurrentThread(const char* name)
} }
#endif #endif
} }
#endif

View File

@ -74,9 +74,6 @@
<ClCompile Include="PrecompiledHeader.cpp"> <ClCompile Include="PrecompiledHeader.cpp">
<PrecompiledHeader>Create</PrecompiledHeader> <PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile> </ClCompile>
<ClCompile Include="Linux\LnxHostSys.cpp" />
<ClCompile Include="Linux\LnxMisc.cpp" />
<ClCompile Include="Linux\LnxThreads.cpp" />
<ClCompile Include="Windows\WinHostSys.cpp" /> <ClCompile Include="Windows\WinHostSys.cpp" />
<ClCompile Include="Windows\WinMisc.cpp" /> <ClCompile Include="Windows\WinMisc.cpp" />
<ClCompile Include="Windows\WinThreads.cpp" /> <ClCompile Include="Windows\WinThreads.cpp" />

View File

@ -28,15 +28,6 @@
<ClCompile Include="emitter\avx.cpp"> <ClCompile Include="emitter\avx.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="Linux\LnxHostSys.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Linux\LnxMisc.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Linux\LnxThreads.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="emitter\movs.cpp"> <ClCompile Include="emitter\movs.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>

View File

@ -1454,12 +1454,12 @@ static __fi void mmap_ClearCpuBlock(uint offset)
Cpu->Clear(m_PageProtectInfo[rampage].ReverseRamMap, __pagesize); Cpu->Clear(m_PageProtectInfo[rampage].ReverseRamMap, __pagesize);
} }
bool PageFaultHandler::HandlePageFault(uptr pc, uptr addr, bool is_write) PageFaultHandler::HandlerResult PageFaultHandler::HandlePageFault(void* exception_pc, void* fault_address, bool is_write)
{ {
pxAssert(eeMem); pxAssert(eeMem);
u32 vaddr; u32 vaddr;
if (CHECK_FASTMEM && vtlb_GetGuestAddress(addr, &vaddr)) if (CHECK_FASTMEM && vtlb_GetGuestAddress(reinterpret_cast<uptr>(fault_address), &vaddr))
{ {
// this was inside the fastmem area. check if it's a code page // 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); // fprintf(stderr, "Fault on fastmem %p vaddr %08X\n", info.addr, vaddr);
@ -1470,23 +1470,26 @@ bool PageFaultHandler::HandlePageFault(uptr pc, uptr addr, bool is_write)
{ {
// fprintf(stderr, "Not backpatching code write at %08X\n", vaddr); // fprintf(stderr, "Not backpatching code write at %08X\n", vaddr);
mmap_ClearCpuBlock(offset); mmap_ClearCpuBlock(offset);
return true; return HandlerResult::ContinueExecution;
} }
else else
{ {
// fprintf(stderr, "Trying backpatching vaddr %08X\n", vaddr); // fprintf(stderr, "Trying backpatching vaddr %08X\n", vaddr);
return vtlb_BackpatchLoadStore(pc, addr); return vtlb_BackpatchLoadStore(reinterpret_cast<uptr>(exception_pc),
reinterpret_cast<uptr>(fault_address)) ?
HandlerResult::ContinueExecution :
HandlerResult::ExecuteNextHandler;
} }
} }
else else
{ {
// get bad virtual address // get bad virtual address
uptr offset = addr - (uptr)eeMem->Main; uptr offset = reinterpret_cast<uptr>(fault_address) - reinterpret_cast<uptr>(eeMem->Main);
if (offset >= Ps2MemSize::ExposedRam) if (offset >= Ps2MemSize::ExposedRam)
return false; return HandlerResult::ExecuteNextHandler;
mmap_ClearCpuBlock(offset); mmap_ClearCpuBlock(offset);
return true; return HandlerResult::ContinueExecution;
} }
} }