From fc7650ebc4eee8a9d3e60910d1a6d3a798fb9f92 Mon Sep 17 00:00:00 2001 From: Status102 <102887808+status102@users.noreply.github.com> Date: Wed, 29 Oct 2025 02:36:46 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=AE=B0=E5=BD=95crash=20log=E6=97=B6?= =?UTF-8?q?=E5=90=8C=E6=97=B6=E8=AE=B0=E5=BD=95stacktrace,=20=E5=B9=B6?= =?UTF-8?q?=E5=85=81=E8=AE=B8=20ASST=5FDEBUG=20=E5=9C=A8=20debug=20?= =?UTF-8?q?=E7=9B=AE=E5=BD=95=E4=B8=8B=E7=94=9F=E6=88=90=20crash.log=20(#1?= =?UTF-8?q?4526)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 记录crash log时同时记录stacktrace, 并允许 ASST_DEBUG 在 debug 目录下生成 crash.log * fix: 加个循环次数限制 * perf: 简单整理 --- src/MaaCore/Utils/ExceptionStacktrace.hpp | 310 ++++++++++++++++++++++ src/MaaCore/Utils/Logger.hpp | 69 ++++- 2 files changed, 367 insertions(+), 12 deletions(-) create mode 100644 src/MaaCore/Utils/ExceptionStacktrace.hpp diff --git a/src/MaaCore/Utils/ExceptionStacktrace.hpp b/src/MaaCore/Utils/ExceptionStacktrace.hpp new file mode 100644 index 0000000000..e26d921a21 --- /dev/null +++ b/src/MaaCore/Utils/ExceptionStacktrace.hpp @@ -0,0 +1,310 @@ +#pragma once + +#ifdef _WIN32 +#include +#include +#define NOMINMAX +#include +#include +#include +#pragma comment(lib, "dbghelp.lib") +#undef NOMINMAX +#elif defined(__unix__) || defined(__APPLE__) +#include +#include +#include +#include +#endif + +#include +#include +#include + +#include "Logger.hpp" + +namespace asst::utils +{ +class ExceptionStacktrace +{ +public: +#ifdef _WIN32 + // Windows 异常堆栈跟踪实现 + static std::string capture_exception_stack_trace(PEXCEPTION_POINTERS pExceptionInfo) + { + std::string result; + + HANDLE process = GetCurrentProcess(); + HANDLE thread = GetCurrentThread(); + + // 初始化符号处理器 + SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES); + if (!SymInitialize(process, NULL, TRUE)) { + return "Failed to initialize symbol handler"; + } + + // 设置上下文 + CONTEXT* context = pExceptionInfo->ContextRecord; + STACKFRAME64 frame = {}; + DWORD imageType; + +#ifdef _M_IX86 + imageType = IMAGE_FILE_MACHINE_I386; + frame.AddrPC.Offset = context->Eip; + frame.AddrPC.Mode = AddrModeFlat; + frame.AddrFrame.Offset = context->Ebp; + frame.AddrFrame.Mode = AddrModeFlat; + frame.AddrStack.Offset = context->Esp; + frame.AddrStack.Mode = AddrModeFlat; +#elif _M_X64 + imageType = IMAGE_FILE_MACHINE_AMD64; + frame.AddrPC.Offset = context->Rip; + frame.AddrPC.Mode = AddrModeFlat; + frame.AddrFrame.Offset = context->Rsp; + frame.AddrFrame.Mode = AddrModeFlat; + frame.AddrStack.Offset = context->Rsp; + frame.AddrStack.Mode = AddrModeFlat; +#elif _M_IA64 + imageType = IMAGE_FILE_MACHINE_IA64; + frame.AddrPC.Offset = context->StIIP; + frame.AddrPC.Mode = AddrModeFlat; + frame.AddrFrame.Offset = context->IntSp; + frame.AddrFrame.Mode = AddrModeFlat; + frame.AddrBStore.Offset = context->RsBSP; + frame.AddrBStore.Mode = AddrModeFlat; + frame.AddrStack.Offset = context->IntSp; + frame.AddrStack.Mode = AddrModeFlat; +#else + return "Unsupported platform for stack walking"; +#endif + + // 分配符号信息结构 + constexpr int MAX_NAME_LEN = 256; + BYTE symbolBuffer[sizeof(SYMBOL_INFO) + MAX_NAME_LEN]; + PSYMBOL_INFO symbol = (PSYMBOL_INFO)symbolBuffer; + symbol->SizeOfStruct = sizeof(SYMBOL_INFO); + symbol->MaxNameLen = MAX_NAME_LEN; + + // 行号信息 + IMAGEHLP_LINE64 line = {}; + line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + + result += std::format("Exception Code: 0x{:08X}\n", pExceptionInfo->ExceptionRecord->ExceptionCode); + result += + std::format("Exception Address: 0x{:016X}\n", (ULONG_PTR)pExceptionInfo->ExceptionRecord->ExceptionAddress); + result += "Call Stack:\n"; + + int frameNum = 0; + while (StackWalk64( + imageType, + process, + thread, + &frame, + context, + NULL, + SymFunctionTableAccess64, + SymGetModuleBase64, + NULL)) { + if (frame.AddrPC.Offset == 0) { + break; + } + + // 获取符号信息 + DWORD64 displacement = 0; + bool hasSymbol = SymFromAddr(process, frame.AddrPC.Offset, &displacement, symbol); + + // 获取行号信息 + DWORD lineDisplacement = 0; + bool hasLine = SymGetLineFromAddr64(process, frame.AddrPC.Offset, &lineDisplacement, &line); + + // 获取模块信息 + IMAGEHLP_MODULE64 moduleInfo = {}; + moduleInfo.SizeOfStruct = sizeof(moduleInfo); + bool hasModule = SymGetModuleInfo64(process, frame.AddrPC.Offset, &moduleInfo); + + result += std::format(" #{:2}: ", frameNum); + + if (hasSymbol) { + result += std::format("{}+0x{:X}", symbol->Name, displacement); + } + else { + result += ""; + } + + if (hasLine) { + result += std::format(" at {}:{}", line.FileName, line.LineNumber); + } + + if (hasModule) { + result += std::format(" [{}]", moduleInfo.ModuleName); + } + + result += std::format(" (0x{:016X})\n", frame.AddrPC.Offset); + + frameNum++; + if (frameNum > 64) { // 防止无限循环 + break; + } + } + + SymCleanup(process); + return result; + } + + // 在异常抛出点捕获堆栈跟踪 + static std::string capture_current_stack_trace() + { + std::string result; + + HANDLE process = GetCurrentProcess(); + + // 初始化符号处理器 + SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES); + if (!SymInitialize(process, NULL, TRUE)) { + return "Failed to initialize symbol handler"; + } + + // 捕获当前堆栈 + void* stack[64]; + USHORT frames = CaptureStackBackTrace(0, 64, stack, NULL); + + // 分配符号信息结构 + constexpr int MAX_NAME_LEN = 256; + BYTE symbolBuffer[sizeof(SYMBOL_INFO) + MAX_NAME_LEN]; + PSYMBOL_INFO symbol = (PSYMBOL_INFO)symbolBuffer; + symbol->SizeOfStruct = sizeof(SYMBOL_INFO); + symbol->MaxNameLen = MAX_NAME_LEN; + + // 行号信息 + IMAGEHLP_LINE64 line = {}; + line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + + result += "Current Stack Trace:\n"; + + for (USHORT i = 0; i < frames && i < 64; i++) { + result += std::format(" #{:2}: ", i); + DWORD64 address = (DWORD64)(stack[i]); + + // 获取符号信息 + DWORD64 displacement = 0; + bool hasSymbol = SymFromAddr(process, address, &displacement, symbol); + if (hasSymbol) { + result += std::format("{}+0x{:X}", symbol->Name, displacement); + } + else { + result += ""; + } + + // 获取行号信息 + DWORD lineDisplacement = 0; + bool hasLine = SymGetLineFromAddr64(process, address, &lineDisplacement, &line); + if (hasLine) { + result += std::format(" at {}:{}", line.FileName, line.LineNumber); + } + + // 获取模块信息 + IMAGEHLP_MODULE64 moduleInfo = {}; + moduleInfo.SizeOfStruct = sizeof(moduleInfo); + bool hasModule = SymGetModuleInfo64(process, address, &moduleInfo); + if (hasModule) { + result += std::format(" [{}]", moduleInfo.ModuleName); + } + + result += std::format(" (0x{:016X})\n", address); + } + + SymCleanup(process); + return result; + } + +#elif defined(__unix__) || defined(__APPLE__) + // Unix/Linux/macOS 堆栈跟踪实现 + static std::string capture_current_stack_trace() + { + std::string result; + result += "Current Stack Trace:\n"; + + // 捕获堆栈帧 + constexpr int MAX_FRAMES = 64; + void* buffer[MAX_FRAMES]; + int frames = backtrace(buffer, MAX_FRAMES); + + if (frames <= 0) { + return "Failed to capture stack trace"; + } + + // 获取符号信息 + char** symbols = backtrace_symbols(buffer, frames); + if (!symbols) { + return "Failed to get symbol information"; + } + + // 解析并格式化每一帧 + for (int i = 0; i < frames && i < 64; i++) { + result += std::format(" #{:2}: ", i); + + // 尝试解析符号名称 + std::string frame_info = demangle_symbol(symbols[i], buffer[i]); + result += frame_info; + result += "\n"; + } + + free(symbols); + return result; + } + +private: + // 辅助函数:解析和 demangle 符号名称 + static std::string demangle_symbol(const char* symbol_raw, void* address) + { + std::string result; + + // 尝试使用 dladdr 获取更详细的信息 + Dl_info info; + if (dladdr(address, &info)) { + // 获取模块名称 + if (info.dli_fname) { + const char* fname = std::strrchr(info.dli_fname, '/'); + result += fname ? (fname + 1) : info.dli_fname; + result += " "; + } + + // 获取函数名称并 demangle + if (info.dli_sname) { + int status = 0; + char* demangled = abi::__cxa_demangle(info.dli_sname, nullptr, nullptr, &status); + + if (status == 0 && demangled) { + result += demangled; + free(demangled); + } + else { + result += info.dli_sname; + } + + // 计算偏移量 + if (info.dli_saddr) { + ptrdiff_t offset = static_cast(address) - static_cast(info.dli_saddr); + result += std::format("+0x{:X}", static_cast(offset)); + } + } + else { + result += ""; + } + + // 添加地址 + result += std::format(" (0x{:016X})", reinterpret_cast(address)); + } + else { + // 如果 dladdr 失败,使用原始符号信息 + result = symbol_raw; + } + + return result; + } + +#else + // 不支持的平台 + static std::string capture_current_stack_trace() { return "Stack trace not supported on this platform"; } +#endif +}; +} // namespace asst::utils diff --git a/src/MaaCore/Utils/Logger.hpp b/src/MaaCore/Utils/Logger.hpp index f791bcaf4e..77311ec463 100644 --- a/src/MaaCore/Utils/Logger.hpp +++ b/src/MaaCore/Utils/Logger.hpp @@ -24,6 +24,7 @@ #include "Platform.hpp" #include "SingletonHolder.hpp" #include "Time.hpp" +#include "Utils/ExceptionStacktrace.hpp" #include "WorkingDir.hpp" #if defined(__APPLE__) || defined(__linux__) @@ -736,9 +737,7 @@ private: m_buff(nullptr), m_of(&m_buff) { -#ifndef ASST_DEBUG initialize_exception_handlers(); -#endif try { std::filesystem::create_directories(m_log_path.parent_path()); @@ -830,17 +829,23 @@ private: trace("-----------------------------"); } -#ifndef ASST_DEBUG - inline static std::atomic g_last_signal_reason { nullptr }; -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable: 4996) -#endif static void write_crash_file(const char* reason, const char* detail = nullptr) noexcept { - FILE* f = fopen("crash.log", "a"); + FILE* f = nullptr; +#ifdef ASST_DEBUG + auto path = (UserDir.get() / "debug" / "crash.log").string(); +#else + auto path = (UserDir.get() / "crash.log").string(); +#endif // ASST_DEBUG +#ifdef _WIN32 + if (fopen_s(&f, path.c_str(), "a") != 0) { + return; + } +#else + f = fopen(path.c_str(), "a"); +#endif // _WIN32 if (!f) { return; } @@ -852,10 +857,32 @@ private: fprintf(f, "Detail: %s\n", detail); } fprintf(f, "===================\n\n"); + fflush(f); fclose(f); } -#ifdef _MSC_VER -#pragma warning(pop) + +#ifdef _WIN32 + // SEH 未处理异常过滤器 + static LONG WINAPI unhandled_exception_filter(PEXCEPTION_POINTERS pExceptionInfo) + { + try { + auto& logger = Logger::get_instance(); + std::string exception_details = utils::ExceptionStacktrace::capture_exception_stack_trace(pExceptionInfo); + logger.error("=== UNHANDLED EXCEPTION ==="); + logger.error("Exception details with stack trace:"); + logger.error(exception_details); + logger.error("============================"); + logger.flush(); + write_crash_file("Unhandled Exception with Stack Trace", exception_details.c_str()); + } + catch (...) { + // 如果日志记录失败,直接写入文件 + write_crash_file("Unhandled Exception", "Failed to capture stack trace"); + } + + // 返回 EXCEPTION_EXECUTE_HANDLER 让程序正常终止 + return EXCEPTION_CONTINUE_EXECUTION; + } #endif static void custom_terminate_handler() noexcept @@ -879,20 +906,28 @@ private: // 再处理 C++ 异常 std::string exception_info = "Unknown exception"; + std::string stack_trace; if (auto eptr = std::current_exception()) { try { std::rethrow_exception(eptr); } catch (const std::exception& e) { exception_info = std::string("std::exception: ") + e.what() + " (type: " + typeid(e).name() + ")"; + stack_trace = utils::ExceptionStacktrace::capture_current_stack_trace(); } catch (...) { exception_info = "Unknown exception type"; + stack_trace = utils::ExceptionStacktrace::capture_current_stack_trace(); } } logger.error("=== FATAL ERROR ==="); logger.error("Unhandled exception caught:", exception_info); + if (!stack_trace.empty()) { + logger.error("Exception stack trace:"); + logger.error(stack_trace); + write_crash_file("Unhandled exception stack trace:", stack_trace.c_str()); + } logger.error("Program terminating..."); logger.error("==================="); logger.flush(); @@ -933,12 +968,22 @@ private: static void initialize_exception_handlers() { +#ifdef _WIN32 + // Windows: 设置未处理异常过滤器 + SetUnhandledExceptionFilter(unhandled_exception_filter); +#endif + std::signal(SIGSEGV, signal_handler); std::signal(SIGABRT, signal_handler); std::signal(SIGFPE, signal_handler); std::signal(SIGILL, signal_handler); +#ifdef ASST_DEBUG + const auto& path = UserDir.get() / "debug" / "crash.log"; + if (std::filesystem::exists(path)) { + std::filesystem::remove(path); + } +#endif // ASST_DEBUG } -#endif template auto stream(level lv, args_t&&... args)