mirror of https://github.com/PCSX2/pcsx2.git
Common: Tidy up signal handlers
Move MacOS into its own file. Fix assertion failure crash dumping.
This commit is contained in:
parent
f2e4a5e780
commit
d48f527d6d
|
@ -69,7 +69,7 @@ else()
|
|||
find_package(Wayland REQUIRED Egl)
|
||||
endif()
|
||||
|
||||
find_package(Libbacktrace)
|
||||
find_package(Libbacktrace REQUIRED)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(DBUS REQUIRED dbus-1)
|
||||
endif()
|
||||
|
|
|
@ -44,15 +44,6 @@ target_sources(common PRIVATE
|
|||
emitter/movs.cpp
|
||||
emitter/simd.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
|
||||
|
@ -124,34 +115,32 @@ target_sources(common PRIVATE
|
|||
emitter/legacy_types.h
|
||||
emitter/x86emitter.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)
|
||||
enable_language(ASM_MASM)
|
||||
target_sources(common PRIVATE FastJmp.asm)
|
||||
target_link_libraries(common PUBLIC WIL::WIL winmm pathcch)
|
||||
target_sources(common PRIVATE
|
||||
CrashHandler.cpp
|
||||
CrashHandler.h
|
||||
FastJmp.asm
|
||||
HTTPDownloaderWinHTTP.cpp
|
||||
HTTPDownloaderWinHTTP.h
|
||||
StackWalker.cpp
|
||||
StackWalker.h
|
||||
Windows/WinThreads.cpp
|
||||
Windows/WinHostSys.cpp
|
||||
Windows/WinMisc.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
target_link_libraries(common PUBLIC
|
||||
WIL::WIL
|
||||
winmm
|
||||
pathcch
|
||||
)
|
||||
elseif(APPLE)
|
||||
target_sources(common PRIVATE
|
||||
CocoaTools.mm
|
||||
CocoaTools.h
|
||||
Darwin/DarwinThreads.cpp
|
||||
Darwin/DarwinMisc.cpp
|
||||
Darwin/DarwinMisc.h
|
||||
)
|
||||
target_compile_options(common PRIVATE -fobjc-arc)
|
||||
target_link_options(common PRIVATE -fobjc-link-runtime)
|
||||
|
@ -159,15 +148,27 @@ if(APPLE)
|
|||
"-framework Foundation"
|
||||
"-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()
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
target_include_directories(common PRIVATE ${DBUS_INCLUDE_DIRS})
|
||||
target_link_libraries(common PRIVATE ${DBUS_LINK_LIBRARIES} X11::X11 X11::Xrandr)
|
||||
if(TARGET libbacktrace::libbacktrace)
|
||||
target_compile_definitions(common PRIVATE "HAS_LIBBACKTRACE=1")
|
||||
target_link_libraries(common PRIVATE libbacktrace::libbacktrace)
|
||||
endif()
|
||||
set_source_files_properties(PrecompiledHeader.cpp PROPERTIES HEADER_FILE_ONLY TRUE)
|
||||
|
||||
if(USE_VTUNE)
|
||||
target_link_libraries(common PUBLIC Vtune::Vtune)
|
||||
endif()
|
||||
|
||||
if (USE_GCC AND CMAKE_INTERPROCEDURAL_OPTIMIZATION)
|
||||
|
@ -181,7 +182,9 @@ if(NOT WIN32)
|
|||
HTTPDownloaderCurl.cpp
|
||||
HTTPDownloaderCurl.h
|
||||
)
|
||||
target_link_libraries(common PRIVATE CURL::libcurl)
|
||||
target_link_libraries(common PRIVATE
|
||||
CURL::libcurl
|
||||
)
|
||||
endif()
|
||||
|
||||
target_link_libraries(common PRIVATE
|
||||
|
|
|
@ -56,23 +56,31 @@ static bool WriteMinidump(HMODULE hDbgHelp, HANDLE hFile, HANDLE hProcess, DWORD
|
|||
PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
|
||||
PMINIDUMP_CALLBACK_INFORMATION CallbackParam);
|
||||
|
||||
PFNMINIDUMPWRITEDUMP minidump_write_dump = hDbgHelp ?
|
||||
reinterpret_cast<PFNMINIDUMPWRITEDUMP>(GetProcAddress(hDbgHelp, "MiniDumpWriteDump")) :
|
||||
nullptr;
|
||||
PFNMINIDUMPWRITEDUMP minidump_write_dump =
|
||||
hDbgHelp ? reinterpret_cast<PFNMINIDUMPWRITEDUMP>(GetProcAddress(hDbgHelp, "MiniDumpWriteDump")) : nullptr;
|
||||
if (!minidump_write_dump)
|
||||
return false;
|
||||
|
||||
MINIDUMP_EXCEPTION_INFORMATION mei;
|
||||
PMINIDUMP_EXCEPTION_INFORMATION mei_ptr = nullptr;
|
||||
MINIDUMP_EXCEPTION_INFORMATION mei = {};
|
||||
if (exception)
|
||||
{
|
||||
mei.ThreadId = thread_id;
|
||||
mei.ExceptionPointers = exception;
|
||||
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;
|
||||
|
@ -168,7 +176,7 @@ void CrashHandler::WriteDumpForCaller()
|
|||
WriteMinidumpAndCallstack(nullptr);
|
||||
}
|
||||
|
||||
#elif defined(HAS_LIBBACKTRACE)
|
||||
#elif !defined(__APPLE__)
|
||||
|
||||
#include "FileSystem.h"
|
||||
|
||||
|
@ -194,16 +202,13 @@ namespace CrashHandler
|
|||
static void FreeBuffer(BacktraceBuffer* buf);
|
||||
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 void CallExistingSignalHandler(int signal, siginfo_t* siginfo, void* ctx);
|
||||
static void CrashSignalHandler(int signal, siginfo_t* siginfo, void* ctx);
|
||||
static void LogCallstack(int signal, const void* exception_pc);
|
||||
|
||||
static std::recursive_mutex s_crash_mutex;
|
||||
static bool s_in_signal_handler = false;
|
||||
|
||||
static backtrace_state* s_backtrace_state = nullptr;
|
||||
static struct sigaction s_old_sigbus_action;
|
||||
static struct sigaction s_old_sigsegv_action;
|
||||
}
|
||||
} // namespace CrashHandler
|
||||
|
||||
const char* CrashHandler::GetSignalName(int signal_no)
|
||||
{
|
||||
|
@ -253,7 +258,8 @@ void CrashHandler::AppendToBuffer(BacktraceBuffer* buf, const char* format, ...)
|
|||
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);
|
||||
AppendToBuffer(buf, " %016p", pc);
|
||||
|
@ -266,23 +272,25 @@ int CrashHandler::BacktraceFullCallback(void* data, uintptr_t pc, const char* fi
|
|||
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;
|
||||
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);
|
||||
}
|
||||
BacktraceBuffer buf;
|
||||
AllocateBuffer(&buf);
|
||||
if (signal != 0 || exception_pc)
|
||||
AppendToBuffer(&buf, "*************** Unhandled %s at %p ***************\n", GetSignalName(signal), exception_pc);
|
||||
else
|
||||
AppendToBuffer(&buf, "*******************************************************************\n");
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void CrashHandler::CrashSignalHandler(int signal, siginfo_t* siginfo, void* ctx)
|
||||
|
@ -304,27 +312,17 @@ void CrashHandler::CrashSignalHandler(int signal, siginfo_t* siginfo, void* ctx)
|
|||
void* const exception_pc = nullptr;
|
||||
#endif
|
||||
|
||||
BacktraceBuffer buf;
|
||||
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);
|
||||
LogCallstack(signal, exception_pc);
|
||||
|
||||
s_in_signal_handler = false;
|
||||
}
|
||||
|
||||
// Chances are we're not going to have anything else to call, but just in case.
|
||||
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()
|
||||
|
@ -339,9 +337,9 @@ bool CrashHandler::Install()
|
|||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_flags = SA_SIGINFO | SA_NODEFER;
|
||||
sa.sa_sigaction = CrashSignalHandler;
|
||||
if (sigaction(SIGBUS, &sa, &s_old_sigbus_action) != 0)
|
||||
if (sigaction(SIGBUS, &sa, nullptr) != 0)
|
||||
return false;
|
||||
if (sigaction(SIGSEGV, &sa, &s_old_sigsegv_action) != 0)
|
||||
if (sigaction(SIGSEGV, &sa, nullptr) != 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
@ -353,6 +351,7 @@ void CrashHandler::SetWriteDirectory(std::string_view dump_directory)
|
|||
|
||||
void CrashHandler::WriteDumpForCaller()
|
||||
{
|
||||
LogCallstack(0, nullptr);
|
||||
}
|
||||
|
||||
#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
|
|
@ -3,9 +3,18 @@
|
|||
|
||||
#include <string_view>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <csignal>
|
||||
#endif
|
||||
|
||||
namespace CrashHandler
|
||||
{
|
||||
bool Install();
|
||||
void SetWriteDirectory(std::string_view dump_directory);
|
||||
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
|
||||
|
|
|
@ -1,11 +1,18 @@
|
|||
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
|
||||
// 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/Error.h"
|
||||
#include "common/Pcsx2Types.h"
|
||||
#include "common/Threading.h"
|
||||
#include "common/WindowInfo.h"
|
||||
#include "common/HostSys.h"
|
||||
|
||||
#include <csignal>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <optional>
|
||||
|
@ -17,17 +24,11 @@
|
|||
#include <mach/mach_port.h>
|
||||
#include <mach/mach_time.h>
|
||||
#include <mach/mach_vm.h>
|
||||
#include <mach/task.h>
|
||||
#include <mach/vm_map.h>
|
||||
#include <mutex>
|
||||
#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
|
||||
// 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)
|
||||
|
@ -399,6 +400,116 @@ void HostSys::EndCodeWrite()
|
|||
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
|
||||
|
||||
// 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
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
@ -1,19 +1,24 @@
|
|||
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
|
||||
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
|
||||
// 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/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
|
||||
// the LOCK prefix. The prefix works on single core CPUs fine (but is slow), but not
|
||||
// having the LOCK prefix is very bad indeed.
|
||||
|
@ -89,6 +94,58 @@ u64 Threading::GetThreadCpuTime()
|
|||
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(const ThreadHandle& handle)
|
||||
|
@ -220,5 +277,3 @@ void Threading::SetNameOfCurrentThread(const char* name)
|
|||
{
|
||||
pthread_setname_np(name);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -123,12 +123,18 @@ namespace HostSys
|
|||
#else
|
||||
void FlushInstructionCache(void* address, u32 size);
|
||||
#endif
|
||||
}
|
||||
} // namespace HostSys
|
||||
|
||||
namespace PageFaultHandler
|
||||
{
|
||||
bool HandlePageFault(uptr pc, uptr addr, bool is_write);
|
||||
bool Install(Error* error);
|
||||
enum class HandlerResult
|
||||
{
|
||||
ContinueExecution,
|
||||
ExecuteNextHandler,
|
||||
};
|
||||
|
||||
HandlerResult HandlePageFault(void* exception_pc, void* fault_address, bool is_write);
|
||||
bool Install(Error* error = nullptr);
|
||||
} // namespace PageFaultHandler
|
||||
|
||||
class SharedMemoryMappingArea
|
||||
|
|
|
@ -1,229 +1,30 @@
|
|||
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
|
||||
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
|
||||
// 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/BitUtils.h"
|
||||
#include "common/Console.h"
|
||||
#include "common/CrashHandler.h"
|
||||
#include "common/Error.h"
|
||||
#include "common/HostSys.h"
|
||||
|
||||
// Apple uses the MAP_ANON define instead of MAP_ANONYMOUS, but they mean
|
||||
// the same thing.
|
||||
#if defined(__APPLE__) && !defined(MAP_ANONYMOUS)
|
||||
#define MAP_ANONYMOUS MAP_ANON
|
||||
#endif
|
||||
#include <cstdio>
|
||||
#include <csignal>
|
||||
#include <cerrno>
|
||||
#include <fcntl.h>
|
||||
#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.
|
||||
// MAP_FIXED combined with MAP_EXCL behaves like 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
|
||||
|
||||
#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)
|
||||
{
|
||||
u32 lnxmode = 0;
|
||||
|
@ -251,11 +52,6 @@ void* HostSys::Mmap(void* base, size_t size, const PageProtectionMode& mode)
|
|||
if (base)
|
||||
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);
|
||||
if (res == MAP_FAILED)
|
||||
return nullptr;
|
||||
|
@ -338,15 +134,6 @@ void HostSys::UnmapSharedMemory(void* baseaddr, size_t size)
|
|||
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)
|
||||
: m_base_ptr(base_ptr)
|
||||
, m_size(size)
|
||||
|
@ -400,6 +187,137 @@ bool SharedMemoryMappingArea::Unmap(void* map_base, size_t map_size)
|
|||
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
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
|
||||
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: LGPL-3.0+
|
||||
|
||||
#if !defined(_WIN32) && !defined(__APPLE__)
|
||||
|
||||
#include "common/Pcsx2Types.h"
|
||||
#include "common/Console.h"
|
||||
#include "common/HostSys.h"
|
||||
|
@ -215,5 +213,3 @@ void Threading::SleepUntil(u64 ticks)
|
|||
ts.tv_nsec = static_cast<long>(ticks % 1000000000ULL);
|
||||
clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &ts, nullptr);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
|
||||
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: LGPL-3.0+
|
||||
|
||||
#if !defined(_WIN32) && !defined(__APPLE__)
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
|
||||
#include "common/Threading.h"
|
||||
#include "common/Assertions.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <pthread.h>
|
||||
|
@ -20,20 +22,10 @@
|
|||
#include <sys/syscall.h>
|
||||
#define gettid() syscall(SYS_gettid)
|
||||
#endif
|
||||
|
||||
#elif defined(__unix__)
|
||||
#else
|
||||
#include <pthread_np.h>
|
||||
#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
|
||||
// the LOCK prefix. The prefix works on single core CPUs fine (but is slow), but not
|
||||
// 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);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
|
|
@ -137,7 +137,7 @@ void Threading::WorkSema::Reset()
|
|||
m_state = STATE_RUNNING_0;
|
||||
}
|
||||
|
||||
#if !defined(__APPLE__) // macOS implementations are in DarwinSemaphore
|
||||
#if !defined(__APPLE__) // macOS implementations are in DarwinThreads
|
||||
|
||||
Threading::KernelSemaphore::KernelSemaphore()
|
||||
{
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: LGPL-3.0+
|
||||
|
||||
#if defined(_WIN32)
|
||||
|
||||
#include "common/HostSys.h"
|
||||
#include "common/AlignedMalloc.h"
|
||||
#include "common/Assertions.h"
|
||||
|
@ -17,57 +15,6 @@
|
|||
|
||||
#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)
|
||||
{
|
||||
DWORD winmode = PAGE_NOACCESS;
|
||||
|
@ -341,4 +288,60 @@ bool SharedMemoryMappingArea::Unmap(void* map_base, size_t map_size)
|
|||
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
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
// 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/FileSystem.h"
|
||||
#include "common/HostSys.h"
|
||||
#include "common/RedtapeWindows.h"
|
||||
|
@ -126,4 +124,3 @@ void Threading::SleepUntil(u64 ticks)
|
|||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
|
||||
// SPDX-License-Identifier: LGPL-3.0+
|
||||
|
||||
#if defined(_WIN32)
|
||||
|
||||
#include "common/Threading.h"
|
||||
#include "common/Assertions.h"
|
||||
#include "common/RedtapeWindows.h"
|
||||
|
@ -265,5 +263,3 @@ void Threading::SetNameOfCurrentThread(const char* name)
|
|||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -74,9 +74,6 @@
|
|||
<ClCompile Include="PrecompiledHeader.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Linux\LnxHostSys.cpp" />
|
||||
<ClCompile Include="Linux\LnxMisc.cpp" />
|
||||
<ClCompile Include="Linux\LnxThreads.cpp" />
|
||||
<ClCompile Include="Windows\WinHostSys.cpp" />
|
||||
<ClCompile Include="Windows\WinMisc.cpp" />
|
||||
<ClCompile Include="Windows\WinThreads.cpp" />
|
||||
|
|
|
@ -28,15 +28,6 @@
|
|||
<ClCompile Include="emitter\avx.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</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">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
|
|
|
@ -1454,12 +1454,12 @@ static __fi void mmap_ClearCpuBlock(uint offset)
|
|||
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);
|
||||
|
||||
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
|
||||
// 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);
|
||||
mmap_ClearCpuBlock(offset);
|
||||
return true;
|
||||
return HandlerResult::ContinueExecution;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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
|
||||
{
|
||||
// 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)
|
||||
return false;
|
||||
return HandlerResult::ExecuteNextHandler;
|
||||
|
||||
mmap_ClearCpuBlock(offset);
|
||||
return true;
|
||||
return HandlerResult::ContinueExecution;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue