您的位置:首页 > 其它

Win32 SEH异常深度探索_8 异常处理是如何开始的

2009-09-25 13:56 597 查看
If you've made it this far, it wouldn't be
fair to finish without completing the entire circuit. I've shown how the
operating system calls a user-defined function when an exception occurs. I've
shown what typically goes on inside of those callbacks, and how compilers use
them to implement _try and _catch. I've even shown what happens when nobody
handles the exception and the system has to do the mopping up. All that remains
is to show where the exception callbacks originate from in the first place.
Yes, let's plunge into the bowels of the system and see the beginning stages of
the structured exception handling sequence.

这里要讲的是异常回调函数从哪开始第一步的,这将深入系统内部看看结构化异常处理流程的开始部分。

Figure 14

shows some pseudocode I whipped up for
KiUserExceptionDispatcher and some related functions. KiUserExceptionDispatcher
is in NTDLL.DLL and is where execution begins after an exception occurs. To be
100 percent accurate, what I just said isn't exactly true. For instance, in the
Intel architecture an exception causes control to vector to a ring 0 (kernel
mode) handler. The handler is defined by the interrupt descriptor table entry
that corresponds to an exception. I'm going to skip all that kernel mode code
and pretend that the CPU goes straight to KiUserExceptionDispatcher upon an
exception

异常发生时,最先调用的是

NTDLL.DLL
中的

KiUserExceptionDispatcher
。不过要更准确地说,在

Intel
架构中如果异常发生,控制将转入

ring 0 (
内核模式

)
处理器。这个处理器定义在中断派发表中。这里略过所有的内核代码,假定

CPU
直接进入

KiUserExceptionDispatcher


KiUserExceptionDispatcher( PEXCEPTION_RECORD pExcptRec, CONTEXT * pContext )
{
DWORD retValue;

// Note: If the exception is handled, RtlDispatchException() never returns
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;

// Get stack boundaries from FS:[4] and 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 )   // Make sure stack is DWORD aligned
{
pExcptRec->ExceptionFlags |= EH_STACK_INVALID;
return DISPOSITION_DISMISS; // 0
}

if ( someProcessFlag )
{
// Doesn't seem to do a whole heck of a lot.
hLog = RtlpLogExceptionHandler( pExcptRec, pContext, 0,
pRegistrationFrame, 0x10 );
}

DWORD retValue, dispatcherContext;

retValue= RtlpExecuteHandlerForException(pExcptRec, pRegistrationFrame,
pContext, &dispatcherContext,
pRegistrationFrame->handler );

// Doesn't seem to do a whole heck of a lot.
if ( someProcessFlag )
RtlpLogLastExceptionDisposition( hLog, retValue );

if ( 0 == pRegistrationFrame )
{
pExcptRec->ExceptionFlags &= ~EH_NESTED_CALL;   // Turn off flag
}

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;  // Go to previous frame
}

return DISPOSITION_DISMISS;
}

_RtlpExecuteHandlerForException:    // Handles exception (first time through)
MOV     EDX,XXXXXXXX
JMP     ExecuteHandler

RtlpExecutehandlerForUnwind:        // Handles unwind (second time through)
MOV     EDX,XXXXXXXX

int ExecuteHandler( PEXCEPTION_RECORD pExcptRec
PEXCEPTION_REGISTRATION pExcptReg
CONTEXT * pContext
PVOID pDispatcherContext,
FARPROC handler ) // Really a ptr to an _except_handler()

// Set up an EXCEPTION_REGISTRATION, where EDX points to the
// appropriate handler code shown below
PUSH    EDX
PUSH    FS:[0]
MOV     FS:[0],ESP

// Invoke the exception callback function
EAX = handler( pExcptRec, pExcptReg, pContext, pDispatcherContext );

// Remove the minimal EXCEPTION_REGISTRATION frame
MOV     ESP,DWORD PTR FS:[00000000]
POP     DWORD PTR FS:[00000000]

return EAX;
}

Exception handler used for _RtlpExecuteHandlerForException:
{
// If unwind flag set, return DISPOSITION_CONTINUE_SEARCH, else
// assign pDispatcher context and return DISPOSITION_NESTED_EXCEPTION

return pExcptRec->ExceptionFlags & EXCEPTION_UNWIND_CONTEXT
? DISPOSITION_CONTINUE_SEARCH
: *pDispatcherContext = pRegistrationFrame->scopetable,
DISPOSITION_NESTED_EXCEPTION;
}

