SetUnhandledExceptionFilter是进程级“最后机会”异常入口,而__try/__except仅限当前函数栈帧内捕获;前者在内核交还控制权前调用,可捕获线程外、异步、析构中等场景崩溃,后者无法覆盖此类情况。

SEH 不能直接捕获所有崩溃,比如栈溢出、访问空指针导致的 STATUS_ACCESS_VIOLATION 可以捕获,但已破坏栈帧或 SEH 链损坏时会失效——别指望它当万能兜底。
为什么要用 SetUnhandledExceptionFilter 而不是 __try/__except 包裹 main?
__try/__except 是编译器级结构化异常处理(SEH),只能捕获当前函数栈帧内抛出的异常;一旦执行流跳出 __try 块(比如异步信号、线程外崩溃、析构函数中抛异常),就无法捕获。而 SetUnhandledExceptionFilter 注册的是进程级未处理异常回调,在 Windows 内核将控制权交还用户态前调用,是真正意义上“最后机会”的入口。
- 必须在程序启动早期注册(如
main开头或 DLL 的DllMain中),晚于首次异常发生则无效 - 该回调运行在异常线程上下文中,栈可能已损坏,禁止调用 C 运行时函数(如
printf、malloc)、不安全的 WinAPI(如CreateThread) - 返回
EXCEPTION_EXECUTE_HANDLER表示你“吃掉”了异常,进程不会终止——但这很危险,尤其对内存损坏类异常,继续运行大概率引发二次崩溃
如何安全地在 UnhandledExceptionFilter 中保存崩溃现场?
核心原则:只做最小必要操作——记录寄存器、栈顶几帧、模块信息,全部用 Windows 原生 API 和栈上变量完成。
- 用
GetThreadContext获取CONTEXT结构,注意指定CONTEXT_FULL标志,否则寄存器值为空 - 用
StackWalk64+SymInitialize解析调用栈,但符号加载本身可能失败,需提前在初始化阶段调用SymInitialize并缓存句柄 - 写 dump 文件必须用
MiniDumpWriteDump,而非自己拼内存——它自动处理堆、线程、模块等上下文,且支持MiniDumpWithIndirectlyReferencedMemory等标志抓关键引用 - 日志路径必须是绝对路径且确保目录存在,避免因相对路径或权限问题写失败(常见坑:
"crash.log"在服务环境下根本写不进)
为什么 MiniDumpWriteDump 后程序仍可能卡死或二次崩溃?
因为 MiniDumpWriteDump 本身会遍历所有线程、读取内存页、锁定模块句柄——若崩溃由堆损坏或锁竞争引起,dump 过程中再次触发访问违规或死锁,就会卡住。
- 务必设置超时:用
CreateJobObject+SetInformationJobObject给 dump 线程加JOBOBJECT_BASIC_LIMIT_INFORMATION限制 CPU 时间,防止无限 hang - 避免在回调中等待其他线程(如
WaitForSingleObject),异常线程状态不可控,等待极易死锁 - 如果使用
MiniDumpWithFullMemory,dump 文件极大且耗时长,生产环境应改用MiniDumpNormal或MiniDumpWithPrivateReadWriteMemory - 注意:32 位程序调用
MiniDumpWriteDump处理 64 位 dump 会失败,反之亦然;多架构部署必须匹配对应版本 dbghelp.dll
真正棘手的不是怎么捕获,而是判断什么该捕获、什么该立即终止——比如 STATUS_STACK_OVERFLOW 捕获后几乎无法安全恢复,强行继续等于埋雷;而 STATUS_ACCESS_VIOLATION 地址在可读写页内,有时还能尝试修复。这些边界情况没有银弹,得靠具体 crash address、context 和模块特征联合判断。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











