Win32 SEH异常深度探索_6 回退

来源:互联网 发布:vb.net高级开发指南 编辑:程序博客网 时间:2024/05/23 12:43

Unwinding

Let's briefly recap what unwinding meansbefore digging into the code that implements it. Earlier, I described howpotential exception handlers are kept in a linked list, pointed to by the firstDWORD of the thread information block (FS:[0]). Since the handler for aparticular exception may not be at the head of the list, there needs to be anorderly way of removing all exception handlers in the list that are ahead ofthe handler that actually deals with the exception.

由于处理异常的 handler 通常不位于异常链表的头部,所以在他之前的 handler 必须按一定顺序作清除处理。

 

As you saw in the Visual C++__except_handler3 function, unwinding is performed by the __global_unwind2 RTLfunction. This function is just a very thin wrapper around the undocumentedRtlUnwind API:

回退是通过__global_unwind2 实现,他仅仅简单包装了一下RtlUnwind接口:

__global_unwind2(void * pRegistFrame)

 {

     _RtlUnwind( pRegistFrame,

                 &__ret_label,

                 0, 0 );

     __ret_label:

 }

 

While RtlUnwind is acritical API for implementing compiler-level SEH, it's not documented anywhere.While technically a KERNEL32 function, the Windows NT KERNEL32.DLL forwards thecall to NTDLL.DLL, which also has an RtlUnwind function. I was able to piecetogether some pseudocode for it, which appears in Figure 12.

RtlUnwind 是用于实现编译器级异常处理的最重要的一个 API,但他却没任何文档。他在 KERNEL32.DLL 中实现,调用了 NTDLL.DLL 中的RtlUnwind 函数。下面是一些伪代码:

void _RtlUnwind( PEXCEPTION_REGISTRATION pRegistrationFrame,

                  PVOIDreturnAddr,  //Not used! (At least on i386)

                  PEXCEPTION_RECORDpExcptRec,

                  DWORD _eax_value )

 {

     DWORD   stackUserBase;

     DWORD   stackUserTop;

     EXCEPTION_RECORD  exceptRec;   

     CONTEXT context;

 

     //Get stack boundaries from FS:[4] and FS:[8]

     RtlpGetStackLimits( &stackUserBase,&stackUserTop );

 

     if( 0 == pExcptRec )   // The normal case

     {

         pExcptRec = &excptRec;

 

        pExcptRec->ExceptionFlags = 0;

         pExcptRec->ExceptionCode= STATUS_UNWIND;

        pExcptRec->ExceptionRecord = 0;

         // Get returnaddress off the stack

        pExcptRec->ExceptionAddress = RtlpGetReturnAddress();

        pExcptRec->ExceptionInformation[0] = 0;

     }

 

     if( pRegistrationFrame )

        pExcptRec->ExceptionFlags |= EXCEPTION_UNWINDING;

     else

        pExcptRec->ExceptionFlags|=(EXCEPTION_UNWINDING|EXCEPTION_EXIT_UNWIND);

 

     context.ContextFlags =

         (CONTEXT_i486 |CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS);

 

     RtlpCaptureContext(&context );

 

     context.Esp += 0x10;

     context.Eax = _eax_value;

 

     PEXCEPTION_REGISTRATIONpExcptRegHead;

 

     pExcptRegHead =RtlpGetRegistrationHead();  // Retrieve FS:[0]

 

     //Begin traversing the list of EXCEPTION_REGISTRATION

     while ( -1 !=pExcptRegHead )

     {

         EXCEPTION_RECORD excptRec2;

 

         if (pExcptRegHead == pRegistrationFrame )

         {

            _NtContinue( &context, 0 );

         }

         else

         {

             // If there's an exception frame, but it's lower on the stack

             // thenthe head of the exception list, something's wrong!

             if ( pRegistrationFrame && (pRegistrationFrame <=pExcptRegHead) )

             {

                 // Generate an exception to bail out

                 excptRec2.ExceptionRecord= pExcptRec;

                excptRec2.NumberParameters = 0;

                excptRec2.ExceptionCode = STATUS_INVALID_UNWIND_TARGET;

                excptRec2.ExceptionFlags = EXCEPTION_NONCONTINUABLE;   

 

                 _RtlRaiseException(&exceptRec2 );

             }

         }

 

         PVOID pStack =pExcptRegHead + 8; //8==sizeof(EXCEPTION_REGISTRATION)

 

         if(    (stackUserBase <= pExcptRegHead)   // Makesure that

             &&  (stackUserTop >= pStack )           // pExcptRegHeadis in

             &&  (0 == (pExcptRegHead & 3)) )        // range, anda multiple

         {                                           // of 4 (i.e., sane)

             DWORD pNewRegistHead;

             DWORD retValue;

 

             retValue =RtlpExecutehandlerForUnwind(

                            pExcptRec, pExcptRegHead, &context,

                            &pNewRegistHead, pExceptRegHead->handler );

 

             if ( retValue != DISPOSITION_CONTINUE_SEARCH )

             {

                 if (retValue != DISPOSITION_COLLIDED_UNWIND )

                 {

                    excptRec2.ExceptionRecord = pExcptRec;

             excptRec2.NumberParameters = 0;

                    excptRec2.ExceptionCode = STATUS_INVALID_DISPOSITION;

                     excptRec2.ExceptionFlags =EXCEPTION_NONCONTINUABLE;   

 

                    RtlRaiseException( &excptRec2 );

                 }

                 else

                     pExcptRegHead =pNewRegistHead;

             }

 

             PEXCEPTION_REGISTRATIONpCurrExcptReg = pExcptRegHead;

            pExcptRegHead = pExcptRegHead->prev;

 

            RtlpUnlinkHandler( pCurrExcptReg );

         }

         else    // The stacklooks goofy!  Raise an exception to bailout

         {

             excptRec2.ExceptionRecord = pExcptRec;

            excptRec2.NumberParameters = 0;

             excptRec2.ExceptionCode= STATUS_BAD_STACK;

            excptRec2.ExceptionFlags = EXCEPTION_NONCONTINUABLE;   

 

             RtlRaiseException(&excptRec2 );

         }

     }

 

     // Ifwe get here, we reached the end of the EXCEPTION_REGISTRATION list.

     //This shouldn't happen normally.

 

     if( -1 == pRegistrationFrame )

         NtContinue( &context, 0);

     else

         NtRaiseException(pExcptRec, &context, 0 );

 

 }

