Utils/VrUtils/Src/VrCrashHandler.cpp

998 lines
38 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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;
}