Utils/VrUtils/Src/VrCrashHandler.cpp

998 lines
38 KiB
C++
Raw Normal View History

2026-10-08 14:56:58 +08:00
#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, &current) == 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;
}