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++

// 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);
}