Exception handler used for _RtlpExecuteHandlerForUnwind:
{
// If unwind flag set, return DISPOSITION_CONTINUE_SEARCH, else
// assign pDispatcher context and return DISPOSITION_COLLIDED_UNWIND

return pExcptRec->ExceptionFlags & EXCEPTION_UNWIND_CONTEXT
? DISPOSITION_CONTINUE_SEARCH
: *pDispatcherContext = pRegistrationFrame->scopetable,
DISPOSITION_COLLIDED_UNWIND;
}


The heart of KiUserExceptionDispatcher is
its call to RtlDispatchException. This kicks off the search for any registered
exception handlers. If a handler handles the exception and continues execution,
the call to RtlDispatchException never returns. If RtlDispatchException
returns, there are two possible paths: either NtContinue is called, which lets
the process continues, or another exception is raised. This time, the exception
isn't continuable, and the process must terminate.

KiUserExceptionDispatcher
的核心是调用

RtlDispatchException
,他将开始查找已经注册的异常处理器。如果有处理器处理异常,那么

RtlDispatchException
将不会返回。如果

RtlDispatchException

返回了,那么进程或者调用

NtContinue
继续进程,或

NtRaiseException

再次抛出一个异常导致进程退出。

Moving on to the RtlDispatchExceptionCode,
this is where you'll find the exception frame walking code that I've referred
to throughout this article. The function grabs a pointer to the linked list of
EXCEPTION_REGISTRATIONs and iterates over every node, looking for a handler.
Because of the possibility of stack corruption, the routine is very paranoid.
Before calling the handler specified in each EXCEPTION_REGISTRATION, the code
ensures that the EXCEPTION_REGISTRATION is DWORD-aligned, within the thread's
stack, and higher on the stack than the previous EXCEPTION_REGISTRATION.

RtlDispatchExceptionCode
的实现就像前面提到的那样,获取异常处理器链表头,遍历链表查找是否有合适的处理器。有了防止栈被破坏,这里会不断作些检查。

RtlDispatchException doesn't directly call
the address specified in the EXCEPTION_REGISTRATION structure. Instead, it
calls RtlpExecuteHandlerForException to do the dirty work. Depending on what
happens inside RtlpExecuteHandlerForException, RtlDispatchException either
continues walking the exception frames or raises another exception. This
secondary exception indicates that something went wrong inside the exception
callback and that execution can't continue.

RtlDispatchException
并不直接调用

EXCEPTION_REGISTRATION
指定的处理器函数地址,而是把这工作交给了

RtlpExecuteHandlerForException


根据

RtlpExecuteHandlerForException
的处理情况,

RtlDispatchException
可能继续遍历链表,或再次抛出异常。第二次异常说明异常回调函数内部有错误发生,进程不能继续运行。

The code for RtlpExecuteHandlerForException
is closely related to another function, RtlpExecutehandlerForUnwind. You may
recall that I mentioned this function earlier when I described unwinding. Both
of these "functions" simply load the EDX register with different
values before sending control to the ExecuteHandler function. Put another way,
RtlpExecuteHandlerForException and RtlpExecutehandlerForUnwind are separate
front ends to a common function, ExecuteHandler.

RtlpExecuteHandlerForException
和另一个函数

RtlpExecutehandlerForUnwind
非常相像,他们仅仅开始给

EDX
存入不同的值

(
其实是不同的异常处理函数

)
,然后都调用了

ExecuteHandler


ExecuteHandler is where the handler field
from the EXCEPTION_REGISTRATION is extracted and called. Strange as it may
seem, the call to the exception callback is itself wrapped by a structured
exception handler. Using SEH within itself seems a bit funky but it makes sense
if you ponder it for a moment. If an exception callback causes another
exception, the operating system needs to know about it. Depending on whether
the exception occurred during the initial callback or during the unwind
callback, ExecuteHandler returns either DISPOSITION_NESTED_ EXCEPTION or
DISPOSITION_COLLIDED_UNWIND. Both are basically "Red Alert! Shut
everything down now!" kind of codes.

ExecuteHandler
再调用

EXCEPTION_REGISTRATION
中的异常回调函数。这里可以发现,他和

RtlpExecuteHandlerForException
结合起来,把这个调用又包含在

SEH
块中。这样做是有意义的,如果异常回调函数触发了另一个异常,操作系统需要获得通知。如果在第一次调用异常回调函数或回退时,

ExecuteHandler
返回了

DISPOSITION_NESTED_ EXCEPTION


DISPOSITION_COLLIDED_UNWIND
,就代表了“警报,把进程杀掉!”
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: