C++異常處理機制__1.SEH處理的函數棧佈局

C++可以有兩種異常處理方法

1. SEH exceptions (Structured Exception Handling), 結構化異常處理
它有三個關鍵字__try, __except, __finally,並通過RaiseException 拋出異常
其中一個__try後只能跟一個__except或一個__finally。
當__try後跟__except時,爲異常處理(exception handling)
當__try後跟__finally時,爲結束處理(termination handling)
它可以捕獲內存非法訪問,除0等 CPU 引發的硬件異常,以可以捕獲通過RaiseException 拋出的軟件異常
但它不支持棧回退,但函數中有類變量時無法編譯通過

2. C++ exceptions, 有try 和 catch 兩個關鍵字,通過throw 拋出異常
C++ exceptions更爲強大,因爲它可以拋出並捕獲任意類型的異常,並進行棧回退工作,使得RAII技術能得以實現。

-------------------------------------------------------------------------------------------------------
先看看普通函數的棧狀態

測試函數


編譯器生成的代碼 (VS2005, Release)
void simple_func (int x)
{
00401FD0  push        ebp                   <== 保存上一函數的EBP
00401FD1  mov         ebp,esp               <== 設置EBP 到當前函數棧頂
00401FD3  sub         esp,0Ch               <== 分配函數局部變量空間
    int *p1 = NULL;
00401FD6  mov         dword ptr [p1],0
......
}
0040200B  mov         esp,ebp              
0040200D  pop         ebp 
0040200E  ret             

當前寄存器:
EAX = 003B39A8 EBX = 00000000 ECX = 781C37E4 EDX = 00000000 ESI = 00000001
EDI = 00408590 EIP = 00401FD6 ESP = 0012FF5C EBP = 0012FF68 EFL = 00000216

內存值
0x0012FF50                                                     ...., 4f 1d 13 78
0x0012FF60  c0 ff 12 00, f0 15 40 00, 74 ff 12 00, ca 21 40 00
0x0012FF70  0a 00 00 00, ......

可見一般函數的棧空間很簡單:(地址從高到低)

1 函數參數     (地址: 0x0012FF70, 值:0a 00 00 00)
2 函數返回地址 (地址: 0x0012FF6C, 值:ca 21 40 00)
3 上一函數的EBP (地址: 0x0012FF68, 值:74 ff 12 00)
4 函數局部變量  (地址: 0x0012FF5C - 0x0012FF64)


-------------------------------------------------------------------------------------------------------
當SEH異常塊(exception block)出現時,編譯程序要生成特殊的代碼。
編譯程序必須產生一些表來支持處理SEH的數據結構。
編譯程序還必須提供回調函數,操作系統可以調用這些函數,保證異常塊被處理。
編譯程序還要負責準備棧結構和其他內部信息,供操作系統使用和參考。

看看包含SEH支持的函數

測試函數


其中SEH_test1() 採取結束處理方式,並在 __try 塊中調用SEH_test2
SEH_test2() 採取異常處理,並在 __try 塊結束後會引發一個異常

編譯器生成的代碼 (VS2005, Release)
void SEH_test1()
{
004020E0  push        ebp 
004020E1  mov         ebp,esp
004020E3  push        0FFFFFFFEh                                               <== Try Level
004020E5  push        offset ___rtc_tzz+11Ch (403870h)          <== Scope table
004020EA  push        offset _except_handler4 (401BC5h)         <== SEH Handler
004020EF  mov         eax,dword ptr fs:[00000000h]
004020F5  push        eax                                                           <== Next SEH Frame

004020F6  add         esp,0FFFFFFECh                                        <== local variable
004020F9  push        ebx                                                           <== saved register
004020FA  push        esi 
004020FB  push        edi 
004020FC  mov         eax,dword ptr [___security_cookie (405004h)]
00402101  xor         dword ptr [ebp-8],eax
00402104  xor         eax,ebp
00402106  push        eax                                                        <== EH Cookie
00402107  lea         eax,[ebp-10h]
0040210A  mov         dword ptr fs:[00000000h],eax              <== store current SEH Frame

    int *p1 = NULL;
00402110  mov         dword ptr [ebp-1Ch],0

當前寄存器:
EAX = 0012FF5C EBX = 00000000 ECX = 78134C58 EDX = 003B0608 ESI = 00000001
EDI = 00408590 EIP = 00402110 ESP = 0012FF38 EBP = 0012FF6C EFL = 00000202

內存值
0x0012FF30                                ... f1 8f ad 16, 90 85 40 00 
0x0012FF40  01 00 00 00, 00 00 00 00, a9 af 68 6e ,fe ff ff ff 
0x0012FF50  58 4c 13 78, 07 20 40 00, 68 56 3b 00, b0 ff 12 00 
0x0012FF60  c5 1b 40 00, ed 48 ff 16, fe ff ff ff, 74 ff 12 00
0x0012FF70  d2 21 40 00 ......

可見在棧中間加入了很多東西

1 函數參數     (這裏無)
2 函數返回地址 (地址: 0x0012FF70, 值:d2 21 40 00)
3 上一函數的EBP (地址: 0x0012FF6C, 值:74 ff 12 00)
4 這裏是一個異常支持的節點頭 (SEH Frame),包括           <== SEH新加的
    Try Level    (地址: 0x0012FF68, 值:fe ff ff ff = -2)
    Scope table  (地址: 0x0012FF64, 值:ed 48 ff 16)
    SEH Handler  (地址: 0x0012FF60, 值:c5 1b 40 00)
    上一函數的SEH Frame (地址: 0x0012FF5C, 值:b0 ff 12 00)

5 函數局部變量  (地址: 0x0012FF48 - 0x0012FF58)
6 保存的寄存器值 (地址: 0x0012FF3C - 0x0012FF44)
7 EH Cookie      (地址: 0x0012FF38, 值:f1 8f ad 16)      <== SEH新加的

在這裏可以看到訪問了一個特殊的地址 fs:[00000000h]
個人認爲fs 段是一個與線程相關的全局棧,專門存放線程全局變量或靜態變量
這裏fs:[00000000h] 保存了當前函數的SEH Frame 地址。
這樣嵌套的函數相互之間的SEH Frame 鏈接到了一起,組成一個鏈表


再看看SEH_test2 使用異常處理的代碼
void SEH_test2()
{
00402010  push        ebp 
00402011  mov         ebp,esp
00402013  push        0FFFFFFFEh
00402015  push        offset ___rtc_tzz+0FCh (403850h)    <== 這個地址和SEH_test1 不同
0040201A  push        offset _except_handler4 (401BC5h)   <== SEH Handler 和SEH_test1 相同
0040201F  mov         eax,dword ptr fs:[00000000h]
00402025  push        eax 
00402026  add         esp,0FFFFFFF4h
00402029  push        ebx 
0040202A  push        esi 
0040202B  push        edi 
0040202C  mov         eax,dword ptr [___security_cookie (405004h)]
00402031  xor         dword ptr [ebp-8],eax
00402034  xor         eax,ebp
00402036  push        eax 
00402037  lea         eax,[ebp-10h]
0040203A  mov         dword ptr fs:[00000000h],eax
00402040  mov         dword ptr [ebp-18h],esp             <== 新加的
    int *p1 = NULL;
00402043  mov         dword ptr [ebp-1Ch],0

可見SEH_test2 和SEH_test1 基本相同,但多保存了 ESP的地址
另外,他們的SEH Handler (也就是異常處理)函數相同,但Scope table地址不同,在Scope table中保存
了__try塊 相匹配的 __except 或 __finally的地址值

可以看看SEH_test2 的Scope table(403850h) 的內存值

0x00403850  fe ff ff ff, 00 00 00 00, d4 ff ff ff, 00 00 00 00  ................
0x00403860  fe ff ff ff, 63 20 40 00, 69 20 40 00, 00 00 00 00  ....c @.i @.....

Scope table 的結構是
    struct _EH4_SCOPETABLE {
        DWORD GSCookieOffset;
        DWORD GSCookieXOROffset;
        DWORD EHCookieOffset;
        DWORD EHCookieXOROffset;
        _EH4_SCOPETABLE_RECORD ScopeRecord[1];
    };

    struct _EH4_SCOPETABLE_RECORD {
        DWORD EnclosingLevel;
        long (*FilterFunc)();
            union {
            void (*HandlerAddress)();
            void (*FinallyFunc)();

        };
    };

在這裏我們可以看到 FilterFunc 的值是 63 20 40 00
HandlerAddress(或 FinallyFunc) 的值是 69 20 40 00

再看看SEH_test2的這代碼,可以發現 63 20 40 00 正好是__except(EXCEPTION_EXECUTE_HANDLER)的指令地址,
而69 20 40 00 正好是__except 開始的{}內的地址

void SEH_test2()
{......
    int *p1 = NULL;
00402043  mov         dword ptr [ebp-1Ch],0
    __try
0040204A  mov         dword ptr [ebp-4],0
    {
        *p1 = 10;
00402051  mov         eax,dword ptr [ebp-1Ch]
00402054  mov         dword ptr [eax],0Ah
    }
0040205A  mov         dword ptr [ebp-4],0FFFFFFFEh
00402061  jmp         $LN6+2Dh (402096h)

    __except(EXCEPTION_EXECUTE_HANDLER)
00402063   mov         eax,1
$LN7:
00402068  ret             
$LN6:
00402069   mov         esp,dword ptr [ebp-18h]
    {
        cout << "SHE_test2::__except" << endl;
        ......
    }
0040208F  mov         dword ptr [ebp-4],0FFFFFFFEh

    cout << "SHE_test2::*p1 = 10" << endl;
    ......

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章