Win32结构化异常处理(SEH)探秘(下)
2010-10-15 09:07:35 来源:Web开发网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的。我甚至还讲了当某个异常没有被处理时所发生的情况以及系统所做的扫尾工作。剩下的就只有异常回调过程最初是从哪里开始的这个问题了。好吧,让我们深入系统内部来看一下结构化异常处理的开始阶段吧。
图十四是我为 KiUserExceptionDispatcher 函数和一些相关函数写的伪代码。这个函数在NTDLL.DLL中,它是异常处理执行的起点。为了绝对准确起见,我必须指出:刚才说的并不是绝对准确。例如在Intel平台上,一个异常导致CPU将控制权转到ring 0(0特权级,即内核模式)的一个处理程序上。这个处理程序由中断描述符表(Interrupt Descriptor Table,IDT)中的一个元素定义,它是专门用来处理相应异常的。我跳过所有的内核模式代码,假设当异常发生时CPU直接将控制权转到了 KiUserExceptionDispatcher 函数。
图十四 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;
retValue= RtlpExecuteHandlerForException(pExcptRec, pRegistrationFrame,
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 )
{
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 );
}
更多精彩
赞助商链接