标 题 :  SEH源码赏析之C篇
作 者 :  xinlin
时 间 :  2007 - 10 - 24 , 12 : 12
链 接 :  http : //bbs.pediy.com/showthread.php?t=53778

SEH结构化异常处理源码赏析 ( C篇 )
                       关键字 :  C SEH 结构化异常处理 _try _finally _except _except_handler3 VC
                      工程使用工具 :  VC7 .1.3088  IDA pro  4.9.0.863  cdb Windbg Editplus

1. 起因
    C ++ 程序员对 try , catch , throw 都应该很熟悉 , 能知道VC怎么实现它的人就不多了 , 不过网络世界使很多人知道了它与SEH  ( structured exception      handling)有密切关系 , 我也不例外 , 也是在若干年前从网络知道了SEH , 并且大致也知道SEH的流程 . 但是和多数人一样在我的实践也很少直接使用 SEH , 对SEH也就仅限于网络上一些文章的介绍 . 曾经在用Windbg对某些软件作分析 , 我遇到了断点失效的情况 , 查找资料介绍是SEH中的 Handler清除了调试寄存器 , 在分析SEH代码中由于VC没有SEH的源码 , 于是我产生了一种想法 , 详细完整地翻译VC的SEH的代码 , 一劳永逸的解决问题 . C和C ++ 的SEH有所不同 , C ++ 的要复杂些 , 我在此介绍的仅为C的SEH代码 , 也就是__try , __finally , __except ,  __leave所产生的SEH代码 , C ++ 篇有时间的话我再作 . 我以前看过的资料大都以比较专业的语言介绍 , 在此我仅以我自己感觉比较通俗的语言介绍 , 希望能有更多的人能认识 , 认清SEH .
2.SEH 术语 : SEH中术语虽然不多 , 但由于没有SDK的明确定义有时很难区别 , 各家说的表述也不太统一 , 因此为此文特定义如下术语 , 这些术语可能与其它文献有点冲突或细微差别 , 有的也是我自己的定义 :
A . SEH ( structured exception handling ):  在C语言中关键字是__try ( _try ), __finally ( _finally ), _except ( __except ), 而C ++ 使用的关键字是  try , catch . 在以下的表述中所有的 try 均不再特别声明为C关键字 , 一律默认为C关键字 .
B .  EH3_List : _EH3_EXCEPTION_REGISTRATION链表 , 表头位于FS :[ 0 ], 0xFFFFFFFF 为链表结束标志 . 编译器在编译一个函数时只要检测到含有_try或__try则为此函数生成一个_EH3_EXCEPTION_REGISTRATION节点 , 并插入到表头 . 因为每个函数编译只生成一个节点 , 因此在一个函数中C和C ++ 的SEH不能同时存在 , 如果代码中同时有 catch 和except则不能通过编译就是此原因 .
C .  EH3_ScopeTable : 是一个由编译器在data section生成的一张表 ( 数组 ), 实质可看作是二叉树结构 ( 可能有多个二叉树顺序存放 ), 节点为_SCOPETABLE_ENTRY类型 , 其中 _SCOPETABLE_ENTRY . ParentLevel是父节点在数组中的位置 , EH3_ScopeTable [ 0 ] 是根节点 ,  _SCOPETABLE_ENTRY . ParentLevel = 0xFFFFFFFF. 由此可见ParentLevel很重要 , 是SEH判断 try 嵌套层次的唯一依据 . 编译器从函数入口点开始遍历 try , 每遇到一个 try 生成一个节点_SCOPETABLE_ENTRY , 并放在表最后 , 注意节点的先后与  try 的嵌套层次无关 .
D .  filter handler : 是异常发生后让用户决定是否认识此异常 , 通过修改异常语句的上下文环境 ( CONTEXT ) 可使应用程序能继续正常运行 . 其返回值有三 .
EXCEPTION_EXECUTE_HANDLER ( 1 ):  去执行exception handler , 然后进程终止 , 且不显示出错提示框 .
     EXCEPTION_CONTINUE_SEARCH ( 0 ):  不执行exception handler , 显示出错提示框 , 进程终止或者进入调试器进行调试 .
     EXCEPTION_CONTINUE_EXECUTION (- 1 ):  不执行exception handler , 系统用CONTEXT重新设置CPU环境 , 进程继续执行 , 如果修改了EIP则从新的EIP开始执行 , 否则从原异常点开始执行 .

     E .  exception handler : 是异常发生后检测到该异常无法被处理 , 而进程终止前提醒应用程序执行的收尾工作
