条目1、促使微软将SEH加入WINDOWS系统的因素之一是它可以简化操作系统本身的开发工作。(P621)
条目2、终止处理的语法(P622)
__try { //Guarded body } __finally { //Termination handler }
操作系统和编译器协同工作保证不管被保护的代码如何退出(return、goto、longjmp)的,终止处理程序都会被调用。即__finally代码块都能执行。(P622)
__try { //Guarded body //退出try块,导致__finally代码块被执行 return 0; } __finally { //Termination handler printf("OK"); }
条目3、局部展开就是为了在try代码块非正常离开前,能够执行finally块而所做的工作。(P624)
我们应该避免在try语句中使用能让try语句块提前结束(跳过)的语句,因为这样会损害应用程序的性能。代码控制流正常地离开try代码块进入finally代码块时,性能开销最小。
验证:
(1) 编写正常离开try块的示例代码
#include <windows.h> int main(void) { int a = 1, b = 2, c; __try { c = a + b; } __finally { //Termination handler printf("OK"); } return 5; }
(2) 编译后,反汇编其内容
00401000 ; int __cdecl main(int argc, const char **argv, const char *envp) 00401000 _main proc near ; CODE XREF: ___tmainCRTStartup+15Ap 00401000 00401000 var_24 = dword ptr -24h 00401000 var_20 = dword ptr -20h 00401000 var_1C = dword ptr -1Ch 00401000 var_10 = dword ptr -10h 00401000 var_8 = dword ptr -8 00401000 var_4 = dword ptr -4 00401000 argc = dword ptr 8 00401000 argv = dword ptr 0Ch 00401000 envp = dword ptr 10h 00401000 00401000 ; FUNCTION CHUNK AT .text:0040106A SIZE 00000014 BYTES 00401000 00401000 push ebp 00401001 mov ebp, esp ; EBP-00 : _ebp 00401003 push 0FFFFFFFEh ; EBP-04 : trylevel 00401005 push offset unk_40B380 ; EBP-08 : scopetable数组指针 0040100A push offset __except_handler4 ; EBP-0C : handler函数地址 0040100F mov eax, large fs:0 00401015 push eax ; EBP-10 : 指向前一个EXCEPTION_REGISTRATION结构 00401016 add esp, -14h ; EBP-14 : 指向一个EXCEPTION_POINTERS结构,也就是GetExceptionPointers()的返回值 00401016 ; EBP-18 : ESP 00401016 ; EBP-1C : 变量a 00401016 ; EBP-20 : 变量b 00401016 ; EBP-24 : 变量c 00401019 ush ebx 0040101A push esi 0040101B push edi 0040101C mov eax, dword_40C004 00401021 xor [ebp+var_8], eax 00401024 xor eax, ebp 00401026 push eax 00401027 lea eax, [ebp+var_10] 0040102A mov large fs:0, eax ; 安装EXCEPTION_REGISTRATION 00401030 mov [ebp+var_1C], 1 ; a=1 00401037 mov [ebp+var_20], 2 ; b=2 0040103E mov [ebp+var_4], 0 00401045 mov eax, [ebp+var_1C] 00401048 add eax, [ebp+var_20] ; eax = a + b 0040104B mov [ebp+var_24], eax ; c = eax = 3 0040104E mov [ebp+var_4], 0FFFFFFFEh 00401055 call sub_40105C ; printf("OK") 0040105A jmp short loc_40106A ; 恢复EXCEPTION_REGISTRATION,并返回0 0040105A _main endp 0040105A 0040105C 0040105C ; =============== S U B R O U T I N E ======================================= 0040105C 0040105C 0040105C sub_40105C proc near ; CODE XREF: _main+55p 0040105C push offset unk_40C000 ; "OK" 00401061 call sub_40107E ; printf函数 00401066 add esp, 4 00401069 retn 00401069 sub_40105C endp 00401069 0040106A ; --------------------------------------------------------------------------- 0040106A ; START OF FUNCTION CHUNK FOR _main 0040106A 0040106A loc_40106A: ; CODE XREF: _main+5Aj 0040106A xor eax, eax 0040106C mov ecx, [ebp+var_10] 0040106F mov large fs:0, ecx ; 恢复EXCEPTION_REGISTRATION 00401076 pop ecx 00401077 pop edi 00401078 pop esi 00401079 pop ebx 0040107A mov esp, ebp 0040107C pop ebp 0040107D retn
我们很清楚的看到,当控制流正常离开try进入finally时,整体编译出来的代码比较精简、流畅,性能开销小(00401055处直接调用finally中的代码)。
(3) 编写非正常离开try块的示例代码
#include <windows.h> int main(void) { int a = 1, b = 2, c; __try { c = a + b; //非正常离开try块,导致局部展开,finally代码块被执行 return c; } __finally { //Termination handler printf("OK"); } return 5; }
(4) 编译后,反汇编其内容
00401000 ; int __cdecl main(int argc, const char **argv, const char *envp) 00401000 _main proc near ; CODE XREF: ___tmainCRTStartup+15Ap 00401000 00401000 var_28 = dword ptr -28h 00401000 var_24 = dword ptr -24h 00401000 var_20 = dword ptr -20h 00401000 var_1C = dword ptr -1Ch 00401000 var_10 = dword ptr -10h 00401000 var_8 = dword ptr -8 00401000 var_4 = dword ptr -4 00401000 argc = dword ptr 8 00401000 argv = dword ptr 0Ch 00401000 envp = dword ptr 10h 00401000 00401000 push ebp 00401001 mov ebp, esp ; EBP-00 : _ebp 00401003 push 0FFFFFFFEh ; EBP-04 : trylevel 00401005 push offset unk_40B380 ; EBP-08 : scopetable数组指针 0040100A push offset __except_handler4 ; EBP-0C : handler函数地址 0040100F mov eax, large fs:0 00401015 push eax ; EBP-10 : 指向前一个EXCEPTION_REGISTRATION结构 00401016 add esp, -18h ; EBP-14 : 指向一个EXCEPTION_POINTERS结构,也就是GetExceptionPointers()的返回值 00401016 ; EBP-18 : ESP 00401016 ; EBP-1C : 变量a 00401016 ; EBP-20 : 变量b 00401016 ; EBP-24 : 变量c 00401016 ; EBP-28 : 临时变量,作为main函数的返回值 00401019 push ebx 0040101A push esi 0040101B push edi 0040101C mov eax, dword_40C004 00401021 xor [ebp+var_8], eax 00401024 xor eax, ebp 00401026 push eax 00401027 lea eax, [ebp+var_10] 0040102A mov large fs:0, eax ; 安装EXCEPTION_REGISTRATION 00401030 mov [ebp+var_1C], 1 ; a=1 00401037 mov [ebp+var_20], 2 ; b=2 0040103E mov [ebp+var_4], 0 00401045 mov eax, [ebp+var_1C] ; eax = a = 1 00401048 add eax, [ebp+var_20] ; eax = a + b = 1 + 2 = 3 0040104B mov [ebp+var_24], eax ; c = eax =3 0040104E mov ecx, [ebp+var_24] 00401051 mov [ebp+var_28], ecx ; 将c的值作为main的返回值 00401054 push 0FFFFFFFEh 00401056 lea edx, [ebp+var_10] 00401059 push edx 0040105A push offset dword_40C004 0040105F call __local_unwind4 ; 局部展开,里面会调用loc_40107A执行finally代码块 00401064 add esp, 0Ch 00401067 mov eax, [ebp+var_28] ; 设置main函数的返回值! 0040106A jmp short loc_401088 ; 清理栈,退出! 0040106C ; --------------------------------------------------------------------------- 0040106C mov [ebp+var_4], 0FFFFFFFEh 00401073 call loc_40107A 00401078 jmp short loc_401088 0040107A ; --------------------------------------------------------------------------- 0040107A 0040107A loc_40107A: ; CODE XREF: _main+73p 0040107A push offset unk_40C000 ; "OK" 0040107F call sub_40109A ; printf 00401084 add esp, 4 00401087 retn 00401088 ; --------------------------------------------------------------------------- 00401088 00401088 loc_401088: ; CODE XREF: _main+6Aj 00401088 ; _main+78j 00401088 mov ecx, [ebp+var_10] 0040108B mov large fs:0, ecx 00401092 pop ecx 00401093 pop edi 00401094 pop esi 00401095 pop ebx 00401096 mov esp, ebp 00401098 pop ebp 00401099 retn 00401099 _main endp
当控制流非正常离开try进入finally时,系统进行局部展开(0040105F call __local_unwind4),经过层层处理后在内部执行finally代码块。很明显,此时性能开销比较大。
条目4、从WINDOWS VISTA系统开始,须显式地保护try/finally框架,以确保在异常抛出时,finally代码块会执行。(P625)
条目5、终止处理程序(finally)同样可以捕获setjmp/longjmp、break、continue、goto等语句。(P626)
示例代码:
DWORD FuncaDoodleDoo() { DWORD dwTemp = 0; while(dwTemp < 10) { __try { if(dwTemp == 2) continue; if(dwTemp == 3) break; } __finally { dwTemp ++; } dwTemp ++; } dwTemp += 10; return dwTemp; }
结果是:14
条目6、在进程或线程被提前终止的情况下,系统没办法保证finally代码块会被执行。换句话说,在try块中调用ExitProcess或ExitThread将不会导致finally代码块的执行。一个线程或进程被其他程序调用TerminateThread或TerminateProcess终止时,也不会导致finally代码块的执行。(P627)
条目7、关键字__leave会导致代码执行控制流跳转到try块的结尾,使代码以正常的方式从try块进入finally块中。(P631)
条目8、总结引起finally块执行的三个原因:
(1) 从try块到finally块的正常代码控制流
(2) 局部展开(从try块中提前退出进入finally块,由goto/longjmp/continue/break/return等语句引起)
(3) 全局展开
注意:在WINDOWS VISTA系统上,全局展开在默认的情况下是不会被触发的,所以finally不会被执行。调用AbnormalTermination宏可以确定与当前finally块相关的try快是否提前退出。AbnormalTermination宏只能在finally块中被调用。