PageFaultHandler: Simplifications

This commit is contained in:
Stenzek 2024-05-25 16:49:24 +10:00
parent b4b579d2b1
commit 0240ea8b49
No known key found for this signature in database
4 changed files with 68 additions and 98 deletions

View file

@ -54,10 +54,7 @@ if(LINUX)
endif() endif()
if(NOT WIN32 AND NOT APPLE) if(NOT WIN32 AND NOT APPLE)
find_package(Libbacktrace) find_package(Libbacktrace REQUIRED)
if(NOT LIBBACKTRACE_FOUND)
message(WARNING "libbacktrace not found, crashes will not produce backtraces.")
endif()
endif() endif()
if(APPLE) if(APPLE)

View file

@ -169,7 +169,7 @@ void CrashHandler::WriteDumpForCaller()
WriteMinidumpAndCallstack(nullptr); WriteMinidumpAndCallstack(nullptr);
} }
#elif defined(ENABLE_LIBBACKTRACE) #elif !defined(__APPLE__)
#include <backtrace.h> #include <backtrace.h>
#include <cstdarg> #include <cstdarg>
@ -194,15 +194,12 @@ static void AllocateBuffer(BacktraceBuffer* buf);
static void FreeBuffer(BacktraceBuffer* buf); static void FreeBuffer(BacktraceBuffer* buf);
static void AppendToBuffer(BacktraceBuffer* buf, const char* format, ...); static void AppendToBuffer(BacktraceBuffer* buf, const char* format, ...);
static int BacktraceFullCallback(void* data, uintptr_t pc, const char* filename, int lineno, const char* function); static int BacktraceFullCallback(void* data, uintptr_t pc, const char* filename, int lineno, const char* function);
static void CallExistingSignalHandler(int signal, siginfo_t* siginfo, void* ctx); static void LogCallstack(int signal, const void* exception_pc);
static void CrashSignalHandler(int signal, siginfo_t* siginfo, void* ctx);
static std::recursive_mutex s_crash_mutex; static std::recursive_mutex s_crash_mutex;
static bool s_in_signal_handler = false; static bool s_in_signal_handler = false;
static backtrace_state* s_backtrace_state = nullptr; static backtrace_state* s_backtrace_state = nullptr;
static struct sigaction s_old_sigbus_action;
static struct sigaction s_old_sigsegv_action;
} // namespace CrashHandler } // namespace CrashHandler
const char* CrashHandler::GetSignalName(int signal_no) const char* CrashHandler::GetSignalName(int signal_no)
@ -268,23 +265,25 @@ int CrashHandler::BacktraceFullCallback(void* data, uintptr_t pc, const char* fi
return 0; return 0;
} }
void CrashHandler::CallExistingSignalHandler(int signal, siginfo_t* siginfo, void* ctx) void CrashHandler::LogCallstack(int signal, const void* exception_pc)
{ {
const struct sigaction& sa = (signal == SIGBUS) ? s_old_sigbus_action : s_old_sigsegv_action; BacktraceBuffer buf;
if (sa.sa_flags & SA_SIGINFO) AllocateBuffer(&buf);
{ if (signal != 0 || exception_pc)
sa.sa_sigaction(signal, siginfo, ctx); AppendToBuffer(&buf, "*************** Unhandled %s at %p ***************\n", GetSignalName(signal), exception_pc);
} else
else if (sa.sa_handler == SIG_DFL) AppendToBuffer(&buf, "*******************************************************************\n");
{
// Re-raising the signal would just queue it, and since we'd restore the handler back to us, const int rc = backtrace_full(s_backtrace_state, 0, BacktraceFullCallback, nullptr, &buf);
// we'd end up right back here again. So just abort, because that's probably what it'd do anyway. if (rc != 0)
abort(); AppendToBuffer(&buf, " backtrace_full() failed: %d\n");
}
else if (sa.sa_handler != SIG_IGN) AppendToBuffer(&buf, "*******************************************************************\n");
{
sa.sa_handler(signal); if (buf.used > 0)
} write(STDERR_FILENO, buf.buffer, buf.used);
FreeBuffer(&buf);
} }
void CrashHandler::CrashSignalHandler(int signal, siginfo_t* siginfo, void* ctx) void CrashHandler::CrashSignalHandler(int signal, siginfo_t* siginfo, void* ctx)
@ -306,27 +305,17 @@ void CrashHandler::CrashSignalHandler(int signal, siginfo_t* siginfo, void* ctx)
void* const exception_pc = nullptr; void* const exception_pc = nullptr;
#endif #endif
BacktraceBuffer buf; LogCallstack(signal, exception_pc);
AllocateBuffer(&buf);
AppendToBuffer(&buf, "*************** Unhandled %s at %p ***************\n", GetSignalName(signal), exception_pc);
const int rc = backtrace_full(s_backtrace_state, 0, BacktraceFullCallback, nullptr, &buf);
if (rc != 0)
AppendToBuffer(&buf, " backtrace_full() failed: %d\n");
AppendToBuffer(&buf, "*******************************************************************\n");
if (buf.used > 0)
write(STDERR_FILENO, buf.buffer, buf.used);
FreeBuffer(&buf);
s_in_signal_handler = false; s_in_signal_handler = false;
} }
// Chances are we're not going to have anything else to call, but just in case.
lock.unlock(); lock.unlock();
CallExistingSignalHandler(signal, siginfo, ctx);
// We can't continue from here. Just bail out and dump core.
std::fputs("Aborting application.\n", stderr);
std::fflush(stderr);
std::abort();
} }
bool CrashHandler::Install() bool CrashHandler::Install()
@ -341,9 +330,9 @@ bool CrashHandler::Install()
sigemptyset(&sa.sa_mask); sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_SIGINFO | SA_NODEFER; sa.sa_flags = SA_SIGINFO | SA_NODEFER;
sa.sa_sigaction = CrashSignalHandler; sa.sa_sigaction = CrashSignalHandler;
if (sigaction(SIGBUS, &sa, &s_old_sigbus_action) != 0) if (sigaction(SIGBUS, &sa, nullptr) != 0)
return false; return false;
if (sigaction(SIGSEGV, &sa, &s_old_sigsegv_action) != 0) if (sigaction(SIGSEGV, &sa, nullptr) != 0)
return false; return false;
return true; return true;
@ -355,6 +344,7 @@ void CrashHandler::SetWriteDirectory(std::string_view dump_directory)
void CrashHandler::WriteDumpForCaller() void CrashHandler::WriteDumpForCaller()
{ {
LogCallstack(0, nullptr);
} }
#else #else
@ -372,4 +362,12 @@ void CrashHandler::WriteDumpForCaller()
{ {
} }
void CrashHandler::CrashSignalHandler(int signal, siginfo_t* siginfo, void* ctx)
{
// We can't continue from here. Just bail out and dump core.
std::fputs("Aborting application.\n", stderr);
std::fflush(stderr);
std::abort();
}
#endif #endif