F .  termination handler : 如果 try 语句被过早终止 , 不管是正常离开或者是非正常离开 , 包括 goto , leave及异常时均执行此handler , 具体执行过程可查MSDN .
G .  展开 ( unwind ): 这个名词很让人费解 , 翻译也确实不好命名 , 我也沿用此名 . 展开的目的是执行finally对应的termination handler , 对照在下面的源代码中我们就能很容易理解MSDN关于finally的解释了 . 展开分为本地局部展开 ( _local_unwind ) 和全局展开 ( _global_unwind ); 本地展开 : 展开此 try 所在函数 try 嵌套关系并分别执行其finally对应的termination handler ; 全局展开 : 当exception handler不在本 try 函数时 , 执行exception handler前需要先执行这之前的termination handler , 全局展开就是查找这些termination handler并执行它 . 需要说明的是全局展开不含本地展开 .
3.SEH 数据结构
typedef struct  // (sizeof=0xC)
{
     DWORD ParentLevel  ;      // 当前Handler父层TRY在EH3_ScopeTable中的位置,根没有上一层,故值=-1
                 // 形成TRY层次的二叉树结构,与_EH3_EXCEPTION_REGISTRATION.TryLevel物理意义一样
     DWORD FilterFunc ;       // 非NULL则HandlerFunc是exception handler,否则termination handler
     DWORD HandlerFunc ;      // exception handler or termination handler
}  _SCOPETABLE_ENTRY ;
typedef struct  // (sizeof=0x10)
{
     _EH3_EXCEPTION_REGISTRATION *  pPrev ; // 栈上一级EH3_List节点,=0xFFFFFFFF则为最后一个节点
     EXCE_HANDLER ExceptionHandler ;      // VC7.1中统一指向_except_handler3
     _SCOPETABLE_ENTRY *  pScopeTable ;      // 指向一个_SCOPETABLE_ENTRY数组,函数有n个TRY,则数组有n个元素
                       // p[0]->Try0为根,p[1]->Try1,p[2]->Try2...
     DWORD TryLevel ;      // 指示当前指令在Try的层次级别,-1未进入TRY,进第一个TRY为0,第二个为1,...
              // 但它与嵌套层次无关,在编译时确定,从函数代码开始处开始计数
}  _EH3_EXCEPTION_REGISTRATION ;
typedef struct  // (sizeof=0x10)还有待进一步分析其用处
{
     DWORD unKnown ;     // 未知:被编译器赋值
     DWORD HandlerFunc ;  // _SCOPETABLE_ENTRY.HandlerFunc
     DWORD firstPara ;     // Try所在函数第一个参数:crtMain!EBP+8
     DWORD TryEBP ;     // Try所在函数EBP
} _NLGDestination ;
// 以下在MSDN中均有定义,不再作解释
typedef struct  _EXCEPTION_RECORD
{
     DWORD    ExceptionCode ;
     DWORD ExceptionFlags ;
     struct  _EXCEPTION_RECORD  * ExceptionRecord ;
     PVOID ExceptionAddress ;
     DWORD NumberParameters ;
     ULONG_PTR ExceptionInformation [ EXCEPTION_MAXIMUM_PARAMETERS ];
}  EXCEPTION_RECORD ,* PEXCEPTION_RECORD ;
typedef struct  _CONTEXT
{
    ...          // 依据CPU类型有不同定义,具体可见winnt.h
}  CONTEXT ,* PCONTEXT ;
typedef struct  _EXCEPTION_POINTERS
{
     PEXCEPTION_RECORD ExceptionRecord ;
     PCONTEXT ContextRecord ;
}  EXCEPTION_POINTERS ,* PEXCEPTION_POINTERS ;
4.SEH 汇编要点
A .  函数中 try 的数据模型 : VC将会为有 try 函数在栈上首先建立三个变量 :
     EBP - 18h :     SaveESP           // TRY前保存当时ESP,因此有:SaveESP<&Eh3Exception
     EBP - 14h :     pExceInfo         // GetExceptionPointers(),在handler中调用filter前赋值
     EBP - 10h :     Eh3Exception       // 一个_EH3_EXCEPTION_REGISTRATION,其大小刚好为10h哦
     EBP + 00h :     ebp             // 上一frame的EBP
     EBP + 04h :     EIP             // CALL返回地址
     在这里我们应该注意到这个公式是成立的 :
           EBP =& Eh3Exception + 10h
     而在_except_handler3中参数 :  pEh3Exce刚好为 & Eh3Exception , 所以每当在_except_handler3中要回调filter , exception ,  termination handler时 , 在这之前汇编中均有一句 :
           lea     ebp , [ ebx + 10h ]     // ebx=_except_handler3!arg_pEh3Exception
     明白了这点就不难明白如何在_except_handler3中访问pExceInfo及SaveESP了 !
