Win32 SEH异常深度探索_6 回退

Unwinding

Let's briefly recap what unwinding means before digging into the code that implements it. Earlier, I described how potential exception handlers are kept in a linked list, pointed to by the first DWORD of the thread information block (FS:[0]). Since the handler for a particular exception may not be at the head of the list, there needs to be an orderly way of removing all exception handlers in the list that are ahead of the 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 RTL function. This function is just a very thin wrapper around the undocumented RtlUnwind API:

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

__global_unwind2(void * pRegistFrame)

 {

     _RtlUnwind( pRegistFrame,

                 &__ret_label,

                 0, 0 );

     __ret_label:

 }

 

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

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

void _RtlUnwind( PEXCEPTION_REGISTRATION pRegistrationFrame,

                  PVOID returnAddr,  // Not used! (At least on i386)

                  PEXCEPTION_RECORD pExcptRec,

                  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 return address 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_REGISTRATION pExcptRegHead;

 

     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

             // then the 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 )   // Make sure that

             &&  (stackUserTop >= pStack )           // pExcptRegHead is in

             &&  (0 == (pExcptRegHead & 3)) )        // range, and a 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_REGISTRATION pCurrExcptReg = pExcptRegHead;

             pExcptRegHead = pExcptRegHead->prev;

 

             RtlpUnlinkHandler( pCurrExcptReg );

         }

         else     // The stack looks goofy!  Raise an exception to bail out

         {

             excptRec2.ExceptionRecord = pExcptRec;

             excptRec2.NumberParameters = 0;

             excptRec2.ExceptionCode = STATUS_BAD_STACK;

             excptRec2.ExceptionFlags = EXCEPTION_NONCONTINUABLE;   

 

             RtlRaiseException( &excptRec2 );

         }

     }

 

     // If we 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 looks imposing, it's not hard to understand if you methodically break it down. The API begins by retrieving the current top and bottom of the thread's stack from FS:[4] and FS:[8]. These values are important later as sanity checks to ensure that all of the exception frames being unwound fall within the stack region.

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

 

RtlUnwind next builds a dummy EXCEPTION_RECORD on the stack and sets the ExceptionCode field to STATUS_UNWIND. Also, the EXCEPTION_UNWINDING flag is set in the ExceptionFlags field of the EXCEPTION_RECORD. A pointer to this structure will later be passed as a parameter to each exception callback. Afterwards, the code calls the _RtlpCaptureContext function to create a dummy CONTEXT structure that also becomes a parameter for the unwind call of the exception callback.

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

 

The remainder of RtlUnwind traverses the linked list of EXCEPTION_REGISTRATION structures. For each frame, the code calls the RtlpExecuteHandlerForUnwind function, which I'll cover later. It's this function that calls the exception callback with the EXCEPTION_UNWINDING flag set. After each callback, the corresponding exception frame is removed by calling RtlpUnlinkHandler.

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

 

RtlUnwind stops unwinding frames when it gets to the frame with the address that was passed in as the first parameter. Interspersed with the code I've described is sanity-checking code to ensure that everything looks okay. If some sort of problem crops up, RtlUnwind raises an exception to indicate what the problem was, and this exception has the EXCEPTION_NONCONTINUABLE flag set. A process isn't allowed to continue execution when this flag is set, so it must terminate.

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

 

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值