文章目录
- 实际演示代码
- [g3log 捕获 Windows 异常崩溃信号完全指南](#g3log 捕获 Windows 异常崩溃信号完全指南)
-
- 一、概述
- [二、Windows 崩溃捕获机制](#二、Windows 崩溃捕获机制)
-
- [2.1 两层异常处理架构](#2.1 两层异常处理架构)
- [2.2 捕获的致命信号](#2.2 捕获的致命信号)
- [2.3 捕获的 Windows 结构化异常](#2.3 捕获的 Windows 结构化异常)
- 三、实现原理深度解析
-
- [3.1 信号处理器的安装](#3.1 信号处理器的安装)
- [3.2 信号处理流程](#3.2 信号处理流程)
- [3.3 异常处理流程](#3.3 异常处理流程)
- [3.4 崩溃安全的保证](#3.4 崩溃安全的保证)
- 四、配置选项
-
- [4.1 CMake 编译选项](#4.1 CMake 编译选项)
- [4.2 运行时控制](#4.2 运行时控制)
- 五、完整示例
- 六、最佳实践
-
- [6.1 多线程注意事项](#6.1 多线程注意事项)
- [6.2 调试与发布的区别](#6.2 调试与发布的区别)
- [6.3 自定义崩溃行为](#6.3 自定义崩溃行为)
- 七、总结
实际演示代码
cpp
// 关闭CRT安全警告
#define _CRT_SECURE_NO_WARNINGS
// 防止Windows宏污染 std::min std::max
#define NOMINMAX
// 精简Windows头文件
#define WIN32_LEAN_AND_MEAN
// Windows API
#include <windows.h>
// Windows 调试符号 API(用于堆栈解析)
#include <DbgHelp.h>
// C 信号处理
#include <csignal>
// C++ 异常
#include <exception>
// 控制台输出
#include <iostream>
// C++标准异常
#include <stdexcept>
// 浮点控制
#include <float.h>
// 内存分配
#include <new>
// RTTI
#include <typeinfo>
// 防止Windows宏污染
#undef ERROR
#undef WARNING
#undef DEBUG
// g3log日志库
#include <g3log/g3log.hpp>
#include <g3log/logworker.hpp>
// 链接 Windows 调试库
#pragma comment(lib, "DbgHelp.lib")
//================================================
// StackTrace
//================================================
// 打印堆栈信息
void PrintStackTrace(CONTEXT* ctx)
{
// 获取当前进程句柄
HANDLE process = GetCurrentProcess();
// 获取当前线程
HANDLE thread = GetCurrentThread();
// 初始化符号解析
SymInitialize(process, NULL, TRUE);
// 堆栈帧结构
STACKFRAME64 frame{};
// CPU架构
DWORD machine;
#ifdef _M_X64
// 64位架构
machine = IMAGE_FILE_MACHINE_AMD64;
// 当前指令地址
frame.AddrPC.Offset = ctx->Rip;
frame.AddrPC.Mode = AddrModeFlat;
// 帧指针
frame.AddrFrame.Offset = ctx->Rbp;
frame.AddrFrame.Mode = AddrModeFlat;
// 栈指针
frame.AddrStack.Offset = ctx->Rsp;
frame.AddrStack.Mode = AddrModeFlat;
#else
// 32位架构
machine = IMAGE_FILE_MACHINE_I386;
frame.AddrPC.Offset = ctx->Eip;
frame.AddrPC.Mode = AddrModeFlat;
frame.AddrFrame.Offset = ctx->Ebp;
frame.AddrFrame.Mode = AddrModeFlat;
frame.AddrStack.Offset = ctx->Esp;
frame.AddrStack.Mode = AddrModeFlat;
#endif
// 打印堆栈开始
LOG(FATAL) << "=========== STACK TRACE ===========";
// 最多打印50层堆栈
for (int i = 0; i < 50; i++)
{
// 进行一次堆栈回溯
if (!StackWalk64(machine, process, thread, &frame, ctx, NULL,
SymFunctionTableAccess64, SymGetModuleBase64, NULL))
break;
// 获取当前地址
DWORD64 addr = frame.AddrPC.Offset;
// 地址为0说明结束
if (addr == 0) break;
// 符号缓冲区
char buffer[sizeof(SYMBOL_INFO) + 256];
// 转换为符号结构
PSYMBOL_INFO symbol = (PSYMBOL_INFO)buffer;
// 设置结构大小
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
// 最大符号名长度
symbol->MaxNameLen = 255;
DWORD64 displacement;
// 尝试解析符号
if (SymFromAddr(process, addr, &displacement, symbol))
{
LOG(FATAL) << i << ": " << symbol->Name
<< " - 0x" << std::hex << symbol->Address;
}
else
{
LOG(FATAL) << i << ": 0x" << std::hex << addr;
}
}
// 结束
LOG(FATAL) << "===================================";
}
//================================================
// Exception Name
//================================================
// 根据异常代码返回字符串
const char* ExceptionName(DWORD code)
{
switch (code)
{
case EXCEPTION_ACCESS_VIOLATION: return "ACCESS_VIOLATION";
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: return "ARRAY_BOUNDS_EXCEEDED";
case EXCEPTION_BREAKPOINT: return "BREAKPOINT";
case EXCEPTION_DATATYPE_MISALIGNMENT: return "DATATYPE_MISALIGNMENT";
case EXCEPTION_FLT_DIVIDE_BY_ZERO: return "FLOAT_DIVIDE_BY_ZERO";
case EXCEPTION_ILLEGAL_INSTRUCTION: return "ILLEGAL_INSTRUCTION";
case EXCEPTION_IN_PAGE_ERROR: return "IN_PAGE_ERROR";
case EXCEPTION_INT_DIVIDE_BY_ZERO: return "INT_DIVIDE_BY_ZERO";
case EXCEPTION_STACK_OVERFLOW: return "STACK_OVERFLOW";
default: return "UNKNOWN_EXCEPTION";
}
}
//================================================
// SEH Handler
//================================================
// Windows 未捕获异常处理函数
LONG WINAPI SehHandler(EXCEPTION_POINTERS* e)
{
// 获取异常码
DWORD code = e->ExceptionRecord->ExceptionCode;
LOG(FATAL) << "========== CRASH ==========";
LOG(FATAL) << "Exception: " << ExceptionName(code);
LOG(FATAL) << "Code: 0x" << std::hex << code;
LOG(FATAL) << "Address: " << e->ExceptionRecord->ExceptionAddress;
// 打印堆栈
PrintStackTrace(e->ContextRecord);
// 告诉系统异常已处理
return EXCEPTION_EXECUTE_HANDLER;
}
//================================================
// Vectored Handler
//================================================
// Windows 向量异常处理
LONG CALLBACK VectoredHandler(PEXCEPTION_POINTERS e)
{
DWORD code = e->ExceptionRecord->ExceptionCode;
LOG(FATAL) << "Vectored exception caught: 0x"
<< std::hex << code;
return EXCEPTION_CONTINUE_SEARCH;
}
//================================================
// terminate
//================================================
// C++ terminate 处理函数
void TerminateHandler()
{
LOG(FATAL) << "std::terminate called";
// 获取当前异常
auto exptr = std::current_exception();
if (exptr)
{
try
{
// 重新抛出异常
std::rethrow_exception(exptr);
}
catch (const std::exception& e)
{
LOG(FATAL) << "Unhandled C++ exception: " << e.what();
}
catch (...)
{
LOG(FATAL) << "Unknown C++ exception";
}
}
abort();
}
//================================================
// Pure virtual
//================================================
// 纯虚函数调用异常
void PureCallHandler()
{
LOG(FATAL) << "Pure virtual function called!";
abort();
}
//================================================
// Signal
//================================================
// C信号处理
void SignalHandler(int sig)
{
LOG(FATAL) << "Signal caught: " << sig;
abort();
}
//================================================
// Install handlers
//================================================
// 安装所有崩溃捕获
void InstallCrashHandler()
{
// Windows向量异常
AddVectoredExceptionHandler(1, VectoredHandler);
// 未处理异常
SetUnhandledExceptionFilter(SehHandler);
// C++ terminate
std::set_terminate(TerminateHandler);
// pure virtual call
_set_purecall_handler(PureCallHandler);
// C signal
signal(SIGABRT, SignalHandler);
signal(SIGSEGV, SignalHandler);
signal(SIGFPE, SignalHandler);
signal(SIGILL, SignalHandler);
}
//================================================
// Crash test functions grouped by categories
//================================================
namespace AccessViolationTests {
// 空指针写
void CrashAccessViolation() {
int* p = nullptr;
*p = 1;
}
// 非法地址写
void CrashBadWrite() {
int* p = (int*)0x123;
*p = 10;
}
// 非法函数调用
void CrashInvalidCall() {
typedef void(*Func)();
Func f = (Func)0x12345678;
f();
}
}
namespace DivideByZeroTests {
// 整数除0
void CrashDivideByZero() {
volatile int a = 1;
volatile int b = 0;
volatile int c = a / b;
}
}
namespace IllegalInstructionTests {
// 非法指令
void CrashIllegalInstruction() {
#if defined(_MSC_VER) && defined(_M_X64)
__ud2();
#endif
}
}
namespace StackTests {
// 栈溢出
void StackOverflow() {
StackOverflow();
}
// 栈缓冲区溢出(触发栈保护页或访问违例)
void CrashStackBufferOverflow() {
volatile char buffer[10];
for (int i = 0; i <= 20; ++i) {
buffer[i] = static_cast<char>(i);
}
}
}
namespace AbortTests {
// abort崩溃
void CrashAbort() {
abort();
}
}
namespace CppExceptionTests {
// 未捕获C++异常
void CrashCppException() {
throw std::runtime_error("test exception");
}
// bad_alloc 异常
void CrashBadAlloc() {
size_t huge = ~(size_t)0; // 最大可分配值
void* p = ::operator new(huge);
}
// bad_cast 异常
struct Base { virtual ~Base() {} };
struct Derived : Base {};
void CrashBadCast() {
Base b;
Derived& d = dynamic_cast<Derived&>(b); // 抛出 std::bad_cast
}
// 在 noexcept 函数中抛出异常(触发 std::terminate)
void CrashNoexceptThrow() noexcept {
throw std::runtime_error("throw from noexcept");
}
}
namespace HeapTests {
// 堆损坏:写入已释放的内存
void CrashHeapCorruption() {
HANDLE heap = HeapCreate(0, 4096, 0);
if (!heap) return;
int* p = (int*)HeapAlloc(heap, 0, sizeof(int));
if (p) {
*p = 123;
HeapFree(heap, 0, p);
*p = 456; // 访问已释放内存
}
HeapDestroy(heap);
}
// 双重释放
void CrashDoubleFree() {
HANDLE heap = HeapCreate(0, 4096, 0);
if (!heap) return;
int* p = (int*)HeapAlloc(heap, 0, sizeof(int));
if (p) {
HeapFree(heap, 0, p);
HeapFree(heap, 0, p); // 第二次释放
}
HeapDestroy(heap);
}
}
namespace InvalidHandleTests {
// 使用无效句柄
void CrashInvalidHandle() {
HANDLE handle = INVALID_HANDLE_VALUE;
CloseHandle(handle); // 关闭无效句柄可能引发异常
}
}
namespace SignalTests {
// 通过 raise 触发信号
void CrashRaiseSigSegv() { raise(SIGSEGV); }
void CrashRaiseSigAbort() { raise(SIGABRT); }
void CrashRaiseSigFpe() { raise(SIGFPE); }
void CrashRaiseSigIll() { raise(SIGILL); }
}
namespace FloatExceptionTests {
// 浮点除零(需启用浮点异常)
void CrashFloatDivideByZero() {
unsigned int old = _controlfp(0, 0);
_controlfp(_EM_ZERODIVIDE, _EM_ZERODIVIDE);
volatile double a = 1.0, b = 0.0, c = a / b;
_controlfp(old, _MCW_EM); // 恢复
}
// 浮点溢出
void CrashFloatOverflow() {
unsigned int old = _controlfp(0, 0);
_controlfp(_EM_OVERFLOW, _EM_OVERFLOW);
volatile double a = 1e300, b = 1e300, c = a * b;
_controlfp(old, _MCW_EM);
}
}
namespace PureCallTests {
//// 构造函数中调用纯虚函数(触发 purecall 处理)
//class AbstractBase {
//public:
// virtual void Pure() = 0;
// AbstractBase() { Pure(); }
//};
//class Derived2 : public AbstractBase {
//public:
// virtual void Pure() override {}
//};
//void CrashPureCallFromConstructor() {
// Derived2 obj;
//}
}
namespace RaiseExceptionTests {
// 使用 RaiseException 触发自定义结构化异常
void CrashRaiseException() {
RaiseException(0xE0000001, 0, 0, NULL);
}
}
//================================================
// main
//================================================
int main()
{
// 创建g3log worker
auto worker = g3::LogWorker::createLogWorker();
// 初始化日志
g3::initializeLogging(worker.get());
// 安装崩溃处理
InstallCrashHandler();
LOG(INFO) << "Program started";
// 修改 test 变量以选择不同的崩溃测试
int test = 17; // 例如:1=AccessViolation, 2=DivideByZero, ...
switch (test)
{
case 1: AccessViolationTests::CrashAccessViolation(); break;
case 2: DivideByZeroTests::CrashDivideByZero(); break;
case 3: IllegalInstructionTests::CrashIllegalInstruction(); break;
case 4: StackTests::StackOverflow(); break;
case 5: AbortTests::CrashAbort(); break;
case 6: AccessViolationTests::CrashBadWrite(); break;
case 7: CppExceptionTests::CrashCppException(); break;
case 8: AccessViolationTests::CrashInvalidCall(); break;
case 9: HeapTests::CrashHeapCorruption(); break;
case 10: HeapTests::CrashDoubleFree(); break;
case 11: StackTests::CrashStackBufferOverflow(); break;
case 12: InvalidHandleTests::CrashInvalidHandle(); break;
case 13: SignalTests::CrashRaiseSigSegv(); break;
case 14: SignalTests::CrashRaiseSigAbort(); break;
case 15: SignalTests::CrashRaiseSigFpe(); break;
case 16: SignalTests::CrashRaiseSigIll(); break;
case 17: FloatExceptionTests::CrashFloatDivideByZero(); break;
case 18: FloatExceptionTests::CrashFloatOverflow(); break;
case 19: CppExceptionTests::CrashBadAlloc(); break;
case 20: CppExceptionTests::CrashBadCast(); break;
case 21: CppExceptionTests::CrashNoexceptThrow(); break;
//case 22: PureCallTests::CrashPureCallFromConstructor(); break;
case 23: RaiseExceptionTests::CrashRaiseException(); break;
default: LOG(INFO) << "No test selected"; break;
}
return 0;
}
g3log 捕获 Windows 异常崩溃信号完全指南
一、概述
g3log 是一个异步、崩溃安全的 C++ 日志库,由 KjellKod.cc 开发并作为公共领域代码发布 。它的三大核心特性包括:直观的 LOG API、设计契约式的 CHECK 功能,以及最重要的------崩溃时的优雅退出机制,确保不会丢失任何日志信息 。
在 Windows 平台上,g3log 提供了比 Linux 更全面的崩溃捕获机制。它不仅处理标准的致命信号(signals),还能捕获 Windows 特有的结构化异常(SEH)和向量化异常(Vectored Exceptions) 。
二、Windows 崩溃捕获机制
2.1 两层异常处理架构
g3log 在 Windows 上实现了双层防御的崩溃捕获架构:
| 层级 | 处理机制 | 捕获范围 |
|---|---|---|
| 第一层 | 向量化异常处理 (Vectored Exception Handling) | 所有异常的第一站,优先于任何线程特定的处理程序 |
| 第二层 | 未处理异常过滤 (Unhandled Exception Filter) | 未被向量化处理捕获或处理的异常 |
2.2 捕获的致命信号
g3log 默认捕获以下致命信号 :
| 信号 | 名称 | 触发场景 |
|---|---|---|
SIGABRT |
中止信号 | abort() 调用、LOG(FATAL)、CHECK(false) |
SIGFPE |
浮点异常 | 整数除以零、浮点运算错误 |
SIGILL |
非法指令 | 非法指令执行 |
SIGSEGV |
段错误 | 空指针解引用、访问非法内存地址 |
SIGTERM |
终止信号 | 外部进程终止请求 |
2.3 捕获的 Windows 结构化异常
除了标准信号,g3log 还捕获 Windows 特有的结构化异常 :
| 异常代码 | 异常名称 | 描述 |
|---|---|---|
EXCEPTION_ACCESS_VIOLATION |
访问违例 | 相当于 SIGSEGV |
EXCEPTION_ARRAY_BOUNDS_EXCEEDED |
数组越界 | 数组访问超出边界 |
EXCEPTION_DATATYPE_MISALIGNMENT |
数据类型错位 | 内存对齐错误 |
EXCEPTION_FLT_DIVIDE_BY_ZERO |
浮点除零 | 浮点数除以零 |
EXCEPTION_INT_DIVIDE_BY_ZERO |
整数除零 | 整数除以零 |
EXCEPTION_STACK_OVERFLOW |
栈溢出 | 函数递归过深导致栈溢出 |
三、实现原理深度解析
3.1 信号处理器的安装
g3log 在 Windows 上有一个独特之处:SIGFPE、SIGILL 和 SIGSEGV 的处理必须在每个线程中单独安装 。这是 Windows 平台的限制,g3log 通过两种方式解决:
- 自动安装 :每次调用
LOG宏时,会通过SIGNAL_HANDLER_VERIFY()宏确保当前线程安装了信号处理器 - 手动安装 :开发者可以调用
g3::installSignalHandlerForThread()为任意线程显式安装
cpp
// 自动安装机制(简化版)
#define SIGNAL_HANDLER_VERIFY() g3::installSignalHandlerForThread()
LogCapture::~LogCapture() {
SIGNAL_HANDLER_VERIFY(); // 每个日志调用都会确保线程信号处理器就绪
saveMessage(...);
}
3.2 信号处理流程
当致命信号发生时,g3log 的处理流程如下 :
- 信号触发 :操作系统调用注册的信号处理器
signalHandler() - 堆栈捕获 :调用
stacktrace::stackdump()生成当前线程的堆栈信息 - 日志记录 :创建
LogCapture对象,将信号信息和堆栈写入日志流 - 调试中断 :在 Debug 模式下且启用了
DEBUG_BREAK_AT_FATAL_SIGNAL宏时,触发__debugbreak()让 Visual Studio 中断 - 日志刷新 :
LogCapture析构时,将消息发送给后台工作线程,确保所有挂起的日志被写入 - 进程退出:恢复默认信号处理,再次引发原信号以终止进程
3.3 异常处理流程
对于 Windows 结构化异常,处理流程类似但略有不同 :
- 异常捕获:向量化异常处理器或未处理异常过滤器捕获异常
- 异常码转换:将 Windows 异常码转换为可读的异常名称
- 堆栈捕获 :从
EXCEPTION_POINTERS结构中获取异常发生时的上下文,生成堆栈信息 - 日志记录:记录异常信息和堆栈
- 继续搜索 :返回
EXCEPTION_CONTINUE_SEARCH,允许其他异常处理器继续处理
3.4 崩溃安全的保证
g3log 的"崩溃安全"体现在 :
- 发生崩溃时,所有已在队列中的日志消息都会被写入持久化存储
- 日志写入发生在后台线程,不会阻塞崩溃的主线程
- 只有当所有日志都处理完毕后,才允许进程真正退出
四、配置选项
4.1 CMake 编译选项
g3log 提供了多个与崩溃处理相关的 CMake 选项 :
| 选项 | 默认值 | 描述 |
|---|---|---|
ENABLE_FATAL_SIGNALHANDLING |
ON |
启用/禁用致命信号处理 |
DISABLE_VECTORED_EXCEPTIONHANDLING |
OFF |
禁用向量化异常处理 |
DEBUG_BREAK_AT_FATAL_SIGNAL |
OFF |
Debug 模式下在致命信号处触发调试中断 |
4.2 运行时控制
g3log 允许开发者自定义崩溃处理行为 :
- 预崩溃钩子:注册在崩溃处理前执行的函数
- 自定义退出处理:覆盖默认的退出行为
五、完整示例
以下是一个完整的 Windows 崩溃捕获示例,展示了 g3log 如何捕获各种类型的崩溃:
cpp
#include <g3log/g3log.hpp>
#include <g3log/logworker.hpp>
#include <windows.h>
int main() {
// 初始化 g3log
auto worker = g3::LogWorker::createLogWorker();
worker->addDefaultLogger("crash_test", "./");
g3::initializeLogging(worker.get());
LOG(INFO) << "程序启动,崩溃处理已就绪";
// 测试各类崩溃
int choice = 7; // 选择测试类型
switch(choice) {
case 1: // 空指针解引用 (SIGSEGV)
LOG(INFO) << "测试: 空指针解引用";
*(volatile int*)0 = 123;
break;
case 2: // 整数除零 (SIGFPE)
LOG(INFO) << "测试: 整数除零";
volatile int a = 1, b = 0, c = a / b;
break;
case 3: // LOG(FATAL) (SIGABRT)
LOG(FATAL) << "主动触发致命错误";
break;
case 4: // CHECK 失败 (SIGABRT)
CHECK(1 == 2) << "条件失败";
break;
case 5: // 栈溢出 (EXCEPTION_STACK_OVERFLOW)
LOG(INFO) << "测试: 栈溢出";
auto stack_overflow = [&]() { stack_overflow(); };
stack_overflow();
break;
case 6: // 非法指令 (SIGILL)
LOG(INFO) << "测试: 非法指令";
__ud2(); // x86/x64 非法指令
break;
case 7: // 自定义结构化异常
LOG(INFO) << "测试: 自定义异常";
RaiseException(0xE0000001, 0, 0, nullptr);
break;
}
return 0;
}
六、最佳实践
6.1 多线程注意事项
由于 Windows 对 SIGFPE、SIGILL、SIGSEGV 的要求,在多线程程序中需要注意:
- 每个新创建的线程如果可能触发崩溃,建议调用
g3::installSignalHandlerForThread() - 或者确保每个线程至少执行一次
LOG调用,自动触发信号处理器安装
6.2 调试与发布的区别
- Debug 模式 :可以启用
DEBUG_BREAK_AT_FATAL_SIGNAL,让崩溃时自动在 Visual Studio 中断,方便调试 - Release 模式:建议关闭此选项,让程序在崩溃时干净地退出并记录日志
6.3 自定义崩溃行为
如需在崩溃时执行自定义操作(如发送错误报告),可以通过以下方式:
cpp
// 注册预崩溃钩子(需要查看具体 API 文档)
g3::setFatalPreLoggingHook([]() {
// 发送错误报告、保存临时文件等
MessageBox(nullptr, "程序即将崩溃", "错误", MB_OK);
});
七、总结
g3log 在 Windows 平台上提供了全面的崩溃捕获能力:
- ✅ 捕获标准致命信号(SIGABRT、SIGFPE、SIGILL、SIGSEGV、SIGTERM)
- ✅ 捕获 Windows 结构化异常(访问违例、除零、栈溢出等)
- ✅ 生成详细的堆栈信息,帮助定位问题
- ✅ 确保崩溃前所有日志都被写入,不丢失信息
- ✅ 支持向量化异常处理,优先捕获异常
- ✅ 提供灵活的配置选项和扩展接口
通过合理使用 g3log 的崩溃捕获机制,开发者可以大大提高 Windows 应用程序的可靠性和可诊断性,让那些难以复现的偶发崩溃变得有迹可循。