B .  try 块的终止 : 异常发生 , goto , return , leave , 正常终止 . goto 和 return 可能跨过其它 try , 所以必须要展开到目的地所在的 TryLevel , 但是leave关键字不会跨过其它 try , 只是跳出自己这层 try , 如果编译器检测到这层 try 有termination handler , 则用CALL xxx直接调用 . 当然也有例外 , 这也是VC聪明的地方 , 如果函数只有一个 try , 则根本不用展开而直接使用CALL了 , 这种情况有时可见 . 这样就不难理解如下代码 :
     goto  004013fb  终止代码 :
       // ---------------goto跳出try----------------------------------------
           goto  try0 ;
       push     0               或者  ( 仅一个 try 时 )     CALL  00401070  // 直接调termination handler
       lea     eax , [ ebp + var_Eh3Exce ]                 jmp loc_4013FB
      push    eax
      call    __local_unwind2           // 展开到0(即第一个try内,goto目的地肯定在第一个try内)
       add     esp ,  8
       jmp     loc_4013FB

     return        终止代码 :( 无返回值 )
       // ---------------return跳出try--------------------------------------
       push         0FFFFFFFFh
       lea         eax ,[ ebp - 10h ]
       push        eax
      call        __local_unwind2  ( 401786h )  // 展开到-1(即所有try外)
       add         esp , 8
           return  ;
       jmp         $L19800  ( 401139h )

     return  var_i 终止代码 :( 有返回值 )
       // ---------------return(带返回值)跳出try----------------------------
       mov         eax , dword ptr  [ i ]
       mov         dword ptr  [ ebp - 100h ], eax  // 返回值先被保存
       push         0FFFFFFFFh
       lea         ecx ,[ ebp - 10h ]
       push        ecx
      call        __local_unwind2  ( 4017D6h )  // 再展开,即使finally中改变了i,也不会改变返回值!!!
       add         esp , 8
           return  i ;
       mov         eax , dword ptr  [ ebp - 100h ]  // 取保存的返回值来保存
       jmp         $L19800  ( 40117Bh )       // 跳到复原先前SEH链处

     leave和正常退出的代码 :
       // ---------------leave跳出try---------------------------------------
           if  ( x  >  18 )
       004010FD  cmp         dword ptr  [ x ], 12h
      00401101  jle         FunC + 55h  ( 401105h )
             __leave ;
       00401103  jmp         FunC + 66h  ( 401116h )  // 直接调用termination handler
           printf ( "%s Try!\n" , fun );
       00401105  mov         eax , dword ptr  [ fun ]
       00401108  push        eax
       00401109  push        offset string  "%s Try!\n"  ( 410130h )
       0040110E  call        printf  ( 401650h )
       00401113  add         esp , 8
      00401116  mov         dword ptr  [ ebp - 4 ], 0FFFFFFFFh  // 退出try
      // ---------------正常退出try----------------------------------------
       0040111D  call        $L19798  ( 401124h )  // 直接调用termination handler
       00401122  jmp         $L19801  ( 40113Fh )