View file

@ -1,11 +1,21 @@
// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com> // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "types.h" #include "types.h"
#include <string_view> #include <string_view>
#ifndef _WIN32
#include <signal.h>
#endif
namespace CrashHandler { namespace CrashHandler {
bool Install(); bool Install();
void SetWriteDirectory(std::string_view dump_directory); void SetWriteDirectory(std::string_view dump_directory);
void WriteDumpForCaller(); void WriteDumpForCaller();
#ifndef _WIN32
// Allow crash handler to be invoked from a signal.
void CrashSignalHandler(int signal, siginfo_t* siginfo, void* ctx);
#endif
} // namespace CrashHandler } // namespace CrashHandler

View file

@ -4,6 +4,7 @@
#include "page_fault_handler.h" #include "page_fault_handler.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/crash_handler.h"
#include "common/error.h" #include "common/error.h"
#include "common/log.h" #include "common/log.h"
@ -14,7 +15,7 @@
#if defined(_WIN32) #if defined(_WIN32)
#include "common/windows_headers.h" #include "common/windows_headers.h"
#elif defined(__linux__) || defined(__ANDROID__) #elif defined(__linux__)
#include <signal.h> #include <signal.h>
#include <ucontext.h> #include <ucontext.h>
#include <unistd.h> #include <unistd.h>
@ -139,54 +140,11 @@ bool PageFaultHandler::Install(Error* error)
namespace PageFaultHandler { namespace PageFaultHandler {
static void SignalHandler(int sig, siginfo_t* info, void* ctx); static void SignalHandler(int sig, siginfo_t* info, void* ctx);
static void CallExistingSignalHandler(int signal, siginfo_t* siginfo, void* ctx);
static struct sigaction s_old_sigsegv_action;
#if defined(__APPLE__) || defined(__aarch64__)
static struct sigaction s_old_sigbus_action;
#endif
} // namespace PageFaultHandler } // namespace PageFaultHandler
void PageFaultHandler::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);
}
}
void PageFaultHandler::SignalHandler(int sig, siginfo_t* info, void* ctx) void PageFaultHandler::SignalHandler(int sig, siginfo_t* info, void* ctx)
{ {
// Executing the handler concurrently from multiple threads wouldn't go down well. #if defined(__linux__)
std::unique_lock lock(s_exception_handler_mutex);
// Prevent recursive exception filtering.
if (s_in_exception_handler)
{
lock.unlock();
CallExistingSignalHandler(sig, info, ctx);
return;
}
#if defined(__linux__) || defined(__ANDROID__)
void* const exception_address = reinterpret_cast<void*>(info->si_addr); void* const exception_address = reinterpret_cast<void*>(info->si_addr);
#if defined(CPU_ARCH_X64) #if defined(CPU_ARCH_X64)
@ -241,19 +199,26 @@ void PageFaultHandler::SignalHandler(int sig, siginfo_t* info, void* ctx)
#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; s_in_exception_handler = true;
result = HandlePageFault(exception_pc, exception_address, is_write);
const HandlerResult result = HandlePageFault(exception_pc, exception_address, is_write);
s_in_exception_handler = false; s_in_exception_handler = false;
}
s_exception_handler_mutex.unlock();
// Resumes execution right where we left off (re-executes instruction that caused the SIGSEGV). // Resumes execution right where we left off (re-executes instruction that caused the SIGSEGV).
if (result == HandlerResult::ContinueExecution) if (result == HandlerResult::ContinueExecution)
return; return;
// Call old signal handler, which will likely dump core. // We couldn't handle it. Pass it off to the crash dumper.
lock.unlock(); CrashHandler::CrashSignalHandler(sig, info, ctx);
CallExistingSignalHandler(sig, info, ctx);
} }
bool PageFaultHandler::Install(Error* error) bool PageFaultHandler::Install(Error* error)
@ -270,14 +235,14 @@ bool PageFaultHandler::Install(Error* error)
// Don't block the signal from executing recursively, we want to fire the original handler. // Don't block the signal from executing recursively, we want to fire the original handler.
sa.sa_flags |= SA_NODEFER; sa.sa_flags |= SA_NODEFER;
#endif #endif
if (sigaction(SIGSEGV, &sa, &s_old_sigsegv_action) != 0) if (sigaction(SIGSEGV, &sa, nullptr) != 0)
{ {
Error::SetErrno(error, "sigaction() for SIGSEGV failed: ", errno); Error::SetErrno(error, "sigaction() for SIGSEGV failed: ", errno);
return false; return false;
} }
#if defined(__APPLE__) || defined(__aarch64__) #if defined(__APPLE__) || defined(__aarch64__)
// MacOS uses SIGBUS for memory permission violations // MacOS uses SIGBUS for memory permission violations
if (sigaction(SIGBUS, &sa, &s_old_sigbus_action) != 0) if (sigaction(SIGBUS, &sa, nullptr) != 0)
{ {
Error::SetErrno(error, "sigaction() for SIGBUS failed: ", errno); Error::SetErrno(error, "sigaction() for SIGBUS failed: ", errno);
return false; return false;