mirror of
https://github.com/MaaAssistantArknights/MaaAssistantArknights.git
synced 2026-07-16 17:57:01 +08:00
feat: 记录crash log时同时记录stacktrace, 并允许 ASST_DEBUG 在 debug 目录下生成 crash.log (#14526)
* feat: 记录crash log时同时记录stacktrace, 并允许 ASST_DEBUG 在 debug 目录下生成 crash.log * fix: 加个循环次数限制 * perf: 简单整理
This commit is contained in:
310
src/MaaCore/Utils/ExceptionStacktrace.hpp
Normal file
310
src/MaaCore/Utils/ExceptionStacktrace.hpp
Normal file
@@ -0,0 +1,310 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <fcntl.h>
|
||||
#include <io.h>
|
||||
#define NOMINMAX
|
||||
#include <dbghelp.h>
|
||||
#include <windows.h>
|
||||
#include <winsock2.h>
|
||||
#pragma comment(lib, "dbghelp.lib")
|
||||
#undef NOMINMAX
|
||||
#elif defined(__unix__) || defined(__APPLE__)
|
||||
#include <cxxabi.h>
|
||||
#include <dlfcn.h>
|
||||
#include <execinfo.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <format>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#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 += "<unknown>";
|
||||
}
|
||||
|
||||
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 += "<unknown>";
|
||||
}
|
||||
|
||||
// 获取行号信息
|
||||
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<char*>(address) - static_cast<char*>(info.dli_saddr);
|
||||
result += std::format("+0x{:X}", static_cast<size_t>(offset));
|
||||
}
|
||||
}
|
||||
else {
|
||||
result += "<unknown>";
|
||||
}
|
||||
|
||||
// 添加地址
|
||||
result += std::format(" (0x{:016X})", reinterpret_cast<uintptr_t>(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
|
||||
@@ -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<const char*> 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 <typename... args_t>
|
||||
auto stream(level lv, args_t&&... args)
|
||||
|
||||
Reference in New Issue
Block a user