C .  _except_handler3执行两次原因 , 实际上理解了展开就能理解它 , 举例解释如下 :
     EH3_List如 : FS [ 0 ]-> E1 -> E2 -> E3 -> E4 , 在E4中发生异常 , 依次搜索E1 , E2 , E3 , 最后在E4中找到能处理此异常的filter handler , 这样E4 , E3 , E2 , E1的_except_handler3均执行了一次 , 这是第一次 .
     第二次 : 在执行E4的exception handler前要先调用全局展开 ( _global_unwind2 ) 以运行E1 , E2 , E3的termination handler在全局展开 ( _global_unwind2 ) 中将为EXCEPTION_RECORD . ExceptionFlags增加标志 _EH_UNWINDING ( 2 ), 再依次调用它们的_except_handler3 , 这就是E1 , E2 , E3的_except_handler3的第二次调用 , 但E4只有一次 .
5.  VC7 .1 下的SEH的C代码 :
// 源码基本以汇编为蓝本,没有进行优化,主要是为了方便大家与汇编对照阅读
#define  MAX_PAGES  0x10
int  ValidateEH3RN ( _EH3_EXCEPTION_REGISTRATION *  pEh3Exce )
{
     // 1.验证_EH3_EXCEPTION_REGISTRATION.pScopeTable的合法性:4字节对齐,且不在栈空间内,因为它是编译器生成的全局变量
     _SCOPETABLE_ENTRY *  pScopeTable = pEh3Exce -> pScopeTable ;
     if  ((( DWORD ) pScopeTable  &  0x3 ) ==  0 )
       return  0 ;
     NT_TIB  * pTeb  = ( NT_TIB *) FS [ 0x18 ];
     DWORD nStackLimit  =  pTeb -> StackLimit ;
     if  ( pScopeTable  >=  pTeb -> StackLimit  &&  pScopeTable  <  pTeb -> StackBase )
       return  0 ; // pScopeTable在栈内肯定不合法
    // 2.判断二叉树pScopeTable是否合法,并统计exception handler数量
     if  ( pEh3Exce -> TryLevel  == - 1 )
       return  1 ; // 表示语句不在try中
     DWORD nFilters , count ;
     nFilters = count = 0 ;
     for (; nFilters  <=  pEh3Exce -> TryLevel ;  nFilters ++)
    { // LOOP1:验证已进入的TRY的SCOPETABLE_ENTRY是否都合法
       if ( pScopeTable [ nFilters ]. ParentLevel !=- 1  &&  pScopeTable [ nFilters ]. ParentLevel >= nFilters )
         return  0 ; // EnclosingLevel在二叉树中不合法:不是根结点且ParentLevel>=nFilters
             // ParentLevel>=nFilters不合法原因:二叉树存贮不可能儿子先存
       if  ( pScopeTable [ nFilters ]. FilterFunc  ==  NULL )
         continue ; // termination handler不统计
       count ++;
    }
     if  ( count  !=  0 )
    { // 有异常处理,验证saveESP
       PVOID saveESP  = 0 ; // 运行时真实值 = crtMain!saveESP=crtMain!(ebp-18h);
       if  ( saveESP  <  nStackLimit  ||  saveESP  >=  pEh3Exce )
         return  0 ; // 不在栈内,或不在TRY函数内,saveESP为TRY函数的栈顶
     }

     // 3.找g_rgValidPages中是否已经有pScopeTable的页基址
     static int  g_nValidPages = 0 ; // g_rgValidPages数组有效元素个数
     static int  g_rgValidPages [ MAX_PAGES ]={ 0 };  // MaxPages=0x10
     DWORD nPageBase  = ( DWORD ) pScopeTable  &  0xfffff000 ; // 取pScopeTable所在页基址
     int  nFind = 0 ;
     if ( g_nValidPages  >  0 )
    {
       do
       { // LOOP2
         if ( nPageBase == g_rgValidPages [ nFind ])
           goto  Finded ; // 找到
         nFind ++;
      }
       while ( nFind < g_nValidPages );
    }

     // 4.没有过在g_rgValidPages找到nPageBase则判断pScopeTable是否在合法的EXE映像内
     MEMORY_BASIC_INFORMATION memInfo ;
     if  ( VirtualQuery ( pScopeTable ,& memInfo , sizeof ( memInfo )) ==  0 )
       return  - 1 ;
     if  ( memInfo . Type  !=  0x01000000 ) // MEM_IMAGE
       return  - 1 ;
     DWORD protect  =  0xCC ; // PAGE_READWRITE|PAGE_WRITECOPY|PAGE_EXECUTE_READWRITE|PAGE_EXECUTE_WRITECOPY;
     if  ( memInfo . Protect  &  protect )
    { // 有指定属性
       IMAGE_DOS_HEADER *  pDosHeader  = ( IMAGE_DOS_HEADER *) memInfo . AllocationBase ;
       if  ( pDosHeader -> e_magic  !=  'ZM' )
         return  - 1 ;  // 非法DOS头,pScopeTable不为编译器分配的空间
       IMAGE_NT_HEADERS *  pPeHeader  = ( IMAGE_NT_HEADERS *)(( char *) pDosHeader + pDosHeader -> e_lfanew );
       if  ( pPeHeader -> Signature  !=  'ZM' )
         return  - 1 ;  // 非法PE头,pScopeTable不为编译器分配的空间
       IMAGE_OPTIONAL_HEADER32 *  pOptHeader  = & pPeHeader -> OptionalHeader ;
       if  ( pOptHeader -> Magic  !=  0x10b )
         return  - 1 ;  // 非WIN32 EXE
       DWORD rvaScope  = ( DWORD ) pScopeTable -( DWORD ) pDosHeader ;  // 计算pScopeTable的RAV
       if  ( pPeHeader -> FileHeader . NumberOfSections  <= 0  )
         return  - 1 ;
       IMAGE_SECTION_HEADER *  pSection  = ( IMAGE_SECTION_HEADER *)( pPeHeader + 1 );
       if  ( rvaScope  >=  pSection -> VirtualAddress  &&  rvaScope  <  pSection -> VirtualAddress + pSection -> Misc . VirtualSize )
      { // rvaScope在代码节内
         if  ( pSection -> Characteristics  &  0x80000000 ) // 0x80000000=IMG_SCN_MEM_WRITE
           return  0 ;
      }
    }

     // 5.对新验证的nPageBse插入到数组中
     static int  g_nLock = 0 ;  // 1:上锁,0:解锁
     if ( InterlockedExchange (& g_nLock , 1 )!= 0 ) // 写线程锁
       return  1 ; // 其它线程已经进入,则不能再进入
     int  k = g_nValidPages ;
     if ( k  >  0 )
    {
       do
       { // LOOP4:判断其它线程是否写入这个页基址
         if ( nPageBase == g_rgValidPages [ k - 1 ])
           break ; // 找到,k>0
         k --;
      }
       while ( k > 0 );
    }
     if  ( k == 0 )
    { // 没有找到,nPageBase插入到g_rgValidPages头
       int  pages  =  0x0F ;
       if ( g_nValidPages  <=  pages )
         pages  =  g_nValidPages ;
       k = 0 ;
       if ( pages  >=  0 )
      {
         do
         { // LOOP5
           int  temp = g_rgValidPages [ k ];
           g_rgValidPages [ k ]= nPageBase ;
           nPageBase = temp ;
           k ++;
        }
         while ( k <= pages );
      }
       if  ( g_nValidPages < 0x10h )
         g_nValidPages ++;
    }
     InterlockedExchange (& g_nLock , 0 ); // 解锁
     return  1 ;

     // 6.找到的nPageBase移到头
    //   但前面找到的结果也可能被其它线程改变位置甚至推出数组,但无论如何这个nPageBase合法可不再验证
Finded :
     if  ( nFind  <=  0 ) // 相当于if(nFind == 0)
       return  1 ; // nPageBase已经在头,可直接返回
     if ( InterlockedExchange (& g_nLock , 1 )!= 0 ) // 写线程锁
       return  1 ; // 其它线程已经进入,则不能再进入
     if ( g_rgValidPages [ nFind ] !=  nPageBase )
    { // 再次对找到的pos进行比较,因为其它线程可能又修改了这个元素的值
       nFind  =  g_nValidPages - 1 ;
       if  ( nFind >= 0 )
      {
         while ( nFind >= 0 )
        { // LOOP3
           if  ( g_rgValidPages [ nFind ]== nPageBase )
             break ;
           nFind --;
        }
         if  ( nFind >= 0 )
           goto  End1 ;
      }
       // 没找到,新增加
       if ( g_nValidPages  <  0x10 )
         g_nValidPages ++;
       nFind  =  g_nValidPages - 1 ;
       goto  end2 ;
    }
     else
      goto  end2 ;
end1 :
     if  ( nFind  !=  0 )
    {
end2 :
       if  ( nFind  >=  0 )
      {
         for ( int  j  =  0 ; j <= nFind ; j ++)
        { // LOOP6:g_rgValidPages中找到的nPageBase移到头或新nPageBase插入头,其余每个元素向后推
           int  temp = g_rgValidPages [ j ];
           g_rgValidPages [ j ]= nPageBase ;
           nPageBase = temp ;
        }
      }
    }
     InterlockedExchange (& g_nLock , 0 ); // 解线程锁
     return  1 ;
}
void  _global_unwind2 ( _EH3_EXCEPTION_REGISTRATION * pEh3Exce )
{ // 对调用RtlUnwind的封装
     RtlUnwind ( pEh3Exce , offset exit , 0 , 0 );
exit :
     return ;
}
int  _UnwindHandler ( EXCEPTION_RECORD * pExceRec , _EH3_EXCEPTION_REGISTRATION * pEh3Exce ,
              void *, _EH3_EXCEPTION_REGISTRATION **  ppEh3Exce )
{
     if ( pExceRec -> ExceptionFlags  &&  6  ==  0 )
       return  1 ; // ExceptionContinueSearch
     * ppEh3Exce  =  pEh3Exce ;
     return  3 ; // ExceptionCollidedUnwind
}
// NLG == "non-local-goto"
_NLGDestination g_NLGDestination ; // 此全局变量我至始至终未见到任何其它EXE处或者DLL使用,通知是何意义暂无知!!!!!!!
void  _NLG_Notify1 ( int  x ) // --------------------------CallSettingFrame调用
{ // g_NLGDestination全局分配变量:0x19930520
     g_NLGDestination . dwInCode  =  EBP + 8 ; // Try函数中第一个参数???
     g_NLGDestination . HandlerFunc  =  EAX ; // 调用前通过EAX传入
     g_NLGDestination . TryEBP  =  EBP ; // Try中的EBP???
}
void  _NLG_Notify ( int  x ) // --------------------------_except_handler3,_local_unwind2调用
{ // 传入参数未用
    // g_NLGDestination.unKnown全局分配变量:0x19930520
     g_NLGDestination . dwInCode  =  EBP + 8 ; // Try函数中第一个参数,如:FunB(i,j)!i,crtMain!ebp+8
     g_NLGDestination . HandlerFunc  =  EAX ; // 调用前通过EAX传入:pScopeTable.HandlerFunc
     g_NLGDestination . TryEBP  =  EBP ; // Try中的EBP
}
// nToLevel:展开到此为止(不含nToLevel),举例:在goto中nToLevel由目标所在try决定,因为系统规定跳入TRY是不合法的,因此
// goto不能跳入其它TRY(嵌套的上层TRY是合法的,很明显平级层是不合法的)层,这时编译是通不过的.
void  _local_unwind2 ( _EH3_EXCEPTION_REGISTRATION * pEh3Exce , int  nToLevel )
{ // 用ESP,未用EBP,之前的EBP传入内调的_NLG_Notify,__try中的goto将用此函数展开得到其handler
     _EH3_EXCEPTION_REGISTRATION eh3Unwind ;
     eh3Unwind . TryLevel  = ( int ) pEh3Exce ;
     eh3Unwind . pScopeTable  = ( _SCOPETABLE_ENTRY *)- 2 ;
     eh3Unwind . ExceptionHandler  = ( EXCE_HANDLER ) _UnwindHandler ;
     eh3Unwind . pPrev  = ( _EH3_EXCEPTION_REGISTRATION *) FS [ 0 ];
     FS [ 0 ]= ESP ;
     int  nLevel ; //
     while ( 1 )
    {
       _SCOPETABLE_ENTRY  * pScope = pEh3Exce -> pScopeTable ;
       ESI  =  pEh3Exce -> TryLevel ;
       if  ( ESI  == - 1 )
         break ;
       if  ( ESI  ==  nToLevel )
         break ;
       nLevel = pScope [ ESI ]. ParentLevel ;
       pEh3Exce -> TryLevel = nLevel ;
       if  ( pScope [ ESI ]. FilterFunc  !=  NULL )  // 有FilterFunc则是exception handler
         continue ;
       EAX = pScope [ ESI ]. HandlerFunc ;     // 无FilterFunc则是termination handler
       _NLG_Notify ( 0x101 );       // 无返回值
       CALL ( EAX );           // 这就是展开所求得的Handler,可以是termination handler,exception Handler
     }
     FS [ 0 ]=( DWORD ) eh3Unwind . pPrev ;
}

