深入探索Win32结构化异常处理 - 图文(7)

2019-08-17 13:54

NtWaitForSingleObject( hEvent, 1, 0 ); return EXCEPTION_CONTINUE_SEARCH; }

}

if ( _BasepAlreadyHadHardError )

NtTerminateProcess(GetCurrentProcess(), pExcptRec->ExceptionCode); }

return EXCEPTION_EXECUTE_HANDLER; }

LPTOP_LEVEL_EXCEPTION_FILTER SetUnhandledExceptionFilter(

LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter ) {

// _BasepCurrentTopLevelFilter 是KERNEL32.DLL中的一个全局变量 LPTOP_LEVEL_EXCEPTION_FILTER previous= _BasepCurrentTopLevelFilter; // 设置为新值

_BasepCurrentTopLevelFilter = lpTopLevelExceptionFilter; return previous; // 返回以前的值 }

UnhandledExceptionFilter 接下来的任务是确定进程是否运行于Win32调试器下。也就是进程的创建标志中是否带有标志 DEBUG_PROCESS 或 DEBUG_ONLY_THIS_PROCESS 。 它使用NtQueryInformationProcess函数来确定进程是否正在被调试,我在本月的Under the Hood专栏中讲解了这个函数。如果正在被调试,UnhandledExceptionFilter就返回

EXCEPTION_CONTINUE_SEARCH,这告诉系统去唤醒调试器并告诉它在被调试程序(debuggee)中产生了一个异常。

UnhandledExceptionFilter 接 下来调用用户安装的未处理异常过滤器(如果存在的话)。通常情况下,用户并没有安装回调函数,但是用户可以调用 SetUnhandledExceptionFilter这个API来安装。上面我也提供了这个API的伪代码。这个函数只是简单地用用户安装的回调函数 的地址来替换一个全局变量,并返回替换前的值。

有 了初步的准备之后,UnhandledExceptionFilter就开始做它的主要工作:用一个时髦的应用程序错误对话框来通知你犯了低级的编程错 误。有两种方法可以避免出现这个对话框。第一种方法是调用SetErrorMode函数并指定SEM_NOGPFAULTERRORBOX标志。另一种方 法是将AeDebug子键下的Auto的值设为1。此时UnhandledExceptionFilter跳过应用程序错误对话框直接启动AeDebug 子键下的Debugger的值所指定的调试器。如果你熟悉“即时调试(Just In Time Debugging,JIT)”的话,这就是操作系统支持它的地方。接下来我会详细讲。

大 多数情况下,上面的两个条件都为假。这样UnhandledExceptionFilter就调用NTDLL.DLL中的 NtRaiseHardError函数。正是这个函数产生了应用程序错误对话框。这个对话框等待你单击“确定”按钮来终止进程,或者单击“取消”按钮来调 试它。(单击“取消”按钮而不是“确定”按钮来加载调试器好像有点颠倒了,可能这只是我个人的感觉吧。)

如果你单击“确定”,UnhandledExceptionFilter就返回EXCEPTION_EXECUTE_HANDLER 。调用 UnhandledExceptionFilter 的进程通常通过终止自身来作为响应(正像你在

BaseProcessStart的伪代码中看到的那样)。这就产生了一个有趣的问题——大多数人都认为

是系 统终止了产生未处理异常的进程,而实际上更准确的说法应该是,系统进行了一些设置使得产生未处理异常的进程将自身终止掉了。

UnhandledExceptionFilter 执 行时真正有意思的部分是当你单击应用程序错误对话框中的“取消”按钮,此时系统将调试器附加(attach)到出错进程上。这段代码首先调用 CreateEvent来创建一个事件内核对象,调试器成功附加到出错进程之后会将此事件对象变成有信号状态。这个事件句柄以及出错进程的ID都被传到 sprintf函数,由它将其格式化成一个命令行,用来启动调试器。一切就绪之后,UnhandledExceptionFilter就调用 CreateProcess来启动调试器。如果CreateProcess成功,它就调用NtWaitForSingleObject来等待前面创建的那 个事件对象。此时这个调用被阻塞,直到调试器进程将此事件变成有信号状态,以表明它已经成功附加到出错进程上。 UnhandledExceptionFilter函数中还有一些其它的代码,我在这里只讲重要的。

进入地狱

如 果你已经走了这么远,不把整个过程讲完对你有点不公平。我已经讲了当异常发生时操作系统是如何调用用户定义的回调函数的。我也讲了这些回调的内部情况,以 及编译器是如何使用它们来实现__try和__except的。我甚至还讲了当某个异常没有被处理时所发生的情况以及系统所做的扫尾工作。剩下的就只有异 常回调过程最初是从哪里开始的这个问题了。好吧,让我们深入系统内部来看一下结构化异常处理的开始阶段吧。

图 14是我为KiUserExceptionDispatcher函数和一些相关函数写的伪代码。这个函数在NTDLL.DLL中,它是异常处理执行的起 点。为了绝对准确起见,我必须指出:刚才说的并不是绝对准确。例如在Intel平台上,一个异常导致CPU将控制权转到ring 0(0特权级,即内核模式)的一个处理程序上。这个处理程序由中断描述符表(Interrupt Descriptor Table,IDT)中的一个元素定义,它是专门用来处理相应异常的。我跳过所有的内核模式代码,假设当异常发生时CPU直接将控制权转到了 KiUserExceptionDispatcher函数。 图14 KiUserExceptionDispatcher的伪代码

KiUserExceptionDispatcher( PEXCEPTION_RECORD pExcptRec, CONTEXT * pContext ) {

DWORD retValue;

// 注意:如果异常被处理,那么RtlDispatchException函数就不会返回 if ( RtlDispatchException( pExceptRec, pContext ) ) retValue = NtContinue( pContext, 0 ); else

retValue = NtRaiseException( pExceptRec, pContext, 0 ); EXCEPTION_RECORD excptRec2;

excptRec2.ExceptionCode = retValue;

excptRec2.ExceptionFlags = EXCEPTION_NONCONTINUABLE; excptRec2.ExceptionRecord = pExcptRec; excptRec2.NumberParameters = 0; RtlRaiseException( &excptRec2 ); }

int RtlDispatchException( PEXCEPTION_RECORD pExcptRec, CONTEXT * pContext ) {

DWORD stackUserBase; DWORD stackUserTop;

PEXCEPTION_REGISTRATION pRegistrationFrame; DWORD hLog;

// 从FS:[4]和FS:[8]处获取堆栈的界限

RtlpGetStackLimits( &stackUserBase, &stackUserTop ); pRegistrationFrame = RtlpGetRegistrationHead(); while ( -1 != pRegistrationFrame ) {

PVOID justPastRegistrationFrame = &pRegistrationFrame + 8; if ( stackUserBase > justPastRegistrationFrame ) {

pExcptRec->ExceptionFlags |= EH_STACK_INVALID; return DISPOSITION_DISMISS; // 0 }

if ( stackUsertop < justPastRegistrationFrame ) {

pExcptRec->ExceptionFlags |= EH_STACK_INVALID; return DISPOSITION_DISMISS; // 0 }

if ( pRegistrationFrame & 3 ) // 确保堆栈按DWORD对齐 {

pExcptRec->ExceptionFlags |= EH_STACK_INVALID; return DISPOSITION_DISMISS; // 0 }

if ( someProcessFlag ) {

hLog = RtlpLogExceptionHandler( pExcptRec, pContext, 0, pRegistrationFrame, 0x10 ); }

DWORD retValue, dispatcherContext; pContext, &dispatcherContext, pRegistrationFrame->handler ); if ( someProcessFlag )

RtlpLogLastExceptionDisposition( hLog, retValue ); if ( 0 == pRegistrationFrame ) {

pExcptRec->ExceptionFlags &= ~EH_NESTED_CALL; // 关闭标志 }

EXCEPTION_RECORD excptRec2; DWORD yetAnotherValue = 0;

if ( DISPOSITION_DISMISS == retValue ) {

if ( pExcptRec->ExceptionFlags & EH_NONCONTINUABLE ) {

ndlerForException(pExcptRec, pRegistrationFrame,

excptRec2.ExceptionRecord = pExcptRec;

excptRec2.ExceptionNumber = STATUS_NONCONTINUABLE_EXCEPTION; excptRec2.ExceptionFlags = EH_NONCONTINUABLE; excptRec2.NumberParameters = 0; RtlRaiseException( &excptRec2 ); } else

return DISPOSITION_CONTINUE_SEARCH; }

else if ( DISPOSITION_CONTINUE_SEARCH == retValue ) {}

else if ( DISPOSITION_NESTED_EXCEPTION == retValue ) {

pExcptRec->ExceptionFlags |= EH_EXIT_UNWIND; if ( dispatcherContext > yetAnotherValue ) yetAnotherValue = dispatcherContext; }

else // DISPOSITION_COLLIDED_UNWIND {

excptRec2.ExceptionRecord = pExcptRec;

excptRec2.ExceptionNumber = STATUS_INVALID_DISPOSITION; excptRec2.ExceptionFlags = EH_NONCONTINUABLE; excptRec2.NumberParameters = 0; RtlRaiseException( &excptRec2 ); }

pRegistrationFrame = pRegistrationFrame->prev; // 转到前一个帧 }

return DISPOSITION_DISMISS; }

_RtlpExecuteHandlerForException: // 处理异常(第一次) MOV EDX,XXXXXXXX JMP ExecuteHandler

RtlpExecutehandlerForUnwind: // 处理展开(第二次) MOV EDX,XXXXXXXX

int ExecuteHandler( PEXCEPTION_RECORD pExcptRec, PEXCEPTION_REGISTRATION pExcptReg, CONTEXT * pContext, PVOID pDispatcherContext,

FARPROC handler ) // 实际上是指向_except_handler()的指针 {

// 安装一个EXCEPTION_REGISTRATION帧,EDX指向相应的handler代码 PUSH EDX

PUSH FS:[0] MOV FS:[0],ESP // 调用异常处理回调函数

EAX = handler( pExcptRec, pExcptReg, pContext, pDispatcherContext ); // 移除EXCEPTION_REGISTRATION帧 MOV ESP,DWORD PTR FS:[00000000] POP DWORD PTR FS:[00000000] return EAX; }

_RtlpExecuteHandlerForException 使用的异常处理程序: {

// 如果设置了展开标志,返回DISPOSITION_CONTINUE_SEARCH

// 否则,给pDispatcherContext赋值并返回DISPOSITION_NESTED_EXCEPTION return pExcptRec->ExceptionFlags & EXCEPTION_UNWIND_CONTEXT ? DISPOSITION_CONTINUE_SEARC : ( *pDispatcherContext =

pRegistrationFrame->scopetable, DISPOSITION_NESTED_EXCEPTION ); }

_RtlpExecuteHandlerForUnwind 使用的异常处理程序: {

// 如果设置了展开标志,返回DISPOSITION_CONTINUE_SEARCH

// 否则,给pDispatcherContext赋值并返回DISPOSITION_COLLIDED_UNWIND

return pExcptRec->ExceptionFlags & EXCEPTION_UNWIND_CONTEXT ? DISPOSITION_CONTINUE_SEARCH : ( *pDispatcherContext =

pRegistrationFrame->scopetable, DISPOSITION_COLLIDED_UNWIND ); }

KiUserExceptionDispatcher 的 核心是对RtlDispatchException的调用。这拉开了搜索已注册的异常处理程序的序幕。如果某个处理程序处理这个异常并继续执行,那么对 RtlDispatchException的调用就不会返回。如果它返回了,只有两种可能:或者调用了NtContinue以便让进程继续执行,或者产生 了新的异常。如果是这样,那异常就不能再继续处理了,必须终止进程。

现在把目光对准 RtlDispatchException 函 数的代码,这就是我通篇提到的遍历异常帧的代码。这个函数获取一个指向EXCEPTION_REGISTRATION结构链表的指针,然后遍历此链表以寻 找一个异常处理程序。由于堆栈可能已经被破坏了,所以这个例程非常谨慎。在调用每个EXCEPTION_REGISTRATION结构中指定的异常处理程 序之前,它确保这个结构是按DWORD对齐的,并且是在线程的堆栈之中,同时在堆栈中比前一个EXCEPTION_REGISTRATION结构高。

RtlDispatchException 并 不直接调用EXCEPTION_REGISTRATION结构中指定的异常处理程序。相反,它调用 RtlpExecuteHandlerForException来完成这个工作。根据

RtlpExecuteHandlerForException的执 行情况,RtlDispatchException或者继续遍历异常帧,或者引发另一个异常。这第二次的异常表明异常处理程序内部出现了错误,这样就不能 继续执行下去了。


深入探索Win32结构化异常处理 - 图文(7).doc 将本文的Word文档下载到电脑 下载失败或者文档不完整,请联系客服人员解决!

下一篇:七年级数学下册《2.1 两条直线的位置关系(一)》教学设计(新版

相关阅读
本类排行
× 注册会员免费下载(下载后可以自由复制和排版)

马上注册会员

注:下载文档有可能“只有目录或者内容不全”等情况,请下载之前注意辨别,如果您已付费且无法下载或内容有问题,请联系我们协助你处理。
微信: QQ: