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;
......
C++異常處理機制__1.SEH處理的函數棧佈局
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.