// _except_handler3执行两次原因:
// 举例:EH3_List如 FS[0]->E1->E2->E3->E4,在E4中发生异常,依次搜索E1,E2,E3,最后在E4中找到能处理此异常的 filter handler,这样E4,E3,E2,E1的_except_handler3均执行了一次,这是第一次.
//     第二次:在执行E4的exception handler前要先调用全局展开(_global_unwind2)以运行E1,E2,E3的termination handler
// 在全局展开(_global_unwind2)中将为EXCEPTION_RECORD.ExceptionFlags增加标志_EH_UNWINDING(2),再依次调用它们的
// _except_handler3,这就是E1,E2,E3的_except_handler3的第二次调用,但E4只有一次.
int  _except_handler3 ( EXCEPTION_RECORD * pExceRec , _EH3_EXCEPTION_REGISTRATION * pEh3Exce ,
              CONTEXT *  pContextRecord /*,void * DispatcherContext*/ )
{
     if  (( pExceRec -> ExceptionFlags  &  6 ) ==  0 )
    { // 无_EH_UNWINDING and _EH_EXIT_UNWIND
       EXCEPTION_POINTERS ePointer ;
       ePointer . ExceptionRecord  =  pExceRec ;
       ePointer . ContextRecord  =  pContextRecord ;
       // pEh3Exce=&EhRecord
      // lea     EAX, [EBP+var_ExcePointers]
      // mov     [ebx-4], EAX ;EBX=pEh3Exce
       *(( DWORD *) pEh3Exce - 1 )=( DWORD )& ePointer ;  // 给_XcptFilter的参数pExceptPointers赋值
       if  ( ValidateEH3RN ( pEh3Exce ))
      {
         _SCOPETABLE_ENTRY *  pScopeTable = pEh3Exce -> pScopeTable ;
         int  i  =  pEh3Exce -> TryLevel ;
         while ( 1 )
        {
           if  ( i ==- 1 )
             return  1 ; // handler拒绝处理这个异常
           EAX = pScopeTable [ i ]. FilterFunc ;
           if ( EAX != NULL )
          { // 属于exception handler
             EBP =( DWORD ) pEh3Exce + 0x10 ; // 恢复原TRY基址pEh3Exce=&crtMain!EhRecord,sizeof(EhRecord)=0x10
             CALL ( EAX ); // 没有参数,ePointer已传过去
             if ( EAX  !=  0 )
            {
               if  (( int ) EAX  <  0 )
                 return  0 ;         // 如果在filter中返回值<0,此返回后会引起代码为
                            // EXCEPTION_NONCONTINUABLE_EXCEPTION(0xC0000025)的异常,反复如此栈溢出
               _global_unwind2 ( pEh3Exce );  // 调用handler前,展开前级函数中的termination handler
               EBP =( DWORD ) pEh3Exce + 0x10 ;  // 恢复原TRY基址pEh3Exce=&crtMain!EhRecord,sizeof(EhRecord)=0x10
               _local_unwind2 ( pEh3Exce , i );  // 此函数内不直接用EBP,但暗传EBP给可能执行的_NLG_Notify
                            // 并展开本函数中的termination handler
               EAX = pScopeTable [ i ]. HandlerFunc ;
               _NLG_Notify ( 1 ); // 此函数内要用EAX
               pEh3Exce -> TryLevel = pScopeTable [ i ]. ParentLevel ; // 修改节点所在层次使EIP在TRY中位置表示正确
               EAX  =  pScopeTable [ i ]. HandlerFunc ;
               CALL ( EAX ); // 进入异常处理,不再返回此函数(内部可接受异常并修改环境,继续执行)
             }
          }
           i = pScopeTable [ i ]. ParentLevel ;
        }
         return  0 ;
      }
       else
       {
         pExceRec -> ExceptionFlags  |=  8 ; // 置pEh3Exce无效标志,它不属于此handler
         return  1 ; // handler拒绝处理这个异常,系统转调上一级
       }
    }
     else
     { // 有_EH_UNWINDING or _EH_EXIT_UNWIND
       EBP =( DWORD ) pEh3Exce + 0x10 ; // 恢复原TRY基址pEh3Exce=&crtMain!EhRecord,sizeof(EhRecord)=0x10
       _local_unwind2 ( pEh3Exce ,- 1 );
    }
     return  1 ;
}
6.  结语
    SEH代码还有很多 , 它们有许多包含在NTDLL . DLL中 , 比如RtlUnwind我曾经非常想翻译它 , 但至今我也还未完成 , 因此我也就没有粘出来 . 实际上从我以上粘出的初步代码 , 我们应该能看出SEH并不复杂 , 翻译DLL中的相关代码也并不困难 , 我估计我们唯一无法看见的代码就是中断处理的起始部分 ,  把它想像成一个黑匣子就成了 . 附件有我的IDA注释说明 , 注释由于是一个动态过程 , 特别是起初的注释可能有错 , 我没有精力再去一一阅读更正 , 希望大家理解 , 一并奉献上 , 希望对大伙有用 .
       在这里 , 我只是作了SEH源码的一部分翻译工作 , 在翻译过程中我也在网上查看了很多关于SEH方面的文章 , 在此我不再一一列出 , 一并致谢这些无私奉献的网友和同行们 ! 看了此文相信再看其它SEH专著就应该容易多了 !
       今天就是我们的嫦娥飞天的日子 , 谨以此文预祝她能回宫成功 ! 顺便也希望此文是我下一个工作的起始点 !