mirror of https://github.com/PCSX2/pcsx2.git
CrashHandler: Print backtrace on Linux
This commit is contained in:
parent
5fc855e519
commit
5805142fd7
|
@ -288,6 +288,11 @@ if(NOT WIN32 AND (QT_BUILD OR NOGUI_BUILD))
|
|||
target_link_libraries(common PRIVATE CURL::libcurl)
|
||||
endif()
|
||||
|
||||
if(UNIX AND NOT APPLE AND TARGET libbacktrace::libbacktrace)
|
||||
target_compile_definitions(common PRIVATE "HAS_LIBBACKTRACE=1")
|
||||
target_link_libraries(common PRIVATE libbacktrace::libbacktrace)
|
||||
endif()
|
||||
|
||||
target_link_libraries(common PRIVATE
|
||||
${LIBC_LIBRARIES}
|
||||
PNG::PNG
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/* PCSX2 - PS2 Emulator for PCs
|
||||
* Copyright (C) 2002-2022 PCSX2 Dev Team
|
||||
* Copyright (C) 2002-2023 PCSX2 Dev Team
|
||||
*
|
||||
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||
|
@ -215,6 +215,199 @@ void CrashHandler::Uninstall()
|
|||
}
|
||||
}
|
||||
|
||||
#elif defined(HAS_LIBBACKTRACE)
|
||||
|
||||
#include "FileSystem.h"
|
||||
|
||||
#include <backtrace.h>
|
||||
#include <signal.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <mutex>
|
||||
|
||||
namespace CrashHandler
|
||||
{
|
||||
struct BacktraceBuffer
|
||||
{
|
||||
char* buffer;
|
||||
size_t used;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
static const char* GetSignalName(int signal_no);
|
||||
static void AllocateBuffer(BacktraceBuffer* buf);
|
||||
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 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;
|
||||
}
|
||||
|
||||
const char* CrashHandler::GetSignalName(int signal_no)
|
||||
{
|
||||
switch (signal_no)
|
||||
{
|
||||
// Don't need to list all of them, there's only a couple we register.
|
||||
// clang-format off
|
||||
case SIGSEGV: return "SIGSEGV";
|
||||
case SIGBUS: return "SIGBUS";
|
||||
default: return "UNKNOWN";
|
||||
// clang-format on
|
||||
}
|
||||
}
|
||||
|
||||
void CrashHandler::AllocateBuffer(BacktraceBuffer* buf)
|
||||
{
|
||||
buf->used = 0;
|
||||
buf->size = __pagesize;
|
||||
buf->buffer = static_cast<char*>(mmap(nullptr, buf->size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
|
||||
if (buf->buffer == static_cast<char*>(MAP_FAILED))
|
||||
{
|
||||
buf->buffer = nullptr;
|
||||
buf->size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void CrashHandler::FreeBuffer(BacktraceBuffer* buf)
|
||||
{
|
||||
if (buf->buffer)
|
||||
munmap(buf->buffer, buf->size);
|
||||
}
|
||||
|
||||
void CrashHandler::AppendToBuffer(BacktraceBuffer* buf, const char* format, ...)
|
||||
{
|
||||
std::va_list ap;
|
||||
va_start(ap, format);
|
||||
|
||||
// Hope this doesn't allocate memory... it *can*, but hopefully unlikely since
|
||||
// it won't be the first call, and we're providing the buffer.
|
||||
if (buf->size > 0 && buf->used < (buf->size - 1))
|
||||
{
|
||||
const int written = std::vsnprintf(buf->buffer + buf->used, buf->size - buf->used, format, ap);
|
||||
if (written > 0)
|
||||
buf->used += static_cast<size_t>(written);
|
||||
}
|
||||
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
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);
|
||||
if (function)
|
||||
AppendToBuffer(buf, " %s", function);
|
||||
if (filename)
|
||||
AppendToBuffer(buf, " [%s:%d]", filename, lineno);
|
||||
|
||||
AppendToBuffer(buf, "\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CrashHandler::CallExistingSignalHandler(int signal, siginfo_t* siginfo, void* ctx)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void CrashHandler::CrashSignalHandler(int signal, siginfo_t* siginfo, void* ctx)
|
||||
{
|
||||
std::unique_lock lock(s_crash_mutex);
|
||||
|
||||
// If we crash somewhere in libbacktrace, don't bother trying again.
|
||||
if (!s_in_signal_handler)
|
||||
{
|
||||
s_in_signal_handler = true;
|
||||
|
||||
#if defined(__APPLE__) && defined(__x86_64__)
|
||||
void* const exception_pc = reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext->__ss.__rip);
|
||||
#elif defined(__FreeBSD__) && defined(__x86_64__)
|
||||
void* const exception_pc = reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext.mc_rip);
|
||||
#elif defined(__x86_64__)
|
||||
void* const exception_pc = reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext.gregs[REG_RIP]);
|
||||
#else
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
bool CrashHandler::Install()
|
||||
{
|
||||
const std::string progpath = FileSystem::GetProgramPath();
|
||||
s_backtrace_state = backtrace_create_state(progpath.empty() ? nullptr : progpath.c_str(), 0, nullptr, nullptr);
|
||||
if (!s_backtrace_state)
|
||||
return false;
|
||||
|
||||
struct sigaction sa;
|
||||
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_flags = SA_SIGINFO | SA_NODEFER;
|
||||
sa.sa_sigaction = CrashSignalHandler;
|
||||
if (sigaction(SIGBUS, &sa, &s_old_sigbus_action) != 0)
|
||||
return false;
|
||||
if (sigaction(SIGSEGV, &sa, &s_old_sigsegv_action) != 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CrashHandler::SetWriteDirectory(const std::string_view& dump_directory)
|
||||
{
|
||||
}
|
||||
|
||||
void CrashHandler::WriteDumpForCaller()
|
||||
{
|
||||
}
|
||||
|
||||
void CrashHandler::Uninstall()
|
||||
{
|
||||
// We can't really unchain the signal handlers... so, YOLO.
|
||||
}
|
||||
|
||||
|
||||
#else
|
||||
|
||||
bool CrashHandler::Install()
|
||||
|
|
|
@ -89,6 +89,7 @@ static void SysPageFaultSignalFilter(int signal, siginfo_t* siginfo, void* ctx)
|
|||
// Prevent recursive exception filtering.
|
||||
if (s_in_exception_handler)
|
||||
{
|
||||
lock.unlock();
|
||||
CallExistingSignalHandler(signal, siginfo, ctx);
|
||||
return;
|
||||
}
|
||||
|
@ -121,6 +122,7 @@ static void SysPageFaultSignalFilter(int signal, siginfo_t* siginfo, void* ctx)
|
|||
return;
|
||||
|
||||
// Call old signal handler, which will likely dump core.
|
||||
lock.unlock();
|
||||
CallExistingSignalHandler(signal, siginfo, ctx);
|
||||
}
|
||||
|
||||
|
@ -135,6 +137,10 @@ bool HostSys::InstallPageFaultHandler(PageFaultHandler handler)
|
|||
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
|
||||
#ifdef __APPLE__
|
||||
// MacOS uses SIGBUS for memory permission violations
|
||||
if (sigaction(SIGBUS, &sa, &s_old_sigbus_action) != 0)
|
||||
|
|
Loading…
Reference in New Issue