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)
endif()
find_package(Libbacktrace)
find_package(Libbacktrace REQUIRED)
find_package(PkgConfig REQUIRED)
pkg_check_modules(DBUS REQUIRED dbus-1)
endif()

View File

@ -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

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_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

View File

@ -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

View File

@ -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;
}

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+
#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

View File

@ -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

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+
#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;
}

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+
#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

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+
#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

View File

@ -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()
{

View File

@ -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;
}

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+
#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

View File

@ -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

View File

@ -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" />

View File

@ -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>

View File

@ -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;
}
}