While RtlUnwind looksimposing, it's not hard to understand if you methodically break it down. TheAPI begins by retrieving the current top and bottom of the thread's stack fromFS:[4] and FS:[8]. These values are important later as sanity checks to ensurethat all of the exception frames being unwound fall within the stack region.

他首先获取当前线程的栈顶和栈底 (分别存在FS:[4] FS:[8])。这个用于在以后判断异常帧是否在栈内。

 

RtlUnwind next builds adummy EXCEPTION_RECORD on the stack and sets the ExceptionCode field toSTATUS_UNWIND. Also, the EXCEPTION_UNWINDING flag is set in the ExceptionFlagsfield of the EXCEPTION_RECORD. A pointer to this structure will later be passedas a parameter to each exception callback. Afterwards, the code calls the_RtlpCaptureContext function to create a dummy CONTEXT structure that alsobecomes a parameter for the unwind call of the exception callback.

然后创建了一个伪EXCEPTION_RECORD,并设置ExceptionCode STATUS_UNWIND 这个结构将被传入每个异常回调函数。然后他还调用_RtlpCaptureContext 获取当前的CONTEXT 结构。

 

The remainder ofRtlUnwind traverses the linked list of EXCEPTION_REGISTRATION structures. Foreach frame, the code calls the RtlpExecuteHandlerForUnwind function, which I'llcover later. It's this function that calls the exception callback with theEXCEPTION_UNWINDING flag set. After each callback, the corresponding exceptionframe is removed by calling RtlpUnlinkHandler.

然后RtlUnwind 遍历EXCEPTION_REGISTRATION 链表,对每一个帧调用RtlpExecuteHandlerForUnwind,后者会调用异常回调函数并设置EXCEPTION_UNWINDING 标志。然后再调RtlpUnlinkHandler 删除当前帧。

 

RtlUnwind stopsunwinding frames when it gets to the frame with the address that was passed inas the first parameter. Interspersed with the code I've described issanity-checking code to ensure that everything looks okay. If some sort ofproblem crops up, RtlUnwind raises an exception to indicate what the problemwas, and this exception has the EXCEPTION_NONCONTINUABLE flag set. A processisn't allowed to continue execution when this flag is set, so it mustterminate.

当枚举到传入的帧时,RtlUnwind 停止了。在整个代码中有很多完整性检查的代码,如果发现问题,它会触发一个异常报告问题,并设置EXCEPTION_NONCONTINUABLE标志。当进程碰到这个标志时,他将停止运行。

 

如果去掉完整性检查等代码,上面可以简化如下:

原创粉丝点击