mirror of
https://github.com/Mauler125/r5sdk.git
synced 2025-02-09 19:15:03 +01:00
* Only gets compiled in for release builds. * Attaches on gamesdk init, so should catch everything on dll setup also. * Fake PDB parsing still needs to be done.
197 lines
7.0 KiB
C++
197 lines
7.0 KiB
C++
//=============================================================================//
|
|
//
|
|
// Purpose: Crash handling, it handles crashes!
|
|
//
|
|
//=============================================================================//
|
|
#include "core/stdafx.h"
|
|
#include "public/utility/binstream.h"
|
|
|
|
#ifndef _DEBUG
|
|
|
|
// Class is just for DLL init and DLL close, so we can actually define it here fine.
|
|
class CCrashHandler
|
|
{
|
|
public:
|
|
CCrashHandler();
|
|
~CCrashHandler();
|
|
|
|
private:
|
|
void* m_hExceptionHandler;
|
|
};
|
|
|
|
static std::map<DWORD, string> g_ExceptionToString =
|
|
{
|
|
{ EXCEPTION_ACCESS_VIOLATION, "Access Violation" },
|
|
{ EXCEPTION_IN_PAGE_ERROR, "Access Violation" },
|
|
{ EXCEPTION_ARRAY_BOUNDS_EXCEEDED, "Array bounds exceeded" },
|
|
{ EXCEPTION_ILLEGAL_INSTRUCTION, "Illegal instruction" },
|
|
{ EXCEPTION_INVALID_DISPOSITION, "Invalid disposition" },
|
|
{ EXCEPTION_NONCONTINUABLE_EXCEPTION, "Non-continuable exception" },
|
|
{ EXCEPTION_PRIV_INSTRUCTION, "Priviledged instruction" },
|
|
{ EXCEPTION_STACK_OVERFLOW, "Stack overflow" },
|
|
{ EXCEPTION_DATATYPE_MISALIGNMENT, "Datatype misalignment" },
|
|
{ EXCEPTION_FLT_DENORMAL_OPERAND, "Denormal operand [FLT]" },
|
|
{ EXCEPTION_FLT_DIVIDE_BY_ZERO, "Divide by zero [FLT]" },
|
|
{ EXCEPTION_FLT_INEXACT_RESULT, "Inexact float result [FLT]" },
|
|
{ EXCEPTION_FLT_INVALID_OPERATION, "Invalid operation [FLT]" },
|
|
{ EXCEPTION_FLT_OVERFLOW, "Numeric overflow [FLT]" },
|
|
{ EXCEPTION_FLT_STACK_CHECK, "Stack check [FLT]" },
|
|
{ EXCEPTION_FLT_UNDERFLOW, "Numeric underflow [FLT]" },
|
|
{ EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero [INT]" },
|
|
{ EXCEPTION_INT_OVERFLOW, "Numeric overfloat [INT]" }
|
|
};
|
|
|
|
// Borrowed from the R2 project
|
|
template<> struct fmt::formatter<M128A> : fmt::formatter<string_view>
|
|
{
|
|
template <typename FormatContext> auto format(const M128A& obj, FormatContext& ctx)
|
|
{
|
|
int v1 = obj.Low & INT_MAX;
|
|
int v2 = obj.Low >> 32;
|
|
int v3 = obj.High & INT_MAX;
|
|
int v4 = obj.High >> 32;
|
|
return fmt::format_to( ctx.out(),
|
|
"[ {:G}, {:G}, {:G}, {:G}], [ 0x{:x}, 0x{:x}, 0x{:x}, 0x{:x} ]",
|
|
*reinterpret_cast<float*>(&v1),
|
|
*reinterpret_cast<float*>(&v2),
|
|
*reinterpret_cast<float*>(&v3),
|
|
*reinterpret_cast<float*>(&v4),
|
|
v1,
|
|
v2,
|
|
v3,
|
|
v4);
|
|
}
|
|
};
|
|
|
|
string GetModuleCrashOffsetAndName(uintptr_t address)
|
|
{
|
|
HMODULE crashedModuleHandle;
|
|
if (!GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, static_cast<LPCSTR>((void*)address), &crashedModuleHandle))
|
|
return {};
|
|
|
|
MODULEINFO crashedModuleInfo;
|
|
if (!GetModuleInformation(GetCurrentProcess(), crashedModuleHandle, &crashedModuleInfo, sizeof(crashedModuleInfo)))
|
|
return {};
|
|
|
|
char szCrashedModuleFullName[MAX_PATH];
|
|
if (!GetModuleFileNameExA(GetCurrentProcess(), crashedModuleHandle, szCrashedModuleFullName, MAX_PATH))
|
|
return {};
|
|
|
|
const char* szCrashedModuleName = strrchr(szCrashedModuleFullName, '\\') + 1;
|
|
|
|
uintptr_t crashedModuleOffset = (address - reinterpret_cast<uintptr_t>(crashedModuleInfo.lpBaseOfDll));
|
|
|
|
return fmt::format("{0} + 0x{1:x}", szCrashedModuleName, crashedModuleOffset);
|
|
}
|
|
|
|
|
|
string GetExceptionReason(EXCEPTION_POINTERS* exceptionInfo)
|
|
{
|
|
const string& svException = g_ExceptionToString.at(exceptionInfo->ExceptionRecord->ExceptionCode);
|
|
stringstream ss;
|
|
|
|
ss << "Cause: " << svException << std::endl;
|
|
|
|
switch (exceptionInfo->ExceptionRecord->ExceptionCode)
|
|
{
|
|
case EXCEPTION_ACCESS_VIOLATION:
|
|
case EXCEPTION_IN_PAGE_ERROR:
|
|
{
|
|
ULONG_PTR uExceptionInfo0 = exceptionInfo->ExceptionRecord->ExceptionInformation[0];
|
|
ULONG_PTR uExceptionInfo1 = exceptionInfo->ExceptionRecord->ExceptionInformation[1];
|
|
|
|
// I don't remember why this has been done like this, but I trust my old self for once on this.
|
|
if (!uExceptionInfo0)
|
|
ss << "Attempted to read from: 0x" << std::setw(8) << std::setfill('0') << std::hex << uExceptionInfo1 << std::endl;
|
|
else if (uExceptionInfo0 == 1)
|
|
ss << "Attempted to write to: 0x" << std::setw(8) << std::setfill('0') << std::hex << uExceptionInfo1 << std::endl;
|
|
else if (uExceptionInfo0 == 8)
|
|
ss << "Data Execution Prevention (DEP) at: 0x" << std::setw(8) << std::setfill('0') << std::hex << uExceptionInfo1 << std::endl;
|
|
else
|
|
ss << "Unknown access violation at: 0x" << std::setw(8) << std::setfill('0') << std::hex << uExceptionInfo1 << std::endl;
|
|
|
|
return ss.str();
|
|
}
|
|
default:
|
|
{
|
|
return ss.str();
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: PDB PARSE AND RUNTIME EXCP.
|
|
long __stdcall ExceptionFilter(EXCEPTION_POINTERS* exceptionInfo)
|
|
{
|
|
// Avoid recursive calls.
|
|
static bool bLogged = false;
|
|
if (bLogged)
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
|
|
// If you are debugging you don't need this.
|
|
if (IsDebuggerPresent())
|
|
return EXCEPTION_EXECUTE_HANDLER;
|
|
|
|
// Goodluck on you if you somehow hit a guard page.
|
|
if (g_ExceptionToString.find(exceptionInfo->ExceptionRecord->ExceptionCode) == g_ExceptionToString.end())
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
|
|
CONTEXT* pExceptionContext = exceptionInfo->ContextRecord;
|
|
|
|
// Setup message.
|
|
string svMessage = fmt::format("{0}Module: {1}\r\n", GetExceptionReason(exceptionInfo), GetModuleCrashOffsetAndName(reinterpret_cast<uintptr_t>(exceptionInfo->ExceptionRecord->ExceptionAddress)));
|
|
|
|
constexpr const char* stdRegs[] = { "RAX", "RCX", "RDX", "RBX", "RSP", "RBP", "RSI", "RDI", "R8", "R9", "R10", "R11", "R12", "R13", "R14", "R15", "RIP" };
|
|
constexpr const char* xmmRegs[] = { "XMM0", "XMM1", "XMM2", "XMM3", "XMM4", "XMM5", "XMM6", "XMM7", "XMM8", "XMM9", "XMM10", "XMM11", "XMM12", "XMM13", "XMM14", "XMM15" };
|
|
|
|
// Normal registers now.
|
|
for (int i = 0; i < SDK_ARRAYSIZE(stdRegs); i++)
|
|
{
|
|
svMessage += fmt::format("{0}: 0x{1:X}\r\n", stdRegs[i], *(DWORD64*)((std::uintptr_t)pExceptionContext + offsetof(CONTEXT, Rax) + (sizeof(DWORD64) * i)));
|
|
}
|
|
|
|
// FPU Time!
|
|
for (int i = 0; i < SDK_ARRAYSIZE(xmmRegs); i++)
|
|
{
|
|
svMessage += fmt::format("{0}: {1}\r\n", xmmRegs[i], *(M128A*)((std::uintptr_t)pExceptionContext + offsetof(CONTEXT, Xmm0) + (sizeof(M128A) * i)));
|
|
}
|
|
|
|
svMessage += "\r\nStacktrace:\r\n\r\n";
|
|
|
|
// Now get the callstack..
|
|
constexpr int NUM_FRAMES_TO_CAPTURE = 60;
|
|
void* pStackTrace[NUM_FRAMES_TO_CAPTURE] = { 0 };
|
|
uint16_t nCapturedFrames = RtlCaptureStackBackTrace(0, NUM_FRAMES_TO_CAPTURE, pStackTrace, NULL);
|
|
|
|
for (uint16_t i = 0; i < nCapturedFrames; i++)
|
|
{
|
|
svMessage += GetModuleCrashOffsetAndName(reinterpret_cast<uintptr_t>(pStackTrace[i])) + "\n";
|
|
}
|
|
|
|
std::time_t time = std::time(nullptr);
|
|
stringstream ss; ss << "platform\\logs\\" << "apex_crash_" << std::put_time(std::localtime(&time), "%Y-%m-%d %H-%M-%S.txt");
|
|
|
|
// Not using CBaseFileSystem here cause if that would crash we'd have a problem.
|
|
CIOStream ioLogFile = CIOStream(ss.str(), CIOStream::Mode_t::WRITE);
|
|
ioLogFile.WriteString(svMessage);
|
|
ioLogFile.Close();
|
|
|
|
MessageBoxA(0, svMessage.c_str(), "R5R Crashed :/", MB_ICONERROR | MB_OK);
|
|
|
|
return EXCEPTION_EXECUTE_HANDLER;
|
|
}
|
|
|
|
CCrashHandler::CCrashHandler()
|
|
{
|
|
m_hExceptionHandler = AddVectoredExceptionHandler(TRUE, ExceptionFilter);
|
|
}
|
|
|
|
CCrashHandler::~CCrashHandler()
|
|
{
|
|
RemoveVectoredExceptionHandler(m_hExceptionHandler);
|
|
}
|
|
|
|
|
|
// Init on DLL init!
|
|
CCrashHandler* g_CrashHandler = new CCrashHandler();
|
|
|
|
#endif // _DEBUG
|