Files
ppsspp/Common/ExceptionHandlerSetup.cpp
T
Henrik RydgårdandClaude Opus 5.5 daf46b1dbc Don't set up a signal alt stack in the memory fault handler
sigaltstack() is per thread. InstallExceptionHandler() runs in CPU_Init()
on the loading thread, which exits after boot, while the faults come from
the CPU thread and UninstallExceptionHandler() runs on the emu thread. So
our stack was never used, and the uninstall "restored" the loading
thread's previous alt stack onto the emu thread. On Android that's the
one bionic gives each thread and frees when the thread exits, so every
later fault on the emu thread ran its handler on freed memory. A Play
crash report shows scudo finding a corrupted chunk header when freeing our
stack in UninstallExceptionHandler().

The alt stack came with the Dolphin code, which needs it to handle its own
stack overflows. Ours only handles guest memory faults, so let the handler
run on the faulting thread's stack, or the alt stack that thread already
has. This also leaves a libretro frontend's alt stack alone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 19:37:23 +01:00

303 lines
9.7 KiB
C++

// Copyright 2008 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
// The corresponding file is called MemTools in the Dolphin project.
#include "ppsspp_config.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
#include "Common/CommonFuncs.h"
#include "Common/CommonTypes.h"
#include "Common/Log.h"
#include "Common/StringUtils.h"
#include "Common/MachineContext.h"
#include "Common/ExceptionHandlerSetup.h"
#if defined(_MSC_VER)
#include <crtdbg.h>
#include "Common/CommonWindows.h"
#endif
static BadAccessHandler g_badAccessHandler;
void SetupCRT(bool suppressDialogs) {
#if defined(_MSC_VER)
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
if (suppressDialogs) {
// 1. Redirect CRT assertions/errors/warnings to stderr.
const _HFILE reportTarget = _CRTDBG_FILE_STDERR;
_CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_ASSERT, reportTarget);
_CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_ERROR, reportTarget);
_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_WARN, reportTarget);
// 2. Suppress the abort() message box & crash reporting dialogs.
_set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT);
#if !PPSSPP_PLATFORM(UWP)
// 3. Suppress Windows OS-level "Program has stopped working" modal dialogs.
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
#endif
}
#endif
}
#ifdef MACHINE_CONTEXT_SUPPORTED
// We cannot handle exceptions in UWP builds. Bleh.
#if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP)
#include <dbghelp.h>
#pragma comment(lib, "dbghelp.lib")
static PVOID g_vectoredExceptionHandle;
static bool g_symInitialized = false;
static bool g_logCrashStackTrace = false;
// Logs a best-effort stack trace when we're about to let a genuinely unhandled access
// violation crash the process - e.g. a bad host pointer (not a guest PSP memory access)
// passed to a CRT function like strlen(). Only meant for diagnostics, so failures here are
// non-fatal; we just lose the extra info.
static void LogCrashStackTrace() {
void *stack[32]{};
USHORT captured = CaptureStackBackTrace(0, (ULONG)ARRAY_SIZE(stack), stack, nullptr);
ERROR_LOG(Log::System, "Unhandled access violation - stack trace (%d frames):", (int)captured);
HANDLE process = GetCurrentProcess();
char symbolBuffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME]{};
SYMBOL_INFO *symbol = (SYMBOL_INFO *)symbolBuffer;
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
symbol->MaxNameLen = MAX_SYM_NAME;
for (USHORT i = 0; i < captured; i++) {
DWORD64 address = (DWORD64)(uintptr_t)stack[i];
std::string line = StringFromFormat(" #%d %016llx", (int)i, (unsigned long long)address);
DWORD64 displacement = 0;
if (SymFromAddr(process, address, &displacement, symbol)) {
line += StringFromFormat(" %s+0x%llx", symbol->Name, (unsigned long long)displacement);
}
DWORD lineDisplacement = 0;
IMAGEHLP_LINE64 lineInfo{};
lineInfo.SizeOfStruct = sizeof(lineInfo);
if (SymGetLineFromAddr64(process, address, &lineDisplacement, &lineInfo)) {
line += StringFromFormat(" (%s:%d)", lineInfo.FileName, (int)lineInfo.LineNumber);
}
ERROR_LOG(Log::System, "%s", line.c_str());
}
}
static LONG NTAPI GlobalExceptionHandler(PEXCEPTION_POINTERS pPtrs) {
switch (pPtrs->ExceptionRecord->ExceptionCode) {
case EXCEPTION_ACCESS_VIOLATION:
{
int accessType = (int)pPtrs->ExceptionRecord->ExceptionInformation[0];
if (accessType == 8) { // Rule out DEP
return (DWORD)EXCEPTION_CONTINUE_SEARCH;
}
// virtual address of the inaccessible data
uintptr_t badAddress = (uintptr_t)pPtrs->ExceptionRecord->ExceptionInformation[1];
CONTEXT* ctx = pPtrs->ContextRecord;
if (g_badAccessHandler(badAddress, ctx)) {
return (DWORD)EXCEPTION_CONTINUE_EXECUTION;
} else {
if (g_logCrashStackTrace) {
ERROR_LOG(Log::System, "Unhandled access violation (%s) at address %016llx, pc=%016llx",
accessType == 1 ? "write" : "read", (unsigned long long)badAddress, (unsigned long long)(uintptr_t)pPtrs->ExceptionRecord->ExceptionAddress);
LogCrashStackTrace();
}
// Let's not prevent debugging.
return (DWORD)EXCEPTION_CONTINUE_SEARCH;
}
}
case EXCEPTION_STACK_OVERFLOW:
// Dolphin has some handling of this for the RET optimization emulation.
return EXCEPTION_CONTINUE_SEARCH;
case EXCEPTION_ILLEGAL_INSTRUCTION:
// No SSE support? Or simply bad codegen?
return EXCEPTION_CONTINUE_SEARCH;
case EXCEPTION_PRIV_INSTRUCTION:
// okay, dynarec codegen is obviously broken.
return EXCEPTION_CONTINUE_SEARCH;
case EXCEPTION_IN_PAGE_ERROR:
// okay, something went seriously wrong, out of memory?
return EXCEPTION_CONTINUE_SEARCH;
case EXCEPTION_BREAKPOINT:
// might want to do something fun with this one day?
return EXCEPTION_CONTINUE_SEARCH;
default:
return EXCEPTION_CONTINUE_SEARCH;
}
}
void InstallExceptionHandler(BadAccessHandler badAccessHandler, bool logStackTraceOnCrash) {
g_logCrashStackTrace = logStackTraceOnCrash;
if (g_vectoredExceptionHandle) {
g_badAccessHandler = badAccessHandler;
return;
}
INFO_LOG(Log::System, "Installing exception handler");
g_badAccessHandler = badAccessHandler;
if (logStackTraceOnCrash && !g_symInitialized) {
SymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_DEFERRED_LOADS | SYMOPT_UNDNAME);
g_symInitialized = SymInitialize(GetCurrentProcess(), nullptr, TRUE) != FALSE;
}
#ifdef USE_ASAN
g_vectoredExceptionHandle = AddVectoredExceptionHandler(FALSE, GlobalExceptionHandler);
#else
g_vectoredExceptionHandle = AddVectoredExceptionHandler(TRUE, GlobalExceptionHandler);
#endif
}
void UninstallExceptionHandler() {
if (g_vectoredExceptionHandle) {
RemoveVectoredExceptionHandler(g_vectoredExceptionHandle);
INFO_LOG(Log::System, "Removed exception handler");
g_vectoredExceptionHandle = nullptr;
}
g_badAccessHandler = nullptr;
}
#else
#include <signal.h>
static struct sigaction old_sa_segv;
static struct sigaction old_sa_bus;
// Hand the signal on to whatever was installed before us. Returning from a fault handler just
// re-runs the faulting instruction, so for anything we can't deal with this is the only exit
// that isn't an infinite loop.
static void ChainToPreviousHandler(int sig, siginfo_t *info, void *raw_context) {
struct sigaction *old_sa = sig == SIGSEGV ? &old_sa_segv : &old_sa_bus;
// Per the sigaction man page: with SA_SIGINFO it's sa_sigaction, otherwise sa_handler is
// SIG_DFL, SIG_IGN, or a handler pointer.
if (old_sa->sa_flags & SA_SIGINFO) {
old_sa->sa_sigaction(sig, info, raw_context);
return;
}
if (old_sa->sa_handler == SIG_DFL) {
signal(sig, SIG_DFL);
return;
}
if (old_sa->sa_handler == SIG_IGN) {
// Ignore signal
return;
}
old_sa->sa_handler(sig);
}
static void sigsegv_handler(int sig, siginfo_t* info, void* raw_context) {
if (sig != SIGSEGV && sig != SIGBUS) {
// We are not interested in other signals - handle it as usual.
return;
}
ucontext_t* context = (ucontext_t*)raw_context;
int sicode = info->si_code;
// Darwin reports some faults (e.g. on PROT_NONE pages) as SIGBUS.
bool addressFault = sig == SIGSEGV ? (sicode == SEGV_MAPERR || sicode == SEGV_ACCERR) : (sicode == BUS_ADRERR || sicode == BUS_ADRALN);
if (!addressFault) {
// Not an address fault we can do anything with - an MTE, protection-key or shadow
// stack fault, or a signal sent with kill(). Returning here would re-run the
// faulting instruction forever at 100% CPU, and would also swallow the signal from
// whatever was installed before us (a crash reporter, say).
ChainToPreviousHandler(sig, info, raw_context);
return;
}
uintptr_t bad_address = (uintptr_t)info->si_addr;
// Get all the information we can out of the context.
#ifdef __OpenBSD__
ucontext_t* ctx = context;
#elif defined(__APPLE__)
// uc_mcontext is a pointer here, and the registers are in its thread state.
SContext* ctx = &context->uc_mcontext->__ss;
#else
mcontext_t* ctx = &context->uc_mcontext;
#endif
if (!g_badAccessHandler(bad_address, ctx)) {
// retry and crash
// According to the sigaction man page, if sa_flags "SA_SIGINFO" is set to the sigaction
// function pointer, otherwise sa_handler contains one of:
// SIG_DEF: The 'default' action is performed
// SIG_IGN: The signal is ignored
// Any other value is a function pointer to a signal handler
ChainToPreviousHandler(sig, info, raw_context);
}
}
void InstallExceptionHandler(BadAccessHandler badAccessHandler, bool logStackTraceOnCrash) {
if (!badAccessHandler) {
return;
}
if (g_badAccessHandler) {
g_badAccessHandler = badAccessHandler;
return;
}
g_badAccessHandler = badAccessHandler;
// No sigaltstack of our own: it's per thread, and this runs on the loading thread, while the faults
// come from the CPU thread. The handler runs on the faulting thread's stack, or on its alternate
// stack if it has one (bionic gives every thread one).
INFO_LOG(Log::System, "Installed exception handler");
struct sigaction sa{};
sa.sa_handler = nullptr;
sa.sa_sigaction = &sigsegv_handler;
sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
sigemptyset(&sa.sa_mask);
sigaction(SIGSEGV, &sa, &old_sa_segv);
#ifdef __APPLE__
sigaction(SIGBUS, &sa, &old_sa_bus);
#endif
}
void UninstallExceptionHandler() {
if (!g_badAccessHandler) {
return;
}
sigaction(SIGSEGV, &old_sa_segv, nullptr);
#ifdef __APPLE__
sigaction(SIGBUS, &old_sa_bus, nullptr);
#endif
INFO_LOG(Log::System, "Uninstalled exception handler");
g_badAccessHandler = nullptr;
}
#endif
#else // !MACHINE_CONTEXT_SUPPORTED
void InstallExceptionHandler(BadAccessHandler badAccessHandler, bool logStackTraceOnCrash) {
ERROR_LOG(Log::System, "Exception handler not implemented on this platform, can't install");
}
void UninstallExceptionHandler() { }
#endif // MACHINE_CONTEXT_SUPPORTED