You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

666 lines
25 KiB
C++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// crash_handler.cpp - 崩溃现场捕获与死因日志实现
//
// 设计约束:崩溃发生时堆/栈可能已损坏,因此处理路径上
// 1) 不使用 std::string / iostream / 动态分配;
// 2) 只用栈缓冲 + vsnprintf + Win32 API 直接写文件并 FlushFileBuffers;
// 3) 用 g_in_handler 防止 handler 内部二次崩溃造成无限递归。
#include "crash_handler.h"
#include <cstdarg>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <ctime>
#include <mutex>
#include <new>
#include <string>
#include <exception>
#include <csignal>
#ifdef _WIN32
#include <windows.h>
#else
#include <unistd.h>
#endif
// 崩溃日志最大单条长度
#define CH_MAX_TEXT 8192
namespace
{
char g_log_dir[512] = "data"; // 崩溃日志根目录
char g_milestone[512] = ""; // 最近一次里程碑说明
char g_exe_name[64] = "main.exe"; // 本程序文件名(生成 addr2line 命令用)
// PE 文件头里的 ImageBase。addr2line 认的是 ImageBase+RVA,不是运行时的加载基址
// (ASLR 下两者不同),直接用崩溃时的虚拟地址会全部解析成 "??"。
uintptr_t g_image_base = 0x140000000;
int g_installed = 0;
int g_in_handler = 0;
std::mutex& milestone_mtx()
{
static std::mutex m; // 函数内静态,保证初始化顺序安全
return m;
}
void now_str(char* buf, size_t cap)
{
std::time_t t = std::time(nullptr);
struct tm tmv;
#ifdef _WIN32
localtime_s(&tmv, &t);
#else
localtime_r(&t, &tmv);
#endif
std::strftime(buf, cap, "%Y-%m-%d %H:%M:%S", &tmv);
}
void stamp_str(char* buf, size_t cap) // YYYYMMDD_HHMMSS(文件名用)
{
std::time_t t = std::time(nullptr);
struct tm tmv;
#ifdef _WIN32
localtime_s(&tmv, &t);
#else
localtime_r(&t, &tmv);
#endif
std::strftime(buf, cap, "%Y%m%d_%H%M%S", &tmv);
}
void date_str(char* buf, size_t cap) // YYYYMMDD(目录名用)
{
std::time_t t = std::time(nullptr);
struct tm tmv;
#ifdef _WIN32
localtime_s(&tmv, &t);
#else
localtime_r(&t, &tmv);
#endif
std::strftime(buf, cap, "%Y%m%d", &tmv);
}
}
// ==================== 日志落盘(多路径回退) ====================
#ifdef _WIN32
namespace
{
// UTF-8 -> UTF-16
int u8_to_w(const char* s, wchar_t* out, int cap)
{
int n = MultiByteToWideChar(CP_UTF8, 0, s, -1, out, cap);
if (n <= 0 && cap > 0) out[0] = L'\0';
return n;
}
bool append_file_w(const wchar_t* wpath, const char* text, size_t len)
{
HANDLE h = CreateFileW(wpath, GENERIC_WRITE, FILE_SHARE_READ,
NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (h == INVALID_HANDLE_VALUE) return false;
LARGE_INTEGER mv; mv.QuadPart = 0;
SetFilePointerEx(h, mv, NULL, FILE_END);
DWORD written = 0;
BOOL ok = WriteFile(h, text, (DWORD)len, &written, NULL);
FlushFileBuffers(h);
CloseHandle(h);
return ok && written > 0;
}
// 依次尝试: <log_dir>/<date>/crash.log -> <exe目录>/crash.log -> <temp>/simiradar_crash.log
// 返回实际写入成功的目录(宽字符,供 minidump 复用);失败返回空。
bool write_crash_text(const char* text, wchar_t* out_dir, int cap)
{
wchar_t path[MAX_PATH * 2];
char date[16];
date_str(date, sizeof(date));
// 1) data/YYYYMMDD/crash.log
wchar_t wdir[MAX_PATH * 2], wdate[32];
u8_to_w(g_log_dir, wdir, MAX_PATH * 2);
u8_to_w(date, wdate, 32);
CreateDirectoryW(wdir, NULL); // 已存在则失败,忽略
_snwprintf(path, MAX_PATH * 2, L"%s\\%s", wdir, wdate);
CreateDirectoryW(path, NULL);
_snwprintf(path, MAX_PATH * 2, L"%s\\%s\\crash.log", wdir, wdate);
if (append_file_w(path, text, std::strlen(text)))
{
_snwprintf(out_dir, cap, L"%s\\%s", wdir, wdate);
return true;
}
// 2) exe 同目录 crash.log(部署机上工作目录常常不是 exe 目录)
wchar_t exe[MAX_PATH];
DWORD n = GetModuleFileNameW(NULL, exe, MAX_PATH);
if (n > 0)
{
for (DWORD i = n - 1; i > 0; --i)
if (exe[i] == L'\\' || exe[i] == L'/') { exe[i] = L'\0'; break; }
_snwprintf(path, MAX_PATH * 2, L"%s\\crash.log", exe);
if (append_file_w(path, text, std::strlen(text)))
{
_snwprintf(out_dir, cap, L"%s", exe);
return true;
}
}
// 3) 系统临时目录
wchar_t tmp[MAX_PATH];
if (GetTempPathW(MAX_PATH, tmp) > 0)
{
_snwprintf(path, MAX_PATH * 2, L"%ssimiradar_crash.log", tmp);
if (append_file_w(path, text, std::strlen(text)))
{
_snwprintf(out_dir, cap, L"%s", tmp);
return true;
}
}
return false;
}
// ---- dbghelp:符号/行号/MiniDump,全部动态加载,缺库也不影响基本记录 ----
typedef BOOL (WINAPI *SymInitialize_t)(HANDLE, PCSTR, BOOL);
typedef BOOL (WINAPI *SymFromAddr_t)(HANDLE, DWORD64, PDWORD64, PVOID);
typedef BOOL (WINAPI *SymGetLineFromAddr64_t)(HANDLE, DWORD64, PDWORD, PVOID);
typedef DWORD (WINAPI *SymSetOptions_t)(DWORD);
typedef BOOL (WINAPI *MiniDumpWriteDump_t)(HANDLE, DWORD, HANDLE, DWORD, PVOID, PVOID, PVOID);
typedef BOOL (WINAPI *SymCleanup_t)(HANDLE);
struct SymPkg // dbghelp.h 的 SYMBOL_INFO(自绘,避免include)
{
ULONG SizeOfStruct;
ULONG TypeIndex;
ULONG64 Reserved[2];
ULONG Index;
ULONG Size;
ULONG64 ModBase;
ULONG Flags;
ULONG64 Value;
ULONG64 Address;
ULONG Register;
ULONG Scope;
ULONG Tag;
ULONG NameLen;
ULONG MaxNameLen;
CHAR Name[1];
};
struct LinePkg // IMAGEHLP_LINE64
{
DWORD SizeOfStruct;
PVOID Key;
DWORD64 LineNumber;
PCHAR FileName;
DWORD64 Address;
};
struct DumpExcInfo // MINIDUMP_EXCEPTION_INFORMATION
{
DWORD ThreadId;
PEXCEPTION_POINTERS ExceptionPointers;
BOOL ClientPointers;
};
HMODULE g_dbg = NULL;
SymInitialize_t g_SymInit = NULL;
SymFromAddr_t g_SymAddr = NULL;
SymGetLineFromAddr64_t g_SymLine = NULL;
SymSetOptions_t g_SymOpt = NULL;
MiniDumpWriteDump_t g_Dump = NULL;
void dbghelp_load()
{
if (g_dbg) return;
g_dbg = LoadLibraryW(L"dbghelp.dll");
if (!g_dbg) return;
g_SymInit = (SymInitialize_t)GetProcAddress(g_dbg, "SymInitialize");
g_SymAddr = (SymFromAddr_t)GetProcAddress(g_dbg, "SymFromAddr");
g_SymLine = (SymGetLineFromAddr64_t)GetProcAddress(g_dbg, "SymGetLineFromAddr64");
g_SymOpt = (SymSetOptions_t)GetProcAddress(g_dbg, "SymSetOptions");
g_Dump = (MiniDumpWriteDump_t)GetProcAddress(g_dbg, "MiniDumpWriteDump");
if (g_SymOpt) g_SymOpt(0x00000002 /*SYMOPT_UNDNAME*/ | 0x00000004 /*SYMOPT_DEFERRED_LOADS*/);
if (g_SymInit) g_SymInit(GetCurrentProcess(), NULL, TRUE);
}
// 地址 -> "模块名+0x偏移 (符号) [文件:行]"
void describe_addr(void* addr, char* out, size_t cap)
{
out[0] = '\0';
HMODULE hMod = NULL;
char modname[64] = "?";
uintptr_t base = 0;
if (GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
(LPCWSTR)addr, &hMod) && hMod)
{
WCHAR wpath[MAX_PATH];
DWORD n = GetModuleFileNameW(hMod, wpath, MAX_PATH);
if (n > 0)
{
const WCHAR* fn = wpath;
for (DWORD i = 0; i < n; ++i)
if (wpath[i] == L'\\' || wpath[i] == L'/') fn = wpath + i + 1;
WideCharToMultiByte(CP_UTF8, 0, fn, -1, modname, sizeof(modname), NULL, NULL);
}
base = (uintptr_t)hMod;
}
uintptr_t off = (uintptr_t)addr - base;
char sym[256] = "", loc[256] = "";
if (g_SymAddr)
{
char buf[sizeof(SymPkg) + 256];
std::memset(buf, 0, sizeof(buf));
SymPkg* si = (SymPkg*)buf;
si->SizeOfStruct = sizeof(SymPkg);
si->MaxNameLen = 256;
DWORD64 disp = 0;
if (g_SymAddr(GetCurrentProcess(), (DWORD64)(uintptr_t)addr, &disp, si))
_snprintf(sym, sizeof(sym), " %s+0x%llX", si->Name, (unsigned long long)disp);
if (g_SymLine)
{
LinePkg ln;
std::memset(&ln, 0, sizeof(ln));
ln.SizeOfStruct = sizeof(LinePkg);
DWORD d = 0;
if (g_SymLine(GetCurrentProcess(), (DWORD64)(uintptr_t)addr, &d, &ln) && ln.FileName)
{
const char* f = std::strrchr(ln.FileName, '\\');
if (!f) f = std::strrchr(ln.FileName, '/');
_snprintf(loc, sizeof(loc), " [%s:%lu]", f ? f + 1 : ln.FileName,
(unsigned long)ln.LineNumber);
}
}
}
_snprintf(out, cap, " 0x%p %s+0x%llX%s%s",
addr, modname, (unsigned long long)off, sym, loc);
}
typedef USHORT (WINAPI *CaptureStackBackTrace_t)(ULONG, ULONG, PVOID*, PULONG);
// 输出调用栈;同时把属于本程序的帧地址收集到 addrs(供 addr2line 还原行号)
void dump_stack(char* out, size_t cap, int skip, uintptr_t* addrs, int* naddr, int maxaddr)
{
HMODULE krnl = GetModuleHandleW(L"kernel32.dll");
CaptureStackBackTrace_t cap_fn =
krnl ? (CaptureStackBackTrace_t)GetProcAddress(krnl, "RtlCaptureStackBackTrace") : NULL;
if (!cap_fn)
{
std::strncat(out, " (不支持栈回溯)\n", cap - std::strlen(out) - 1);
return;
}
void* frames[64];
USHORT n = cap_fn(0, 64, frames, NULL);
if (n == 0) { std::strncat(out, " (空)\n", cap - std::strlen(out) - 1); return; }
uintptr_t load_base = (uintptr_t)GetModuleHandleW(NULL); // 运行时加载基址(ASLR)
for (USHORT i = (USHORT)skip; i < n && i < 64; ++i)
{
char line[512];
describe_addr(frames[i], line, sizeof(line));
std::strncat(out, line, cap - std::strlen(out) - 1);
std::strncat(out, "\n", cap - std::strlen(out) - 1);
if (addrs && naddr && *naddr < maxaddr && std::strstr(line, g_exe_name))
{
// 转成 ImageBase+RVA:addr2line 只认这种形式
uintptr_t rva = (uintptr_t)frames[i] - load_base;
addrs[(*naddr)++] = g_image_base + rva;
}
}
}
void write_minidump(const wchar_t* dir, PEXCEPTION_POINTERS ep)
{
if (!g_Dump || !dir || !dir[0]) return;
char stamp[32];
stamp_str(stamp, sizeof(stamp));
wchar_t wstamp[64], path[MAX_PATH * 2];
u8_to_w(stamp, wstamp, 64);
_snwprintf(path, MAX_PATH * 2, L"%s\\crash_%s.dmp", dir, wstamp);
HANDLE hf = CreateFileW(path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, NULL);
if (hf == INVALID_HANDLE_VALUE) return;
DumpExcInfo ei;
ei.ThreadId = GetCurrentThreadId();
ei.ExceptionPointers = ep;
ei.ClientPointers = FALSE;
BOOL ok = g_Dump(GetCurrentProcess(), GetCurrentProcessId(), hf, 0 /*MiniDumpNormal*/,
&ei, NULL, NULL);
FlushFileBuffers(hf);
DWORD sz = GetFileSize(hf, NULL);
CloseHandle(hf);
// 空的/失败的 dmp 只会误导排查,直接删掉(地址+addr2line 已足够定位)
if (!ok || sz == 0)
{
DeleteFileW(path);
char note[160];
_snprintf(note, sizeof(note),
"[说明] minidump 生成失败(ok=%d size=%lu err=%lu),"
"请用上面的 addr2line 命令还原行号。\n",
(int)ok, (unsigned long)sz, (unsigned long)GetLastError());
wchar_t wpath2[MAX_PATH * 2];
_snwprintf(wpath2, MAX_PATH * 2, L"%s\\crash.log", dir);
append_file_w(wpath2, note, std::strlen(note));
}
}
// 异常码 -> 中文说明
const char* exc_name(DWORD code)
{
switch (code)
{
case 0xC0000005: return "ACCESS_VIOLATION 访问违规(空指针/野指针/越界/已释放内存)";
case 0xC00000FD: return "STACK_OVERFLOW 栈溢出(递归过深或栈上大数组)";
case 0xC0000094: return "INT_DIVIDE_BY_ZERO 除零";
case 0xC0000095: return "INT_OVERFLOW 整数溢出";
case 0xC000001D: return "ILLEGAL_INSTRUCTION 非法指令";
case 0xC0000006: return "IN_PAGE_ERROR 内存页错误(读取映射文件失败)";
case 0xC000008C: return "ARRAY_BOUNDS_EXCEEDED 数组越界";
case 0xC0000135: return "DLL_NOT_FOUND 依赖的DLL未找到";
case 0xC0000142: return "DLL_INIT_FAILED DLL初始化失败";
case 0xC0000409: return "STACK_BUFFER_OVERRUN 栈缓冲区溢出(编译器安全检查触发)";
case 0xC0000374: return "HEAP_CORRUPTION 堆损坏(重复释放/越界写)";
case 0xC0000417: return "INVALID_CRUNTIME_PARAMETER CRT非法参数";
case 0xE06D7363: return "C++ 异常未捕获 (std::terminate)";
case 0x40010005: return "CTRL_CLOSE / 控制台被关闭";
case 0x80000003: return "BREAKPOINT 断点";
default: return "未知异常";
}
}
// 组装并落盘一条崩溃记录
void report_crash(const char* title, PEXCEPTION_POINTERS ep, DWORD code)
{
if (g_in_handler) return; // 防递归
g_in_handler = 1;
char text[CH_MAX_TEXT];
char ts[32];
now_str(ts, sizeof(ts));
int len = _snprintf(text, sizeof(text),
"\n==================== %s ====================\n"
"时间 : %s\n"
"进程/线程 : pid=%lu tid=%lu\n"
"最后里程碑: %s\n",
title, ts,
(unsigned long)GetCurrentProcessId(), (unsigned long)GetCurrentThreadId(),
g_milestone[0] ? g_milestone : "(无)");
if (code)
len += _snprintf(text + len, sizeof(text) - len,
"异常码 : 0x%08lX %s\n", (unsigned long)code, exc_name(code));
void* fault_addr = NULL;
if (ep && ep->ExceptionRecord)
{
PEXCEPTION_RECORD r = ep->ExceptionRecord;
fault_addr = r->ExceptionAddress;
len += _snprintf(text + len, sizeof(text) - len,
"异常地址 : 0x%p (%s 0x%p)\n"
"异常标志 : 0x%lX 参数个数: %lu\n",
r->ExceptionAddress,
(r->ExceptionCode == 0xC0000005
? (r->ExceptionInformation[0] ? "写入" : "读取") : "访问"),
(r->ExceptionCode == 0xC0000005 && r->NumberParameters >= 2)
? (void*)r->ExceptionInformation[1] : (void*)0,
(unsigned long)r->ExceptionFlags, (unsigned long)r->NumberParameters);
char line[512];
describe_addr(fault_addr, line, sizeof(line));
len += _snprintf(text + len, sizeof(text) - len, "出错位置 :%s\n", line);
if (ep->ContextRecord)
{
#ifdef _WIN64
len += _snprintf(text + len, sizeof(text) - len,
"寄存器 : RIP=0x%llX RSP=0x%llX RBP=0x%llX RAX=0x%llX\n",
(unsigned long long)ep->ContextRecord->Rip,
(unsigned long long)ep->ContextRecord->Rsp,
(unsigned long long)ep->ContextRecord->Rbp,
(unsigned long long)ep->ContextRecord->Rax);
#else
len += _snprintf(text + len, sizeof(text) - len,
"寄存器 : EIP=0x%lX ESP=0x%lX EBP=0x%lX EAX=0x%lX\n",
(unsigned long)ep->ContextRecord->Eip,
(unsigned long)ep->ContextRecord->Esp,
(unsigned long)ep->ContextRecord->Ebp,
(unsigned long)ep->ContextRecord->Eax);
#endif
}
}
len += _snprintf(text + len, sizeof(text) - len, "调用栈 :\n");
uintptr_t addrs[64];
int naddr = 0;
dump_stack(text + len, sizeof(text) - len, 0, addrs, &naddr, 64);
len = (int)std::strlen(text);
len += _snprintf(text + len, sizeof(text) - len,
"提示 : 同目录下 crash_*.dmp 可用 Visual Studio / WinDbg 打开查看完整现场。\n");
// MinGW 生成的是 DWARF 调试信息,Windows 的 dbghelp 读不了,用 addr2line 还原:
// addr2line -e main.exe -f -C -i <地址...>
if (naddr > 0 && len < (int)sizeof(text) - 64)
{
len += _snprintf(text + len, sizeof(text) - len,
"还原行号 : addr2line -e %s -f -C -i", g_exe_name);
for (int i = 0; i < naddr && len < (int)sizeof(text) - 24; ++i)
len += _snprintf(text + len, sizeof(text) - len, " 0x%llX",
(unsigned long long)addrs[i]);
len += _snprintf(text + len, sizeof(text) - len, "\n");
}
wchar_t dir[MAX_PATH * 2] = { 0 };
write_crash_text(text, dir, MAX_PATH * 2);
// 控制台/调试器可见
fflush(stdout);
std::fwrite(text, 1, std::strlen(text), stderr);
fflush(stderr);
OutputDebugStringA(text);
write_minidump(dir, ep);
// 弹窗提示(无人值守部署可设环境变量 SIMIRADAR_NO_CRASH_MSGBOX=1 关闭)
char envbuf[8] = { 0 };
if (GetEnvironmentVariableA("SIMIRADAR_NO_CRASH_MSGBOX", envbuf, sizeof(envbuf)) == 0)
{
char msg[512];
_snprintf(msg, sizeof(msg),
"%s\n\n异常码: 0x%08lX (%s)\n\n详细信息已写入:\n%ls\\crash.log",
title, (unsigned long)code, exc_name(code),
dir[0] ? dir : L"(写入失败)");
MessageBoxA(NULL, msg, "Pavo2 程序异常终止",
MB_OK | MB_ICONERROR | MB_SETFOREGROUND | MB_TOPMOST);
}
g_in_handler = 0;
}
LONG WINAPI seh_filter(PEXCEPTION_POINTERS ep)
{
DWORD code = ep && ep->ExceptionRecord ? ep->ExceptionRecord->ExceptionCode : 0;
if (code == 0x40010005) return EXCEPTION_CONTINUE_SEARCH; // 控制台关闭:正常退出
report_crash("崩溃: 未处理的系统异常(SEH)", ep, code);
return EXCEPTION_EXECUTE_HANDLER; // 交给系统终止进程
}
}
#endif // _WIN32
// ==================== 非 Windows:只做 signal 兜底 ====================
namespace
{
void report_text(const char* title)
{
char text[CH_MAX_TEXT];
char ts[32];
now_str(ts, sizeof(ts));
_snprintf(text, sizeof(text),
"\n==================== %s ====================\n时间: %s\n最后里程碑: %s\n",
title, ts, g_milestone[0] ? g_milestone : "(无)");
#ifdef _WIN32
wchar_t dir[MAX_PATH * 2] = { 0 };
write_crash_text(text, dir, MAX_PATH * 2);
#endif
std::fwrite(text, 1, std::strlen(text), stderr);
fflush(stderr);
}
void abort_signal(int sig)
{
if (g_in_handler) { std::signal(sig, SIG_DFL); std::raise(sig); return; }
g_in_handler = 1;
char title[128];
_snprintf(title, sizeof(title), "崩溃: 收到信号 %d (%s)", sig,
sig == SIGABRT ? "abort()/未处理异常/std::thread未join"
: (sig == SIGSEGV ? "段错误" :
(sig == SIGFPE ? "浮点异常" : "非法指令")));
#ifdef _WIN32
report_crash(title, NULL, 0); // 仍记录调用栈与 minidump
#else
report_text(title);
#endif
g_in_handler = 0;
std::signal(sig, SIG_DFL);
std::raise(sig); // 保持默认退出行为,便于外部捕获退出码
}
void on_terminate()
{
if (g_in_handler) std::abort();
g_in_handler = 1;
std::string what;
if (std::exception_ptr ep = std::current_exception())
{
try { std::rethrow_exception(ep); }
catch (const std::exception& e) { what = e.what(); }
catch (...) { what = "非 std::exception 类型的异常"; }
}
std::string title = "崩溃: std::terminate 被调用";
if (!what.empty())
{
title += " (未捕获异常: ";
title += what;
title += ")";
}
else
{
// terminate 最常见的另一种诱因:std::thread 析构时仍 joinable(未 join/detach)
title += " (无活动异常 —— 极可能是 std::thread 对象析构时线程仍在运行,"
"即某处 start 了线程却没有 join/detach)";
}
#ifdef _WIN32
report_crash(title.c_str(), NULL, 0); // 记录调用栈 + minidump
fflush(stderr);
ExitProcess(3);
#else
report_text(title.c_str());
std::_Exit(3);
#endif
}
void on_new_fail()
{
if (g_in_handler) return;
g_in_handler = 1;
report_text("崩溃: new 失败(std::bad_alloc) —— 内存不足或申请了超大内存");
g_in_handler = 0;
throw std::bad_alloc();
}
}
// ==================== 对外接口 ====================
void install_crash_handlers(const char* log_dir)
{
if (g_installed) return;
g_installed = 1;
if (log_dir && log_dir[0])
std::strncpy(g_log_dir, log_dir, sizeof(g_log_dir) - 1);
#ifdef _WIN32
char exe[MAX_PATH];
if (GetModuleFileNameA(NULL, exe, MAX_PATH) > 0)
{
const char* fn = std::strrchr(exe, '\\');
if (!fn) fn = std::strrchr(exe, '/');
std::strncpy(g_exe_name, fn ? fn + 1 : exe, sizeof(g_exe_name) - 1);
g_exe_name[sizeof(g_exe_name) - 1] = '\0';
}
// 读 PE 头里的 ImageBase,供 addr2line 还原行号
{
char* base = (char*)GetModuleHandleW(NULL);
uintptr_t load_base = (uintptr_t)base; // 运行时加载基址(ASLR,每次不同)
if (base)
{
IMAGE_DOS_HEADER* dos = (IMAGE_DOS_HEADER*)base;
if (dos->e_magic == IMAGE_DOS_SIGNATURE)
{
IMAGE_NT_HEADERS* nt = (IMAGE_NT_HEADERS*)(base + dos->e_lfanew);
if (nt->Signature == IMAGE_NT_SIGNATURE)
{
uintptr_t ib = (uintptr_t)nt->OptionalHeader.ImageBase;
// 只有读到"合理且与加载基址不同"的静态值才采用;
// 否则保持默认 0x140000000(MinGW 链接默认值)
if (ib >= 0x10000 && ib != load_base) g_image_base = ib;
}
}
}
}
dbghelp_load(); // 提前初始化符号,崩溃时只做查询
SetUnhandledExceptionFilter(seh_filter);
// 关掉 Windows 的 "程序已停止工作" 系统弹窗,避免覆盖我们的提示
SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
#endif
std::signal(SIGABRT, abort_signal);
std::set_terminate(on_terminate);
#ifndef _WIN32
// Windows 上 SEH 已覆盖 SIGSEGV/FPE/ILL:CRT 的 signal 映射会抢在
// UnhandledExceptionFilter 之前触发,反而拿不到异常地址和调用栈,故不注册。
std::signal(SIGSEGV, abort_signal);
std::signal(SIGFPE, abort_signal);
std::signal(SIGILL, abort_signal);
#endif
std::set_new_handler(on_new_fail);
char ts[32];
now_str(ts, sizeof(ts));
CRASH_MILESTONE("崩溃日志已安装 (%s)", ts);
}
void crash_milestone(const char* fmt, ...)
{
char buf[512];
va_list ap;
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
{
std::lock_guard<std::mutex> lk(milestone_mtx());
std::strncpy(g_milestone, buf, sizeof(g_milestone) - 1);
g_milestone[sizeof(g_milestone) - 1] = '\0';
}
// 同时进控制台与 run.log(便于对照时间线)
char line[600];
char ts[32];
now_str(ts, sizeof(ts));
snprintf(line, sizeof(line), "[%s][里程碑] %s\n", ts, buf);
std::fwrite(line, 1, std::strlen(line), stdout);
fflush(stdout);
}
void crash_log(const char* fmt, ...)
{
char buf[CH_MAX_TEXT];
va_list ap;
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
char text[CH_MAX_TEXT + 64];
char ts[32];
now_str(ts, sizeof(ts));
snprintf(text, sizeof(text), "\n===== 严重错误 %s =====\n%s\n", ts, buf);
#ifdef _WIN32
wchar_t dir[MAX_PATH * 2] = { 0 };
write_crash_text(text, dir, MAX_PATH * 2);
#endif
std::fwrite(text, 1, std::strlen(text), stderr);
fflush(stderr);
}