998 lines
38 KiB
C++
998 lines
38 KiB
C++
#include "VrCrashHandler.h"
|
||
#include "VrPathUtils.h"
|
||
|
||
#include <atomic>
|
||
#include <cerrno>
|
||
#include <cstdint>
|
||
#include <cstdio>
|
||
#include <cstdlib>
|
||
#include <ctime>
|
||
#include <exception>
|
||
#include <memory>
|
||
#include <mutex>
|
||
#include <string>
|
||
#include <vector>
|
||
|
||
#ifdef _WIN32
|
||
#ifndef NOMINMAX
|
||
#define NOMINMAX
|
||
#endif
|
||
#include <windows.h>
|
||
#include <dbghelp.h>
|
||
#include <csignal>
|
||
#elif defined(__linux__)
|
||
#include <fcntl.h>
|
||
#include <limits.h>
|
||
#include <signal.h>
|
||
#include <sys/stat.h>
|
||
#include <sys/syscall.h>
|
||
#include <time.h>
|
||
#include <ucontext.h>
|
||
#include <unistd.h>
|
||
#if defined(__GLIBC__)
|
||
#include <execinfo.h>
|
||
#endif
|
||
#endif
|
||
|
||
namespace
|
||
{
|
||
#if defined(_WIN32) || defined(__linux__)
|
||
const int maxFrames = 64;
|
||
|
||
struct OutputTarget
|
||
{
|
||
std::string directory;
|
||
#ifdef _WIN32
|
||
std::wstring directoryPath;
|
||
#endif
|
||
};
|
||
|
||
// 回调只读不可变快照,不获取配置锁。旧快照保留到进程结束,避免切换目录时悬空。
|
||
std::atomic<OutputTarget*> outputTarget{nullptr};
|
||
std::atomic_flag handlingCrash = ATOMIC_FLAG_INIT;
|
||
|
||
#ifdef _WIN32
|
||
using NativeFile = HANDLE;
|
||
const NativeFile invalidFile = INVALID_HANDLE_VALUE;
|
||
#else
|
||
using NativeFile = int;
|
||
const NativeFile invalidFile = -1;
|
||
#endif
|
||
|
||
void writeBytes(NativeFile file, const char* data, size_t size)
|
||
{
|
||
while (size > 0)
|
||
{
|
||
#ifdef _WIN32
|
||
DWORD written = 0;
|
||
if (!WriteFile(file, data, static_cast<DWORD>(size), &written, nullptr) || written == 0)
|
||
{
|
||
return;
|
||
}
|
||
#else
|
||
const ssize_t written = write(file, data, size);
|
||
if (written < 0 && errno == EINTR)
|
||
{
|
||
continue;
|
||
}
|
||
if (written <= 0)
|
||
{
|
||
return;
|
||
}
|
||
#endif
|
||
data += written;
|
||
size -= static_cast<size_t>(written);
|
||
}
|
||
}
|
||
|
||
void writeText(NativeFile file, const char* text)
|
||
{
|
||
size_t size = 0;
|
||
while (text[size] != '\0')
|
||
{
|
||
++size;
|
||
}
|
||
writeBytes(file, text, size);
|
||
}
|
||
|
||
// 信号路径不用 printf/iostream 或日志库,避免堆分配和 stdio/日志锁重入。
|
||
void writeNumber(NativeFile file, const char* label, uint64_t value, unsigned int base = 10)
|
||
{
|
||
char buffer[32];
|
||
char* end = buffer + sizeof(buffer);
|
||
char* begin = end;
|
||
do
|
||
{
|
||
*--begin = "0123456789abcdef"[value % base];
|
||
value /= base;
|
||
} while (value != 0);
|
||
writeText(file, label);
|
||
if (base == 16)
|
||
{
|
||
writeText(file, "0x");
|
||
}
|
||
writeBytes(file, begin, static_cast<size_t>(end - begin));
|
||
writeText(file, "\n");
|
||
}
|
||
|
||
#ifdef _WIN32
|
||
struct DebugFunctions
|
||
{
|
||
HMODULE library = nullptr;
|
||
decltype(&SymInitialize) initialize = nullptr;
|
||
decltype(&SymSetOptions) setOptions = nullptr;
|
||
decltype(&SymFromAddr) fromAddress = nullptr;
|
||
decltype(&SymGetLineFromAddr64) getLine = nullptr;
|
||
decltype(&SymFunctionTableAccess64) functionTable = nullptr;
|
||
decltype(&SymGetModuleBase64) moduleBase = nullptr;
|
||
decltype(&StackWalk64) walk = nullptr;
|
||
decltype(&MiniDumpWriteDump) dump = nullptr;
|
||
bool symbolsReady = false;
|
||
};
|
||
|
||
// 正常初始化阶段加载 DbgHelp,静态库的调用方无需额外链接 dbghelp.lib。
|
||
DebugFunctions debugFunctions;
|
||
|
||
void prepareUnwinder()
|
||
{
|
||
if (debugFunctions.library)
|
||
{
|
||
return;
|
||
}
|
||
wchar_t systemPath[MAX_PATH] = {};
|
||
const UINT length = GetSystemDirectoryW(systemPath, MAX_PATH);
|
||
if (length == 0 || length >= MAX_PATH - 13)
|
||
{
|
||
return;
|
||
}
|
||
const std::wstring libraryPath = std::wstring(systemPath) + L"\\dbghelp.dll";
|
||
debugFunctions.library = LoadLibraryW(libraryPath.c_str());
|
||
if (!debugFunctions.library)
|
||
{
|
||
return;
|
||
}
|
||
#define VR_LOAD_DEBUG_FUNCTION(member, name) \
|
||
debugFunctions.member = reinterpret_cast<decltype(debugFunctions.member)>(GetProcAddress(debugFunctions.library, name))
|
||
VR_LOAD_DEBUG_FUNCTION(initialize, "SymInitialize");
|
||
VR_LOAD_DEBUG_FUNCTION(setOptions, "SymSetOptions");
|
||
VR_LOAD_DEBUG_FUNCTION(fromAddress, "SymFromAddr");
|
||
VR_LOAD_DEBUG_FUNCTION(getLine, "SymGetLineFromAddr64");
|
||
VR_LOAD_DEBUG_FUNCTION(functionTable, "SymFunctionTableAccess64");
|
||
VR_LOAD_DEBUG_FUNCTION(moduleBase, "SymGetModuleBase64");
|
||
VR_LOAD_DEBUG_FUNCTION(walk, "StackWalk64");
|
||
VR_LOAD_DEBUG_FUNCTION(dump, "MiniDumpWriteDump");
|
||
#undef VR_LOAD_DEBUG_FUNCTION
|
||
if (debugFunctions.setOptions)
|
||
{
|
||
debugFunctions.setOptions(SYMOPT_UNDNAME | SYMOPT_LOAD_LINES | SYMOPT_DEFERRED_LOADS | SYMOPT_FAIL_CRITICAL_ERRORS);
|
||
}
|
||
if (debugFunctions.initialize)
|
||
{
|
||
debugFunctions.symbolsReady = debugFunctions.initialize(GetCurrentProcess(), nullptr, TRUE) != FALSE;
|
||
}
|
||
}
|
||
|
||
void writeWindowsFrame(NativeFile file, DWORD64 address)
|
||
{
|
||
writeNumber(file, " pc=", address, 16);
|
||
MEMORY_BASIC_INFORMATION memory = {};
|
||
if (VirtualQuery(reinterpret_cast<const void*>(static_cast<uintptr_t>(address)), &memory, sizeof(memory)))
|
||
{
|
||
writeNumber(file, " module_offset=", address - reinterpret_cast<uintptr_t>(memory.AllocationBase), 16);
|
||
wchar_t modulePath[MAX_PATH] = {};
|
||
if (GetModuleFileNameW(static_cast<HMODULE>(memory.AllocationBase), modulePath, MAX_PATH))
|
||
{
|
||
char utf8Path[MAX_PATH * 3] = {};
|
||
if (WideCharToMultiByte(CP_UTF8, 0, modulePath, -1, utf8Path, sizeof(utf8Path), nullptr, nullptr))
|
||
{
|
||
writeText(file, " module=");
|
||
writeText(file, utf8Path);
|
||
writeText(file, "\n");
|
||
}
|
||
}
|
||
}
|
||
if (!debugFunctions.symbolsReady)
|
||
{
|
||
return;
|
||
}
|
||
alignas(SYMBOL_INFO) char symbolBuffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME] = {};
|
||
SYMBOL_INFO* symbol = reinterpret_cast<SYMBOL_INFO*>(symbolBuffer);
|
||
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
|
||
symbol->MaxNameLen = MAX_SYM_NAME;
|
||
DWORD64 displacement = 0;
|
||
if (debugFunctions.fromAddress && debugFunctions.fromAddress(GetCurrentProcess(), address, &displacement, symbol))
|
||
{
|
||
writeText(file, " symbol=");
|
||
writeText(file, symbol->Name);
|
||
writeNumber(file, "+", displacement, 16);
|
||
}
|
||
IMAGEHLP_LINE64 line = {};
|
||
line.SizeOfStruct = sizeof(line);
|
||
DWORD lineDisplacement = 0;
|
||
if (debugFunctions.getLine && debugFunctions.getLine(GetCurrentProcess(), address, &lineDisplacement, &line))
|
||
{
|
||
writeText(file, " source=");
|
||
writeText(file, line.FileName);
|
||
writeNumber(file, ":", line.LineNumber);
|
||
}
|
||
}
|
||
|
||
void writeWindowsExceptionName(NativeFile file, DWORD code)
|
||
{
|
||
const char* name = "unknown";
|
||
switch (code)
|
||
{
|
||
case EXCEPTION_ACCESS_VIOLATION: name = "EXCEPTION_ACCESS_VIOLATION"; break;
|
||
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: name = "EXCEPTION_ARRAY_BOUNDS_EXCEEDED"; break;
|
||
case EXCEPTION_DATATYPE_MISALIGNMENT: name = "EXCEPTION_DATATYPE_MISALIGNMENT"; break;
|
||
case EXCEPTION_FLT_DIVIDE_BY_ZERO: name = "EXCEPTION_FLT_DIVIDE_BY_ZERO"; break;
|
||
case EXCEPTION_ILLEGAL_INSTRUCTION: name = "EXCEPTION_ILLEGAL_INSTRUCTION"; break;
|
||
case EXCEPTION_IN_PAGE_ERROR: name = "EXCEPTION_IN_PAGE_ERROR"; break;
|
||
case EXCEPTION_INT_DIVIDE_BY_ZERO: name = "EXCEPTION_INT_DIVIDE_BY_ZERO"; break;
|
||
case EXCEPTION_STACK_OVERFLOW: name = "EXCEPTION_STACK_OVERFLOW"; break;
|
||
case 0xC0000374U: name = "STATUS_HEAP_CORRUPTION"; break;
|
||
case 0xC0000409U: name = "STATUS_STACK_BUFFER_OVERRUN"; break;
|
||
default: break;
|
||
}
|
||
writeText(file, "exception_name=");
|
||
writeText(file, name);
|
||
writeText(file, "\n");
|
||
}
|
||
|
||
void writeWindowsRegisters(NativeFile file, const CONTEXT& context)
|
||
{
|
||
writeText(file, "Registers:\n");
|
||
#if defined(_M_X64) || defined(__x86_64__)
|
||
writeNumber(file, " rip=", context.Rip, 16);
|
||
writeNumber(file, " rsp=", context.Rsp, 16);
|
||
writeNumber(file, " rbp=", context.Rbp, 16);
|
||
writeNumber(file, " rflags=", context.EFlags, 16);
|
||
writeNumber(file, " rax=", context.Rax, 16);
|
||
writeNumber(file, " rbx=", context.Rbx, 16);
|
||
writeNumber(file, " rcx=", context.Rcx, 16);
|
||
writeNumber(file, " rdx=", context.Rdx, 16);
|
||
writeNumber(file, " rsi=", context.Rsi, 16);
|
||
writeNumber(file, " rdi=", context.Rdi, 16);
|
||
writeNumber(file, " r8=", context.R8, 16);
|
||
writeNumber(file, " r9=", context.R9, 16);
|
||
#elif defined(_M_IX86) || defined(__i386__)
|
||
writeNumber(file, " eip=", context.Eip, 16);
|
||
writeNumber(file, " esp=", context.Esp, 16);
|
||
writeNumber(file, " ebp=", context.Ebp, 16);
|
||
writeNumber(file, " eflags=", context.EFlags, 16);
|
||
writeNumber(file, " eax=", context.Eax, 16);
|
||
writeNumber(file, " ebx=", context.Ebx, 16);
|
||
writeNumber(file, " ecx=", context.Ecx, 16);
|
||
writeNumber(file, " edx=", context.Edx, 16);
|
||
writeNumber(file, " esi=", context.Esi, 16);
|
||
writeNumber(file, " edi=", context.Edi, 16);
|
||
#elif defined(_M_ARM64) || defined(__aarch64__)
|
||
writeNumber(file, " pc=", context.Pc, 16);
|
||
writeNumber(file, " sp=", context.Sp, 16);
|
||
writeNumber(file, " fp=", context.Fp, 16);
|
||
writeNumber(file, " lr=", context.Lr, 16);
|
||
for (int index = 0; index < 31; ++index)
|
||
{
|
||
char label[8] = {};
|
||
label[0] = ' '; label[1] = 'x';
|
||
if (index >= 10)
|
||
{
|
||
label[2] = static_cast<char>('0' + index / 10);
|
||
label[3] = static_cast<char>('0' + index % 10);
|
||
label[4] = '=';
|
||
}
|
||
else
|
||
{
|
||
label[2] = static_cast<char>('0' + index);
|
||
label[3] = '=';
|
||
}
|
||
writeNumber(file, label, context.X[index], 16);
|
||
}
|
||
writeNumber(file, " cpsr=", context.Cpsr, 16);
|
||
#elif defined(_M_ARM) || defined(__arm__)
|
||
writeNumber(file, " pc=", context.Pc, 16);
|
||
writeNumber(file, " sp=", context.Sp, 16);
|
||
writeNumber(file, " fp=", context.R11, 16);
|
||
writeNumber(file, " lr=", context.Lr, 16);
|
||
writeNumber(file, " r0=", context.R0, 16);
|
||
writeNumber(file, " r1=", context.R1, 16);
|
||
writeNumber(file, " r2=", context.R2, 16);
|
||
writeNumber(file, " r3=", context.R3, 16);
|
||
writeNumber(file, " r12=", context.R12, 16);
|
||
writeNumber(file, " cpsr=", context.Cpsr, 16);
|
||
#endif
|
||
}
|
||
|
||
void writeWindowsStack(NativeFile file, CONTEXT context)
|
||
{
|
||
STACKFRAME64 frame = {};
|
||
DWORD machine = 0;
|
||
#if defined(_M_X64) || defined(__x86_64__)
|
||
machine = IMAGE_FILE_MACHINE_AMD64;
|
||
frame.AddrPC.Offset = context.Rip;
|
||
frame.AddrFrame.Offset = context.Rbp;
|
||
frame.AddrStack.Offset = context.Rsp;
|
||
#elif defined(_M_IX86) || defined(__i386__)
|
||
machine = IMAGE_FILE_MACHINE_I386;
|
||
frame.AddrPC.Offset = context.Eip;
|
||
frame.AddrFrame.Offset = context.Ebp;
|
||
frame.AddrStack.Offset = context.Esp;
|
||
#elif defined(_M_ARM64) || defined(__aarch64__)
|
||
machine = IMAGE_FILE_MACHINE_ARM64;
|
||
frame.AddrPC.Offset = context.Pc;
|
||
frame.AddrFrame.Offset = context.Fp;
|
||
frame.AddrStack.Offset = context.Sp;
|
||
#endif
|
||
frame.AddrPC.Mode = AddrModeFlat;
|
||
frame.AddrFrame.Mode = AddrModeFlat;
|
||
frame.AddrStack.Mode = AddrModeFlat;
|
||
writeText(file, "Faulting thread stack (frame 0 is the captured PC):\n");
|
||
for (int index = 0; index < maxFrames && frame.AddrPC.Offset != 0; ++index)
|
||
{
|
||
writeNumber(file, "frame=", index);
|
||
writeWindowsFrame(file, frame.AddrPC.Offset);
|
||
if (!debugFunctions.symbolsReady || !debugFunctions.walk || !debugFunctions.functionTable || !debugFunctions.moduleBase)
|
||
{
|
||
break;
|
||
}
|
||
const DWORD64 previousPc = frame.AddrPC.Offset;
|
||
const DWORD64 previousSp = frame.AddrStack.Offset;
|
||
if (!debugFunctions.walk(machine, GetCurrentProcess(), GetCurrentThread(), &frame, &context,
|
||
nullptr, debugFunctions.functionTable, debugFunctions.moduleBase, nullptr) ||
|
||
(frame.AddrPC.Offset == previousPc && frame.AddrStack.Offset == previousSp))
|
||
{
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
void writeWindowsArchitecture(NativeFile file)
|
||
{
|
||
#if defined(_M_ARM64) || defined(__aarch64__)
|
||
writeText(file, "architecture=arm64\n");
|
||
#elif defined(_M_ARM) || defined(__arm__)
|
||
writeText(file, "architecture=arm32\n");
|
||
#elif defined(_M_X64) || defined(__x86_64__)
|
||
writeText(file, "architecture=x64\n");
|
||
#elif defined(_M_IX86) || defined(__i386__)
|
||
writeText(file, "architecture=x86\n");
|
||
#else
|
||
writeText(file, "architecture=unknown\n");
|
||
#endif
|
||
}
|
||
|
||
void writeWindowsReport(EXCEPTION_POINTERS* exceptionInfo, int signalNumber)
|
||
{
|
||
if (handlingCrash.test_and_set())
|
||
{
|
||
TerminateProcess(GetCurrentProcess(), 1);
|
||
return;
|
||
}
|
||
OutputTarget* target = outputTarget.load(std::memory_order_acquire);
|
||
if (!target)
|
||
{
|
||
return;
|
||
}
|
||
SYSTEMTIME localTime = {};
|
||
GetLocalTime(&localTime);
|
||
wchar_t stackPath[32768] = {};
|
||
wchar_t dumpPath[32768] = {};
|
||
const int stackPathLength = _snwprintf_s(stackPath, _countof(stackPath), _TRUNCATE,
|
||
L"%s\\crash_stack_%04u%02u%02u_%02u%02u%02u_%03u.txt",
|
||
target->directoryPath.c_str(), localTime.wYear, localTime.wMonth, localTime.wDay,
|
||
localTime.wHour, localTime.wMinute, localTime.wSecond, localTime.wMilliseconds);
|
||
const int dumpPathLength = _snwprintf_s(dumpPath, _countof(dumpPath), _TRUNCATE,
|
||
L"%s\\crash_%04u%02u%02u_%02u%02u%02u_%03u.dmp",
|
||
target->directoryPath.c_str(), localTime.wYear, localTime.wMonth, localTime.wDay,
|
||
localTime.wHour, localTime.wMinute, localTime.wSecond, localTime.wMilliseconds);
|
||
if (stackPathLength <= 0 || dumpPathLength <= 0)
|
||
{
|
||
return;
|
||
}
|
||
const NativeFile file = CreateFileW(stackPath, GENERIC_WRITE, FILE_SHARE_READ,
|
||
nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||
if (file == invalidFile)
|
||
{
|
||
return;
|
||
}
|
||
writeText(file, "VrUtils crash report\n");
|
||
writeWindowsArchitecture(file);
|
||
writeNumber(file, "pid=", GetCurrentProcessId());
|
||
writeNumber(file, "tid=", GetCurrentThreadId());
|
||
FILETIME time = {};
|
||
GetSystemTimeAsFileTime(&time);
|
||
const uint64_t ticks = (static_cast<uint64_t>(time.dwHighDateTime) << 32) | time.dwLowDateTime;
|
||
writeNumber(file, "unix_time=", (ticks - 116444736000000000ULL) / 10000000ULL);
|
||
writeNumber(file, "signal=", signalNumber);
|
||
const EXCEPTION_RECORD* record = exceptionInfo ? exceptionInfo->ExceptionRecord : nullptr;
|
||
const CONTEXT* capturedContext = exceptionInfo ? exceptionInfo->ContextRecord : nullptr;
|
||
if (record)
|
||
{
|
||
writeNumber(file, "exception_code=", record->ExceptionCode, 16);
|
||
writeNumber(file, "exception_address=", reinterpret_cast<uintptr_t>(record->ExceptionAddress), 16);
|
||
writeWindowsExceptionName(file, record->ExceptionCode);
|
||
if (record->ExceptionCode == EXCEPTION_ACCESS_VIOLATION && record->NumberParameters >= 2)
|
||
{
|
||
writeText(file, "access_type=");
|
||
writeText(file, record->ExceptionInformation[0] == 0 ? "read" :
|
||
(record->ExceptionInformation[0] == 1 ? "write" : "execute"));
|
||
writeText(file, "\n");
|
||
writeNumber(file, "access_address=", record->ExceptionInformation[1], 16);
|
||
}
|
||
if (record->ExceptionCode == EXCEPTION_IN_PAGE_ERROR && record->NumberParameters >= 3)
|
||
{
|
||
writeNumber(file, "in_page_status=", record->ExceptionInformation[0], 16);
|
||
writeNumber(file, "in_page_address=", record->ExceptionInformation[1], 16);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
writeText(file, "exception_code=unavailable\n");
|
||
writeText(file, "exception_address=unavailable\n");
|
||
}
|
||
// 先提交异常头,再尽力生成 dump 和展开栈;展开器自身异常时仍有最低限度现场。
|
||
FlushFileBuffers(file);
|
||
if (debugFunctions.dump && exceptionInfo)
|
||
{
|
||
const HANDLE dumpFile = CreateFileW(dumpPath, GENERIC_WRITE, FILE_SHARE_READ,
|
||
nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||
if (dumpFile != INVALID_HANDLE_VALUE)
|
||
{
|
||
MINIDUMP_EXCEPTION_INFORMATION information = {};
|
||
information.ThreadId = GetCurrentThreadId();
|
||
information.ExceptionPointers = exceptionInfo;
|
||
information.ClientPointers = FALSE;
|
||
const BOOL success = debugFunctions.dump(GetCurrentProcess(), GetCurrentProcessId(), dumpFile,
|
||
static_cast<MINIDUMP_TYPE>(MiniDumpWithThreadInfo | MiniDumpWithUnloadedModules), &information, nullptr, nullptr);
|
||
writeNumber(file, "minidump_written=", success ? 1 : 0);
|
||
FlushFileBuffers(dumpFile);
|
||
CloseHandle(dumpFile);
|
||
}
|
||
}
|
||
if (capturedContext)
|
||
{
|
||
writeWindowsRegisters(file, *capturedContext);
|
||
writeText(file, "Faulting instruction:\n");
|
||
const DWORD64 faultAddress = record && record->ExceptionAddress
|
||
? reinterpret_cast<DWORD64>(record->ExceptionAddress)
|
||
: 0;
|
||
if (faultAddress != 0)
|
||
{
|
||
writeWindowsFrame(file, faultAddress);
|
||
}
|
||
writeWindowsStack(file, *capturedContext);
|
||
}
|
||
else
|
||
{
|
||
writeText(file, "Faulting thread stack unavailable\n");
|
||
}
|
||
FlushFileBuffers(file);
|
||
CloseHandle(file);
|
||
}
|
||
|
||
bool isFatalWindowsException(DWORD code)
|
||
{
|
||
switch (code)
|
||
{
|
||
case EXCEPTION_ACCESS_VIOLATION:
|
||
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
|
||
case EXCEPTION_DATATYPE_MISALIGNMENT:
|
||
case EXCEPTION_FLT_DIVIDE_BY_ZERO:
|
||
case EXCEPTION_FLT_INVALID_OPERATION:
|
||
case EXCEPTION_ILLEGAL_INSTRUCTION:
|
||
case EXCEPTION_IN_PAGE_ERROR:
|
||
case EXCEPTION_INT_DIVIDE_BY_ZERO:
|
||
case EXCEPTION_INT_OVERFLOW:
|
||
case EXCEPTION_NONCONTINUABLE_EXCEPTION:
|
||
case EXCEPTION_STACK_OVERFLOW:
|
||
return true;
|
||
default:
|
||
return false;
|
||
}
|
||
}
|
||
|
||
LONG CALLBACK windowsVectoredExceptionHandler(EXCEPTION_POINTERS* exceptionInfo)
|
||
{
|
||
if (exceptionInfo && exceptionInfo->ExceptionRecord &&
|
||
isFatalWindowsException(exceptionInfo->ExceptionRecord->ExceptionCode))
|
||
{
|
||
writeWindowsReport(exceptionInfo, 0);
|
||
}
|
||
return EXCEPTION_CONTINUE_SEARCH;
|
||
}
|
||
|
||
LONG WINAPI windowsExceptionHandler(EXCEPTION_POINTERS* exceptionInfo)
|
||
{
|
||
writeWindowsReport(exceptionInfo, 0);
|
||
return EXCEPTION_EXECUTE_HANDLER;
|
||
}
|
||
|
||
void windowsSignalHandler(int signalNumber)
|
||
{
|
||
CONTEXT context = {};
|
||
RtlCaptureContext(&context);
|
||
EXCEPTION_RECORD record = {};
|
||
record.ExceptionCode = 0xe0000000U | static_cast<DWORD>(signalNumber);
|
||
EXCEPTION_POINTERS information = {&record, &context};
|
||
writeWindowsReport(&information, signalNumber);
|
||
TerminateProcess(GetCurrentProcess(), static_cast<UINT>(128 + signalNumber));
|
||
}
|
||
|
||
const int fatalSignals[] = {SIGABRT, SIGSEGV, SIGILL, SIGFPE};
|
||
using SignalHandler = void (*)(int);
|
||
#else
|
||
void prepareUnwinder()
|
||
{
|
||
#if defined(__GLIBC__)
|
||
// 提前加载 libgcc 展开器,减少首次崩溃时的动态加载/内存分配。
|
||
void* frames[1];
|
||
backtrace(frames, 1);
|
||
#endif
|
||
}
|
||
|
||
void writeLinuxContext(NativeFile file, void* rawContext)
|
||
{
|
||
if (!rawContext)
|
||
{
|
||
return;
|
||
}
|
||
const ucontext_t* context = static_cast<const ucontext_t*>(rawContext);
|
||
#if defined(__x86_64__)
|
||
writeNumber(file, "fault_pc=", context->uc_mcontext.gregs[REG_RIP], 16);
|
||
writeNumber(file, "fault_sp=", context->uc_mcontext.gregs[REG_RSP], 16);
|
||
#elif defined(__i386__)
|
||
writeNumber(file, "fault_pc=", context->uc_mcontext.gregs[REG_EIP], 16);
|
||
writeNumber(file, "fault_sp=", context->uc_mcontext.gregs[REG_ESP], 16);
|
||
#elif defined(__aarch64__)
|
||
writeNumber(file, "fault_pc=", context->uc_mcontext.pc, 16);
|
||
writeNumber(file, "fault_sp=", context->uc_mcontext.sp, 16);
|
||
writeNumber(file, "fault_lr=", context->uc_mcontext.regs[30], 16);
|
||
writeNumber(file, "fault_fp=", context->uc_mcontext.regs[29], 16);
|
||
writeNumber(file, "fault_x0=", context->uc_mcontext.regs[0], 16);
|
||
writeNumber(file, "fault_x1=", context->uc_mcontext.regs[1], 16);
|
||
writeNumber(file, "fault_x2=", context->uc_mcontext.regs[2], 16);
|
||
writeNumber(file, "fault_x3=", context->uc_mcontext.regs[3], 16);
|
||
writeNumber(file, "fault_x4=", context->uc_mcontext.regs[4], 16);
|
||
writeNumber(file, "fault_x5=", context->uc_mcontext.regs[5], 16);
|
||
writeNumber(file, "fault_x6=", context->uc_mcontext.regs[6], 16);
|
||
writeNumber(file, "fault_x7=", context->uc_mcontext.regs[7], 16);
|
||
writeNumber(file, "fault_pstate=", context->uc_mcontext.pstate, 16);
|
||
#elif defined(__arm__)
|
||
writeNumber(file, "fault_pc=", context->uc_mcontext.arm_pc, 16);
|
||
writeNumber(file, "fault_sp=", context->uc_mcontext.arm_sp, 16);
|
||
writeNumber(file, "fault_lr=", context->uc_mcontext.arm_lr, 16);
|
||
writeNumber(file, "fault_fp=", context->uc_mcontext.arm_fp, 16);
|
||
writeNumber(file, "fault_r0=", context->uc_mcontext.arm_r0, 16);
|
||
writeNumber(file, "fault_r1=", context->uc_mcontext.arm_r1, 16);
|
||
writeNumber(file, "fault_r2=", context->uc_mcontext.arm_r2, 16);
|
||
writeNumber(file, "fault_r3=", context->uc_mcontext.arm_r3, 16);
|
||
writeNumber(file, "fault_r12=", context->uc_mcontext.arm_ip, 16);
|
||
writeNumber(file, "fault_cpsr=", context->uc_mcontext.arm_cpsr, 16);
|
||
#endif
|
||
}
|
||
|
||
void writeLinuxArchitecture(NativeFile file)
|
||
{
|
||
#if defined(__aarch64__)
|
||
writeText(file, "architecture=arm64\n");
|
||
#elif defined(__arm__)
|
||
writeText(file, "architecture=arm32\n");
|
||
#elif defined(__x86_64__)
|
||
writeText(file, "architecture=x64\n");
|
||
#elif defined(__i386__)
|
||
writeText(file, "architecture=x86\n");
|
||
#else
|
||
writeText(file, "architecture=unknown\n");
|
||
#endif
|
||
}
|
||
|
||
void writeLinuxFaultFrames(NativeFile file, void* rawContext)
|
||
{
|
||
if (!rawContext)
|
||
{
|
||
return;
|
||
}
|
||
const ucontext_t* context = static_cast<const ucontext_t*>(rawContext);
|
||
writeText(file, "Fault registers used for symbol lookup:\n");
|
||
#if defined(__aarch64__)
|
||
writeNumber(file, " fault_frame_pc=", context->uc_mcontext.pc, 16);
|
||
writeNumber(file, " fault_frame_lr=", context->uc_mcontext.regs[30], 16);
|
||
writeNumber(file, " fault_frame_fp=", context->uc_mcontext.regs[29], 16);
|
||
#elif defined(__arm__)
|
||
writeNumber(file, " fault_frame_pc=", context->uc_mcontext.arm_pc, 16);
|
||
writeNumber(file, " fault_frame_lr=", context->uc_mcontext.arm_lr, 16);
|
||
writeNumber(file, " fault_frame_fp=", context->uc_mcontext.arm_fp, 16);
|
||
#elif defined(__x86_64__)
|
||
writeNumber(file, " fault_frame_pc=", context->uc_mcontext.gregs[REG_RIP], 16);
|
||
#elif defined(__i386__)
|
||
writeNumber(file, " fault_frame_pc=", context->uc_mcontext.gregs[REG_EIP], 16);
|
||
#endif
|
||
}
|
||
|
||
void linuxSignalHandler(int signalNumber, siginfo_t* information, void* context)
|
||
{
|
||
if (handlingCrash.test_and_set())
|
||
{
|
||
_exit(128 + signalNumber);
|
||
}
|
||
OutputTarget* target = outputTarget.load(std::memory_order_acquire);
|
||
char stackPath[PATH_MAX] = {};
|
||
struct timespec time = {};
|
||
clock_gettime(CLOCK_REALTIME, &time);
|
||
struct tm localTime = {};
|
||
localtime_r(&time.tv_sec, &localTime);
|
||
if (target)
|
||
{
|
||
std::snprintf(stackPath, sizeof(stackPath),
|
||
"%s/crash_stack_%04d%02d%02d_%02d%02d%02d_%03ld.txt",
|
||
target->directory.c_str(), localTime.tm_year + 1900, localTime.tm_mon + 1,
|
||
localTime.tm_mday, localTime.tm_hour, localTime.tm_min, localTime.tm_sec,
|
||
time.tv_nsec / 1000000L);
|
||
}
|
||
NativeFile file = target ? open(stackPath, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0600) : invalidFile;
|
||
const bool ownsFile = file != invalidFile;
|
||
if (!ownsFile)
|
||
{
|
||
file = STDERR_FILENO;
|
||
}
|
||
writeText(file, "VrUtils crash report\n");
|
||
writeLinuxArchitecture(file);
|
||
writeNumber(file, "pid=", getpid());
|
||
writeNumber(file, "tid=", static_cast<uint64_t>(syscall(SYS_gettid)));
|
||
writeNumber(file, "unix_time=", static_cast<uint64_t>(time.tv_sec));
|
||
writeNumber(file, "signal=", signalNumber);
|
||
if (information)
|
||
{
|
||
writeNumber(file, "si_code_unsigned=", static_cast<unsigned int>(information->si_code));
|
||
if (information->si_code > 0 && signalNumber != SIGABRT)
|
||
{
|
||
writeNumber(file, "fault_address=", reinterpret_cast<uintptr_t>(information->si_addr), 16);
|
||
}
|
||
}
|
||
writeLinuxContext(file, context);
|
||
writeLinuxFaultFrames(file, context);
|
||
writeText(file, "Faulting instruction address is fault_pc above; resolve it against the maps below.\n");
|
||
|
||
// 路径、加载地址和文件偏移用于离线解析 PIE/共享库中的地址。
|
||
writeText(file, "Memory maps (/proc/self/maps):\n");
|
||
const int maps = open("/proc/self/maps", O_RDONLY | O_CLOEXEC);
|
||
if (maps >= 0)
|
||
{
|
||
char buffer[4096];
|
||
ssize_t size = 0;
|
||
while ((size = read(maps, buffer, sizeof(buffer))) > 0)
|
||
{
|
||
writeBytes(file, buffer, static_cast<size_t>(size));
|
||
}
|
||
close(maps);
|
||
}
|
||
if (ownsFile)
|
||
{
|
||
fsync(file);
|
||
}
|
||
writeText(file, "Faulting thread backtrace (includes signal trampoline/handler):\n");
|
||
#if defined(__GLIBC__)
|
||
// backtrace 并非 POSIX 异步信号安全接口,只尽力展开;原始 PC 和映射已先写入。
|
||
void* frames[maxFrames];
|
||
const int count = backtrace(frames, maxFrames);
|
||
for (int index = 0; index < count; ++index)
|
||
{
|
||
writeNumber(file, "frame=", index);
|
||
writeNumber(file, " pc=", reinterpret_cast<uintptr_t>(frames[index]), 16);
|
||
}
|
||
backtrace_symbols_fd(frames, count, file);
|
||
#else
|
||
writeText(file, "Unwinder unavailable on this libc; use fault_pc and memory maps.\n");
|
||
#endif
|
||
if (ownsFile)
|
||
{
|
||
fsync(file);
|
||
close(file);
|
||
}
|
||
// 恢复默认动作并给当前故障线程重发信号,保留 core dump 和信号退出状态。
|
||
struct sigaction action = {};
|
||
action.sa_handler = SIG_DFL;
|
||
sigemptyset(&action.sa_mask);
|
||
sigaction(signalNumber, &action, nullptr);
|
||
sigset_t mask;
|
||
sigemptyset(&mask);
|
||
sigaddset(&mask, signalNumber);
|
||
sigprocmask(SIG_UNBLOCK, &mask, nullptr);
|
||
raise(signalNumber);
|
||
_exit(128 + signalNumber);
|
||
}
|
||
|
||
const int fatalSignals[] = {SIGSEGV, SIGABRT, SIGILL, SIGFPE, SIGBUS};
|
||
#endif
|
||
|
||
void terminateHandler()
|
||
{
|
||
// 未捕获 C++ 异常最终进入 SIGABRT,保留 terminate 调用链。
|
||
std::abort();
|
||
}
|
||
|
||
struct HandlerState
|
||
{
|
||
std::mutex mutex;
|
||
bool enabled = false;
|
||
std::terminate_handler previousTerminate = nullptr;
|
||
std::vector<std::unique_ptr<OutputTarget>> targets;
|
||
#ifdef _WIN32
|
||
LPTOP_LEVEL_EXCEPTION_FILTER previousException = nullptr;
|
||
PVOID vectoredExceptionHandler = nullptr;
|
||
SignalHandler previousSignals[sizeof(fatalSignals) / sizeof(fatalSignals[0])] = {};
|
||
#else
|
||
struct sigaction previousSignals[sizeof(fatalSignals) / sizeof(fatalSignals[0])] = {};
|
||
#endif
|
||
};
|
||
|
||
HandlerState& handlerState()
|
||
{
|
||
// 不参与静态析构,程序退出阶段崩溃时路径和处理器仍有效。
|
||
static HandlerState* state = new HandlerState;
|
||
return *state;
|
||
}
|
||
|
||
#ifdef _WIN32
|
||
std::wstring fromUtf8(const std::string& text)
|
||
{
|
||
const int count = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, text.c_str(), -1, nullptr, 0);
|
||
if (count <= 0)
|
||
{
|
||
return std::wstring();
|
||
}
|
||
std::wstring result(static_cast<size_t>(count), L'\0');
|
||
MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, text.c_str(), -1, &result[0], count);
|
||
result.resize(static_cast<size_t>(count - 1));
|
||
return result;
|
||
}
|
||
|
||
bool createDirectories(const std::wstring& path)
|
||
{
|
||
const DWORD attributes = GetFileAttributesW(path.c_str());
|
||
if (attributes != INVALID_FILE_ATTRIBUTES)
|
||
{
|
||
return (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
|
||
}
|
||
const size_t slash = path.find_last_of(L"\\/");
|
||
if (slash != std::wstring::npos && slash > 0 && !createDirectories(path.substr(0, slash)))
|
||
{
|
||
return false;
|
||
}
|
||
return CreateDirectoryW(path.c_str(), nullptr) != FALSE || GetLastError() == ERROR_ALREADY_EXISTS;
|
||
}
|
||
#else
|
||
bool createDirectories(const std::string& path)
|
||
{
|
||
struct stat information = {};
|
||
if (stat(path.c_str(), &information) == 0)
|
||
{
|
||
return S_ISDIR(information.st_mode);
|
||
}
|
||
const size_t slash = path.find_last_of('/');
|
||
if (slash != std::string::npos && slash > 0 && !createDirectories(path.substr(0, slash)))
|
||
{
|
||
return false;
|
||
}
|
||
return mkdir(path.c_str(), 0700) == 0 || errno == EEXIST;
|
||
}
|
||
#endif
|
||
|
||
std::unique_ptr<OutputTarget> prepareTarget(const std::string& directory)
|
||
{
|
||
if (directory.find('\0') != std::string::npos)
|
||
{
|
||
return nullptr;
|
||
}
|
||
std::unique_ptr<OutputTarget> target(new OutputTarget);
|
||
#ifdef _WIN32
|
||
const std::wstring requested = fromUtf8(directory);
|
||
wchar_t absolutePath[32768] = {};
|
||
const DWORD length = GetFullPathNameW(requested.c_str(), 32768, absolutePath, nullptr);
|
||
if (requested.empty() || length == 0 || length >= 32768 || !createDirectories(absolutePath))
|
||
{
|
||
return nullptr;
|
||
}
|
||
target->directoryPath = absolutePath;
|
||
const int count = WideCharToMultiByte(CP_UTF8, 0, absolutePath, -1, nullptr, 0, nullptr, nullptr);
|
||
if (count <= 0)
|
||
{
|
||
return nullptr;
|
||
}
|
||
target->directory.resize(static_cast<size_t>(count));
|
||
WideCharToMultiByte(CP_UTF8, 0, absolutePath, -1, &target->directory[0], count, nullptr, nullptr);
|
||
target->directory.resize(static_cast<size_t>(count - 1));
|
||
#else
|
||
if (!createDirectories(directory))
|
||
{
|
||
return nullptr;
|
||
}
|
||
char* absolutePath = realpath(directory.c_str(), nullptr);
|
||
if (!absolutePath)
|
||
{
|
||
return nullptr;
|
||
}
|
||
target->directory = absolutePath;
|
||
free(absolutePath);
|
||
#endif
|
||
return target;
|
||
}
|
||
#endif
|
||
}
|
||
|
||
void VrCrashHandler::Initialize()
|
||
{
|
||
// 多个入口只安装一次,后续主动关闭不会被重复初始化重新开启。
|
||
static const bool initialized = []()
|
||
{
|
||
// Lambda has no implicit this pointer; qualify the public function explicitly.
|
||
const bool enabled = VrCrashHandler::Enable();
|
||
if (!enabled)
|
||
{
|
||
std::fputs("[VrUtils] failed to enable crash reporting\n", stderr);
|
||
}
|
||
return enabled;
|
||
}();
|
||
static_cast<void>(initialized);
|
||
}
|
||
|
||
bool VrCrashHandler::Enable(const std::string& outputDirectory)
|
||
{
|
||
#if defined(_WIN32) || defined(__linux__)
|
||
// 回调中的原子访问必须无锁,不能在信号打断时进入库内部互斥量。
|
||
if (!outputTarget.is_lock_free())
|
||
{
|
||
return false;
|
||
}
|
||
HandlerState& state = handlerState();
|
||
std::lock_guard<std::mutex> lock(state.mutex);
|
||
OutputTarget* target = outputTarget.load();
|
||
const std::string directory = outputDirectory.empty()
|
||
? (target ? target->directory : VrPathUtils::GetLogDirectory() + "/crash") : outputDirectory;
|
||
if (!target || directory != target->directory)
|
||
{
|
||
std::unique_ptr<OutputTarget> prepared = prepareTarget(directory);
|
||
if (!prepared)
|
||
{
|
||
return false;
|
||
}
|
||
target = prepared.get();
|
||
state.targets.push_back(std::move(prepared));
|
||
}
|
||
if (state.enabled)
|
||
{
|
||
outputTarget.store(target, std::memory_order_release);
|
||
return true;
|
||
}
|
||
prepareUnwinder();
|
||
outputTarget.store(target, std::memory_order_release);
|
||
handlingCrash.clear();
|
||
size_t installed = 0;
|
||
for (; installed < sizeof(fatalSignals) / sizeof(fatalSignals[0]); ++installed)
|
||
{
|
||
#ifdef _WIN32
|
||
state.previousSignals[installed] = std::signal(fatalSignals[installed], windowsSignalHandler);
|
||
if (state.previousSignals[installed] == SIG_ERR)
|
||
{
|
||
break;
|
||
}
|
||
#else
|
||
struct sigaction action = {};
|
||
action.sa_sigaction = linuxSignalHandler;
|
||
sigemptyset(&action.sa_mask);
|
||
action.sa_flags = SA_SIGINFO | SA_RESTART;
|
||
if (sigaction(fatalSignals[installed], &action, &state.previousSignals[installed]) != 0)
|
||
{
|
||
break;
|
||
}
|
||
#endif
|
||
}
|
||
if (installed != sizeof(fatalSignals) / sizeof(fatalSignals[0]))
|
||
{
|
||
while (installed > 0)
|
||
{
|
||
--installed;
|
||
#ifdef _WIN32
|
||
std::signal(fatalSignals[installed], state.previousSignals[installed]);
|
||
#else
|
||
sigaction(fatalSignals[installed], &state.previousSignals[installed], nullptr);
|
||
#endif
|
||
}
|
||
return false;
|
||
}
|
||
#ifdef _WIN32
|
||
state.vectoredExceptionHandler = AddVectoredExceptionHandler(1, windowsVectoredExceptionHandler);
|
||
if (!state.vectoredExceptionHandler)
|
||
{
|
||
while (installed > 0)
|
||
{
|
||
--installed;
|
||
std::signal(fatalSignals[installed], state.previousSignals[installed]);
|
||
}
|
||
return false;
|
||
}
|
||
state.previousException = SetUnhandledExceptionFilter(windowsExceptionHandler);
|
||
#endif
|
||
state.previousTerminate = std::set_terminate(terminateHandler);
|
||
state.enabled = true;
|
||
return true;
|
||
#else
|
||
(void)outputDirectory;
|
||
return false;
|
||
#endif
|
||
}
|
||
|
||
void VrCrashHandler::Disable()
|
||
{
|
||
#if defined(_WIN32) || defined(__linux__)
|
||
HandlerState& state = handlerState();
|
||
std::lock_guard<std::mutex> lock(state.mutex);
|
||
if (!state.enabled)
|
||
{
|
||
return;
|
||
}
|
||
if (std::get_terminate() == terminateHandler)
|
||
{
|
||
std::set_terminate(state.previousTerminate);
|
||
}
|
||
#ifdef _WIN32
|
||
if (state.vectoredExceptionHandler)
|
||
{
|
||
RemoveVectoredExceptionHandler(state.vectoredExceptionHandler);
|
||
state.vectoredExceptionHandler = nullptr;
|
||
}
|
||
const auto currentException = SetUnhandledExceptionFilter(state.previousException);
|
||
if (currentException != windowsExceptionHandler)
|
||
{
|
||
SetUnhandledExceptionFilter(currentException);
|
||
}
|
||
#endif
|
||
for (size_t index = 0; index < sizeof(fatalSignals) / sizeof(fatalSignals[0]); ++index)
|
||
{
|
||
// 若第三方后来接管了处理器,不覆盖它的配置。
|
||
#ifdef _WIN32
|
||
const auto current = std::signal(fatalSignals[index], state.previousSignals[index]);
|
||
if (current != windowsSignalHandler && current != SIG_ERR)
|
||
{
|
||
std::signal(fatalSignals[index], current);
|
||
}
|
||
#else
|
||
struct sigaction current = {};
|
||
if (sigaction(fatalSignals[index], nullptr, ¤t) == 0 &&
|
||
(current.sa_flags & SA_SIGINFO) && current.sa_sigaction == linuxSignalHandler)
|
||
{
|
||
sigaction(fatalSignals[index], &state.previousSignals[index], nullptr);
|
||
}
|
||
#endif
|
||
}
|
||
state.enabled = false;
|
||
#endif
|
||
}
|
||
|
||
bool VrCrashHandler::IsEnabled()
|
||
{
|
||
#if defined(_WIN32) || defined(__linux__)
|
||
HandlerState& state = handlerState();
|
||
std::lock_guard<std::mutex> lock(state.mutex);
|
||
return state.enabled;
|
||
#else
|
||
return false;
|
||
#endif
|
||
}
|
||
|
||
std::string VrCrashHandler::GetOutputDirectory()
|
||
{
|
||
#if defined(_WIN32) || defined(__linux__)
|
||
OutputTarget* target = outputTarget.load(std::memory_order_acquire);
|
||
return target ? target->directory : std::string();
|
||
#else
|
||
return std::string();
|
||
#endif
|
||
}
|
||
|
||
namespace
|
||
{
|
||
// 对象文件被链接后独立自动启用,不依赖 VrLog 的初始化顺序。
|
||
VrCrashHandler::AutoInitializer crashHandlerAutoInitializer;
|
||
}
|