VEH
开发人员主要使用两种异常处理技术,一种是 SEH (结构化异常处理),另一种是 VEH (向量化异常处理,XP 以上)
VEH全称Vectored Exception Handling ,VEH
向量化异常处理的基本理念与VEH相似,也是注册一个回调函数,当发生异常时会被系统的异常处理过程调用
通过API函数AddVectioredExceptionHandler注册VEH回调函数,其原型如下
WINBASEAPI PVOID WINAPI AddVectoredExceptionHandler{
ULONG FirstHandler,
PVECTORED_EXCEPTION_HANDLER VectoredHandler //回调函数地址
}
VEH回调函数也形成了一个链表,若参数FirstHandler
的值为0,则回调函数位于VEH链表的尾部
若该值非0,则置于VEH链表的最前端,当有多个VEH回调函数存在时
这个值会营销回调函数被调用的顺序,应该将函数的返回值保存下来,用来卸载回调函数
VEH回调函数所在的模块被卸载之后,系统不能自动地将回调函数地址从VEH链表上移除,需要程序在退出前自己完成卸载工作,可以用如下API实现
ULONG RemoveVectoredExceptionHandler{
PVOID VectoredHandlerHnalde
};
只有一个参数即VectoredHnalderHandle
就是前面保存的AddVectoredExcpetionHandler
的返回值
回调函数的参数PEXCEPTION_PRIONERS
与在顶层异常处理回调函数中用到的参数相同,即EXCEPTION_POINTERS
结构的指针。回调函数合理的返回值有2个,分别是EXCEPTION_CONTINUE_EXECUTION
和EXCEPTION_CONTINUE_SEARCH
,意义与SEH回调函数一致
VEH和SEH的异同
在初识SEH中,VEH只发生在用户层的线程
并且当异常发生时VEH会优先与SEH获得控制权
如果有调试器,调试器会优先于VEH回调函数
系统会自动调用AddVectoredExceptionHandler
注册的VEH回调函数
如果回调函数修复了异常
则返回EXCEPTION_CONTINUE_EXECUTION
在异常发生时以CONTEXT
指定的线程环境继续运行,此时SEH处理过程将被跳过
如果回调函数不能处理异常,回到函数应返回EXCEPTION_CONTINUE_SEARCH
系统会采取与SEH
大致相同的策略遍历VEH
链表。
如果整个链表搜索完毕,没有回调函数对异常进行处理,就将控制权转移给系统,由系统继续遍历SEH链表上注册的回调函数,然后就是SEH机制
区别
-
注册机制不同,SEH的相关信息主要保存在栈中,而且后注册的回调函数总是处于SEH链的前端,也就是说,当异常发生时,异常总是由内层回调函数优先处理,只有在内层回调不处理异常时,外层回调才有机会获得控制权。而VEH不同,它的相关信息保存在独立的链表(实际存储在ntdll.dll),在注册VEH时可以指定回调函数是位于VEH链表的前端和后端,这就解决了我们系统在SEH中获得优先处理权的问题
-
优先级不同
VEH先于SEH被调用
如果VEH表明自己已经 处理了异常,那么SEH就没机会再处理异常
-
作用范围不同
SEH机制基于线程
作用范围也是基于同一个线程之间
而VEH在整个进程范围内都是有效的
可以捕获和处理所有线程产生的异常
-
VEH不需要栈展开,有序SEH的注册和使用一来于函数调用的栈帧,在嗲用SEH回调函数会涉及栈展开的问题,这样SEH就有两次被调用的机会
而VEH的实现不一来栈,所以在调用VEH回调函数前不需要进行栈展开
只有一次被调用的机会
VCH
从Windows Vista
开始,微软又为VEH处理增加了新的内容-VCH
与VEH类似,它使用一下两个API进行注册和注销
需要注意的是VCH回调函数的调用时机
分析ntdll!RtlDispatchException
的代码可知
VCH会在两种情况下被调用
-
当SEH机制无法正常运行的情况(相关数据结构被破坏,未通过SafeSEH或者SEHOP验证)SEH分发将被跳过
-
当SEH回调函数能够返回
ntdll!RtlDispatchException
函数时,因为SEH处理程序具有特殊性,在执行SEH回调函数的时候,不仅有可能直接跳转到其他位置执行(例如EXCEPTION_EXECUTE_HANDLER的情况,会跳转的Try的结束处)也有可能不在返回,所有,只有当SEH回调函数返回了ExceptinContinueExecutinon
或者UEF函数修复了异常,或者UEF因为调试器的存在在其终结处理被跳过的情况,SEH才会返回ntdll!RtlDispatchException函数,此时才有机会执行VCH回调函数 -
总的来说,VCH的调用时机是在SEH处理之后,返回值可以忽略掉