《Windows核心编程》读书笔记二十五章 未处理异常,向量化异常处理与C++异常

本文深入探讨了Windows系统中的未处理异常处理机制,包括SetUnhandledExceptionFilter函数的作用,以及异常如何传递给调试器。同时,对比了C++异常与结构化异常的差异。此外,介绍了向量化异常处理和继续处理程序,以及在C++中如何使用异常处理来确保资源的正确管理。
摘要由CSDN通过智能技术生成

第二十五章  未处理异常,向量化异常处理与C++异常


本章内容

25.1 UnhandledExceptionFilter函数详解

25.2 即时调试

25.3 电子表格示例程序

25.4 向量化异常和继续处理程序

25.5 C++异常与结构化异常的比较

25.6 异常与调试器


前一章讨论的异常过滤函数返回EXCEPTION_CONTINUE_SEARCH会继续向外层搜索异常处理代码块。

如果每个异常过滤程序都返回EXCEPTION_CONTINUE_SEARCH,最后就会导致一个未处理异常


MS提供了一个函数SetUnhandledExceptionFilter,给我们处理异常的最后机会,否则Windows就认为该异常没被处理,从而交给错误报告处理程序来处理最后杀死进程。

WINBASEAPI
LPTOP_LEVEL_EXCEPTION_FILTER
WINAPI
SetUnhandledExceptionFilter(
    _In_opt_ LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter
    );
应该在进程初始化阶段调用该函数。进程中任意线程抛出未处理异常都将会导致我们设定的最上层异常过滤函数被执行。

该函数原型类似如下

 LONG WINAPI TopLevelUnhandledExceptionFilter(
	 PEXCEPTION_POINTERS pExceptionInfo
	 );


注意不要在过滤函数中使用堆,很可能已经被破坏




当我们设置了未处理异常过滤程序时,SetUnhandledExceptionFilter返回上次安装的异常过滤程序地址。

如果程序使用C/C++运行库,在进程入口点函数执行前,C/C++运行库默认会安装一个全局异常过滤程序__CxxUnhandledExceptionFilter, 该函数检查是否是C++异常。如果是则在结束时执行abort函数,后者将调用Kernel32.dll中的UnhandledExceptionFilter函数。

利用_set_abort_behavior可以配置abort函数执行的错误报告。 如果__CxxUnhandledExceptionFilter认为不是C++的异常,则会返回EXCEPTION_CONTINUE_SEARCH让windows来处理该异常。




如果过滤程序即将返回EXCEPTION_CONTINUE_SEARCH,读者可能想在此之前调用之前保存的全局异常过滤程序,但不是建议这么用。因为不知道程序使用了某个第三方组件是否安装了什么异常过滤程序,如果第三方组件是一个动态装载模块,此时它可能已经被卸载了。


线程函数是通过NTDLL.dll中的BaseThreadStart开始的


VOID BaseThreadStart(PTHREAD_START_ROUTINE pfnStartAddr, PVOID pvParam) {
	__try{
		ExitThread((pfnStartAddr)(pvParam));
	}
	__except (UnhandledExceptionFilter(GetExceptionInformation())){
		ExitProcess(GetExceptionCode());
	}
}


如果线程函数内部抛出一个异常,并且所有try/except块的过滤函数都返回EXCEPTION_CONTINUE_SEARCH最后就会被BaseThreadStart中的UnhandledExceptionFilter捕获。和普通的过滤程序一样,改函数返回3个EXCEPTION_*异常标识符中的一个,表252-2分别描述各异常标识符返回时发生的情况。



在返回这些异常标识符之前,UnhandledExceptionFilter还会执行大量的代码。

25.1 UnhandledExceptionFilter 函数详解

UnhandledExceptionFilter会按顺序执行5个步骤。在执行完这些步骤以后,UnhandledExceptionFilter将执行控制交给Windows错误报告处理程序。


1. 允许对资源进行写入操作并继续执行

如果因为线程的写操作而引起非法访问异常,UnhandledExceptionFilter会查看这个线程是否在修改exe或DLL模块中的资源。这些资源默认是只读的,试图去修改就会触发访问违规。接着UnhandledExceptionFilter使用VirtualProtect将资源页的保护属性设置为PAGE_READWRITE并返回EXCEPTION_CONTINUE_EXECUTION以允许失败的指令再次执行。


2. 将未处理异常报告给调试器

UnhandledExceptionFilter首先检查当前应用程序是否是在调试器的控制下,如果是,它将返回EXCEPTION_CONTINUE_SEARCH。因为当前异常未处理,所以Windows通知附着在当前进程的调试器。调试器会收到当前异常的EXCEPTION_RECORD结构的ExceptionInformation成员,它通过这些信息来定位到代码里抛出异常的那条指令,并通知我们什么样的异常被抛出。可以在代码里调用IsDebuggerPresent来检测当前进程是否处于调试器的控制下。


3. 通知我们设置的全局异常过滤函数

如果应用程序已经通过SetUnhandledExceptionFilter来指定全局异常过滤程序,UnhandledExceptionFilter将调用这个函数。

如果异常过滤函数返回是EXCEPTION_EXECUTE_HANDLER或者EXCEPTION_CONTINUE_EXECUTION.UnhandledExceptionFilter将直接返回这个值给系统。

如果返回值是EXCEPTION_CONTINUE_SEARCH将转到步骤4.

注意:C++的全局未处理异常过滤函数__CxxUnhandledExceptionFilter会显式调用UnhandledExceptionFilter。因此这里有一个无穷递归调用。为了防止出现该问题。__CxxUnhandledExceptionFilter在调用UnhandledExceptionFilter之前会设置SetUnhandledExceptionFilter(NULL)


当程序使用CRT的时候,运行库会使用try/except结构保护线程入口点函数,对应的异常过滤程序会调用C/C++运行库的_XcpFilter函数。_XcpFilter函数内部也调用UnhandledExceptionFilter。而后者又会继续调用全局异常过滤函数(如果存在的话)。 如果我们注册的全局异常过滤函数返回EXCEPTION_CONTINUE_SEARCH,(真正的)未处理异常就会到达BaseThreadStart异常过滤程序,于是UnhandledExceptionFilter又会执行,结果,全局过滤程序也会再被执行一次。


4. 将未处理异常报告给调试器(两次)

在步骤3的时候,全局未处理异常过滤函数会调用调试器并让调试器附着到抛出异常的进程。如果这时候未处理异常过滤程序返回EXCEPTION_CONTINUE_SEARCH,调试器会再一次被调用(操作系统内核判断了目标进程以及附着了一个调试器,会再次将此异常发送给调试器)


5. 终止进程

如果进程中某个线程曾经调用SetErrorMode设置标志SEM_NOGPFAULTERRORBOX 那么UnhandledExceptionFilter会返回EXCEPTION_EXECUTE_HANDLER.。 在未处理异常的情形下,这个值将会引发全局展开,后者在进程悄然终止前让未执行的finally块执行。

如果进程处于作业中,并且作业的限制信息(limit information)设置了标志JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION, 那么UnhandledExceptionFilter也将返回EXCEPTION_EXECUTE_HANDLER


在这些步骤里,UnhandledExceptionFilter工作于后台,试图解决引发异常的问题,通知调试器(如果存在),或者在必要情况下简单终止程序运行。如果不能处理异常,就会返回EXCEPTION_CONTINUE_SEARCH,于是系统内核得到程序的控制,将程序运行错误通知用户。

UnhandledExceptionFilter返回并在Windows内核所做的工作之前,会先弹出一个用户界面,


<

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值