容错:使用 Windows 异常过滤器






4.48/5 (9投票s)
2004年4月30日
4分钟阅读

74331

1600
一篇说明 SetUnhandledExceptionFilter 实际用法的文章
引言
与客户打交道可能非常有益,但当出现问题时——尤其是在生产系统上运行的软件中出现一般保护性故障 (GPF) 时——所有人都会感到沮丧。客户惊慌失措;工程师们争先恐后地获取有关出错信息:每个人都不高兴。
这不是完成工作的理想方式——防患于未然胜于亡羊补牢。防止 GPF 带来的沮丧的最好方法是做好准备。如果您的系统崩溃,它应该能够收集所有必要的信息,以便快速、几乎无需客户协助地评估故障。
如何实现这一目标?不接受任何替代方案:在应用程序的整个开发生命周期中进行彻底的测试是基本(良好的设计也有帮助)。不幸的是,在大型复杂系统中,测试不能作为完美的保证,需要一个安全网(给人群中所有得意洋洋的电气工程师一点小钱)。异常过滤器为意外紧急情况提供了出色的保护。
概述
异常过滤器使应用程序“能够覆盖每个线程和进程的顶级异常处理程序”。这是什么意思?当出现严重问题时,顶级异常处理程序通常是第一个也是唯一一个知道的人。
灵活性
正确实现后,处理异常的处理程序独立于传递异常的机制。这种设计标准促进了处理程序在项目之间的重用——一个很好的例子就是异常报告器和邮件发送器。过滤器将异常传递给处理程序,处理程序再写入报告、发送邮件,然后关闭。
int main(int, char**) { // write an exception report, mail it and shutdown exception::filter<exception::shutdown>::install(); exception::filter<exception::mailman>::install(); exception::filter<exception::report>::install();
粒度
许多人参与一个大型系统;每个部分都是独立的,但最终将集成在一起。所有这些都需要专门的异常处理过程(GPF 拆解过程)。我们如何满足这些要求?良好的设计允许代码按子系统进行分区,并允许最终用户控制调用处理程序的顺序。
// cleanup (in order): transport, loader, logging and controller
exception::filter<controller::gpf_handler>::install();
exception::filter<logging::gpf_handler>::install();
exception::filter<loader::gpf_handler>::install();
exception::filter<transport::gpf_handler>::install();
随着子系统的添加,可以添加特定的处理程序来适应它们特殊的要求。
设计
exception::filter
类(单例)提供了一个公共接口来安装、卸载和配置异常过滤器;exception::filter
私有地与操作系统进行交互。模板参数将作为发生未处理异常时执行的操作。新安装的过滤器会链接到先前安装的过滤器。
图 1。 过滤器协作图
实现
::SetUnhandledExceptionFilter
负责所有工作。它接受一个回调函数作为输入参数,并返回先前的过滤器。先前的过滤器被保存,提供了一个简单的机制来链接过滤器。回调函数接受 PEXCEPTION_POINTERS
作为输入参数,该参数提供了有关错误发生情况的上下文。
请注意,链接机制不允许以除安装顺序的相反顺序以外的任何顺序卸载过滤器。因此
// install exception::filter<exception::shutdown>::install(); exception::filter<exception::report>::install(); // uninstall exception::filter<exception::report>::uninstall(); exception::filter<exception::shutdown>::uninstall();
是明确定义的,而
// install exception::filter<exception::shutdown>::install(); exception::filter<exception::report>::install(); // uninstall exception::filter<exception::shutdown>::uninstall();
会导致未定义行为。
一个简单的处理程序:exception::shutdown
我们的第一个处理程序的任务是简单但必需的:尽快终止发生故障的程序。因此,我们的处理程序变成
struct shutdown { static void handler(const char*, PEXCEPTION_POINTERS) { ::exit(-1); }};
我们依赖其他处理程序在我们的第一个也是最后一个处理程序拆解进程之前通知我们故障情况并进行报告。
报告处理程序:exception::report
仅关闭不足以确定出了什么问题,需要上下文才能在现场正确诊断问题。最好的上下文通常是在故障发生时的进程/系统快照:寄存器信息、调用堆栈、已加载的模块、进程、正在运行的线程。
Matt Pietrek 在 1997 年 4 月的 Microsoft Systems Journal 上使用 Windows imagehlp 库实现了一个异常报告器。我已改编此代码供使用。
使用代码
要定义一个处理程序,请实现一个定义了静态函数 'handler' 的结构或类,其签名如下:
struct report { static void handler(const char* sz_log, PEXCEPTION_POINTERS p_info)
第一个参数指定日志信息应发送到何处,第二个参数提供必要的上下文。
要安装异常过滤器,请使用 exception::filter::install
。
// install our handler(s)
plugware::exception::filter<plugware::exception::shutdown>::install();
plugware::exception::filter<plugware::exception::report>::install();
plugware::exception::filter<gpf_handler_1>::install();
plugware::exception::filter<gpf_handler_2>::install();
要卸载过滤器,请使用 exception::filter::uninstall。您必须按照安装的相反顺序卸载过滤器。否则会导致未定义行为。
// uninstall
exception::filter<exception::report>::uninstall();
exception::filter<exception::shutdown>::uninstall();
关于演示程序
演示项目安装了两个用户定义的处理程序(gpf_handler_1、gpf_handler_2)和两个内置处理程序(shutdown、report)。通过解引用空指针会生成一个故意的故障。应用程序在调试器下无法成功运行——调试环境会安装覆盖所有先前安装的过滤器的过滤器。
要运行程序,请编译它并从命令行运行。
结论
随着软件变得越来越复杂,供应商能够对应用程序的寿命和行为做出强有力保证的能力就会减弱。虽然在产品的整个生命周期中进行彻底充分的测试很重要,但如果可能出错 就会出错。
异常过滤器虽然不是万能药,但它提供了充足的机会来收集上下文信息,以确保无法预见的难题能够得到快速诊断和在现场得到处理。编码愉快!
历史
- 2004/04/30 添加了 UML 协作图
- 2004/04/30 添加了历史记录部分
- 2004/04/27 文章创建