您的位置:首页 > 其它

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

2009-11-05 20:47 344 查看
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 。

view plaincopy to clipboardprint?
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;
}
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 ,就代表了“警报,把进程杀掉!”

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/hongjiqin/archive/2009/09/25/4593849.aspx
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: