标 题 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  ( 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 !=- &&  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  <= )
        
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 ( 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  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  &&  ==  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  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专著就应该容易多了 !
      
今天就是我们的嫦娥飞天的日子 , 谨以此文预祝她能回宫成功 ! 顺便也希望此文是我下一个工作的起始点 !