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