#include "VrCrashHandler.h" #include "VrPathUtils.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #ifndef NOMINMAX #define NOMINMAX #endif #include #include #include #elif defined(__linux__) #include #include #include #include #include #include #include #include #if defined(__GLIBC__) #include #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{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(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(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(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(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(static_cast(address)), &memory, sizeof(memory))) { writeNumber(file, " module_offset=", address - reinterpret_cast(memory.AllocationBase), 16); wchar_t modulePath[MAX_PATH] = {}; if (GetModuleFileNameW(static_cast(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(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('0' + index / 10); label[3] = static_cast('0' + index % 10); label[4] = '='; } else { label[2] = static_cast('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(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(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(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(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(signalNumber); EXCEPTION_POINTERS information = {&record, &context}; writeWindowsReport(&information, signalNumber); TerminateProcess(GetCurrentProcess(), static_cast(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(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(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(syscall(SYS_gettid))); writeNumber(file, "unix_time=", static_cast(time.tv_sec)); writeNumber(file, "signal=", signalNumber); if (information) { writeNumber(file, "si_code_unsigned=", static_cast(information->si_code)); if (information->si_code > 0 && signalNumber != SIGABRT) { writeNumber(file, "fault_address=", reinterpret_cast(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)); } 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(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> 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(count), L'\0'); MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, text.c_str(), -1, &result[0], count); result.resize(static_cast(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 prepareTarget(const std::string& directory) { if (directory.find('\0') != std::string::npos) { return nullptr; } std::unique_ptr 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(count)); WideCharToMultiByte(CP_UTF8, 0, absolutePath, -1, &target->directory[0], count, nullptr, nullptr); target->directory.resize(static_cast(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(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 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 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 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 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; }