关于SEH局部展开的一点思考

来源:互联网 发布:聚合数据公司怎么样 编辑:程序博客网 时间:2024/05/16 08:16

最近读了Matt Pietrek的《A Crash Course on the Depths of Win32 Structured Exception Handling》,有如醍醐灌顶,尤其是编译器级的SEH,如果是SEH的初学者,推荐读一下这篇文章,不要先去看《windows核心编程》的异常处理章节,那样只会越搞越乱。如果把SEH帧链和_SCOPETABLE链这两条链搞清了基本就OK了,至于细节部分值得深究的地方还是很多的。但在这篇文章中一个没有具体提到的问题就是SEH的局部展开,也就是_local_unwind函数,由于这是一个与编译器相关的函数,所以对研究系统的SEH没有障碍。后来我在张银奎的《软件调试》补编的异常编译章节中找到了一段该函数的伪码,摘录如下:

 

这段伪码中最主要的是12行开始的那个while循环,第18行判断pCurScopeEntry->lpfnFilter是否为NULL,原因是在VC中try_finally块和try_except块一样,都对应一个scopetable中的_SCOPETABLE表项,区别在于try_finally对应表项的lpfnFilter值为NULL,也就是说没有过滤表达式。因此以上代码可以看出,局部展开的任务主要是搜索_SCOPETABLE链中的try_finally节点并执行finally处理代码。为了验证这个说法,我写了一小段代码

#include <windows.h>
#include <stdio.h>

void ViewScopeTable(DWORD* pSEHFrame)
{
     DWORD* Scopetable = (DWORD*)*(pSEHFrame+2);
     DWORD TryLevel = *(pSEHFrame+3);
     DWORD* pCurTable = (DWORD*)(Scopetable + TryLevel*3);
     while( TRUE )
     {
          printf("prevTryLevel:%x/nlpfnfilter:%x/nlpfnHandler:%x/n/n",
                   *pCurTable,
                   *(pCurTable+1),
                   *(pCurTable+2)
                   );
          if( ( TryLevel = *(pCurTable) ) == 0xFFFFFFFF)
                   break;
          pCurTable = (DWORD*)(Scopetable + TryLevel*3);
       }
}

 

void ShowSEHFrame()
{
          DWORD pSEHHead;
          _asm mov eax, fs:[0]
          _asm mov pSEHHead, eax
          printf("fs:[0]:%x/n/n", pSEHHead);
          DWORD* pHead = (DWORD*)pSEHHead;
          while(TRUE)
          {
                 printf("Frame addr:%x/nprev:%x/nhandler:%x/nscopetable:%x/ntrylevel:%x/n/n",
                          Head,
                         *pHead,
                         *(pHead+1),
                         *(pHead+2),
                         *(pHead+3)
                         );
                ViewScopeTable(pHead);
                if( *pHead == 0xFFFFFFFF )
                      break;
                pHead = (DWORD*)*pHead;
            }
}

void func1()
{
     _try{
              _try{
                           _try{
                                          ShowSEHFrame();
                          }_except( EXCEPTION_CONTINUE_SEARCH )    //except块
                          {

 

                           }
               }_finally   //有意设置成finally块

               {

 

               }
      }_except( EXCEPTION_CONTINUE_SEARCH )     //except块
      {

 

      }
}

 

int main()
{
 func1();
}

 

执行结果如下:

 

 

可见try_finally块和try_except一样被插入到_SCOPETABLE链中,只是lpfnFilter为0。

在Matt Pietrek的_except_handler3的伪码中有这样一句:

if ( pRegistrationFrame->scopetable[trylevel].lpfnFilter )

        .......

即判断_SCOPETABLE项的lpfnFilter,若是try_finally块则直接略过该节点,查询下一节点。而在上述的_local_unwind2伪码中,若是try_except节点则略过,查询下一节点。由此可以总结出:

try_except节点在发生异常时搜索异常处理块时被使用,而try_finally节点则用于局部展开的时候。

 

由于VC中的异常handler都指向_except_handler3,不是一个异常处理块对应一个异常处理帧,故没有办法在全局展开时直接用_except_handler3完成清理工作,所以有了try_finally结构,这样看来这个结构的出现也是在情理之中的。

原创粉丝点击