Windows异常学习笔记(四)—— 编译器扩展SEH
要点回顾
当我们通过编程实现windows的异常处理方式使用SEH结构化异常时,我们必须做如下几件事情
- 将异常处理函数挂入SEH链表
- 需要定义一个异常处理函数(包含异常过滤,异常处理)
注意:这种方式相对而言不太方便,因此现在的编译器都对SEH结构化异常提供了语法支持,并在其之上做了一定的拓展
编译器支持的SEH
语法:
_try //挂入链表
{
//代码
}
_except(过滤表达式) //异常过滤
{
//异常处理程序 //异常处理
}
拆解:
_try
//相当于
__asm
{
mov eax, FS:[0]
mov temp, eax
lea ecx, myException
mov FS:[0], ecx
}
_except(过滤表达式)
//相当于
if( ExceptionRecord->ExceptionCode == 0xC0000094 ) //除0异常
过滤表达式
三个值:
1. EXCEPTION_EXECUTE_HANDLER(1) 执行except代码
2. EXCEPTION_CONTINUE_SEARCH(0) 不处理异常,寻找下一个异常处理函数
3. EXCEPTION_CONTINUE_EXECUTION(-1) 返回出错位置重新执行
三种写法:
1)常量
try
{
//代码
}
_except(EXCEPTION_EXECUTE_HANDLER)
{
//异常处理程序
}
2)表达式
try
{
//代码
}
_except(GetExceptionCode()==0xC0000094?EXCEPTION_EXECUTE_HANDLER:EXCEPTION_CONTINUE_SEARCH)
{
//异常处理程序
}
3)调用函数
int ExceptFilter(LPEXCEPTION_POINTERS pExceptionInfo)
{
printf("%x", pExceptionInfo->ExceptionRecord->ExceptionCode);
printf("%p", pExceptionInfo->ContextRecord->Eip);
return EXCEPTION_CONTINUE_EXECUTION;
}
try
{
//代码
}
_except(ExceptFilter(GetExceptionInformation()))
{
//异常处理程序
}
实验一:理解_try_except
1)编译并运行以下代码
#include <stdio.h>
#include <windows.h>
void TestException()
{
_try
{
}
_except(1)
{
}
}
int main()
{
TestException();
getchar();
return 0;
}
2)查看TestException函数的反汇编
4: void TestException()
5: {
00401020 push ebp
00401021 mov ebp,esp
00401023 push 0FFh
00401025 push offset string "_filbuf.c"+0FFFFFFD4h (00422020)
0040102A push offset __except_handler3 (0040127c)
0040102F mov eax,fs:[00000000]
00401035 push eax
00401036 mov dword ptr fs:[0],esp
0040103D add esp,0FFFFFFB8h
00401040 push ebx
00401041 push esi
00401042 push edi
00401043 mov dword ptr [ebp-18h],esp
00401046 lea edi,[ebp-58h]
00401049 mov ecx,10h
0040104E mov eax,0CCCCCCCCh
00401053 rep stos dword ptr [edi]
6: _try
00401055 mov dword ptr [ebp-4],0
7: {
8:
9: }
0040105C mov dword ptr [ebp-4],0FFFFFFFFh
00401063 jmp $L53962+0Ah (00401075)
10: _except(1)
00401065 mov eax,1
$L53963:
0040106A ret
$L53962:
0040106B mov esp,dword ptr [ebp-18h]
11: {
12:
13: }
0040106E mov dword ptr [ebp-4],0FFFFFFFFh
14: }
00401075 mov ecx,dword ptr [ebp-10h]
00401078 mov dword ptr fs:[0],ecx
0040107F pop edi
00401080 pop esi
00401081 pop ebx
00401082 mov esp,ebp
00401084 pop ebp
00401085 ret
3)总结
在编译器支持的情况下,当我们写入_try_except程序块时,编译器就会自动在当前堆栈链表中挂入一个结构体,并且在挂入时指定的处理函数时固定的,当我们手动挂入时,处理函数是我们手动编写的
实验二:_try_except 嵌套
描述:当我们手动挂入链表时,若我们需要两个处理函数时,就需要挂入两个处理函数,需要多少个就要挂多少个,而当我们使用编译器提供的这种格式,无论我们嵌套了多少个异常处理,编译器只会生成一个异常处理函数
1)编译并运行以下代码
#include <stdio.h>
#include <windows.h>
void TestException()
{
_try
{
_try
{
}
_except(1)
{
}
}
_except(1)
{
}
_try
{
}
_except(1)
{
}
}
int main()
{
TestException();
getchar();
return 0;
}
2)查看TestException函数的反汇编
4: void TestException()
5: {
00401020 push ebp
00401021 mov ebp,esp
00401023 push 0FFh
00401025 push offset string "_filbuf.c"+0FFFFFFD4h (00422020)
0040102A push offset __except_handler3 (0040127c)
0040102F mov eax,fs:[00000000]
00401035 push eax
00401036 mov dword ptr fs:[0],esp //只有此处修改了fs,只挂入了一个异常处理函数
0040103D add esp,0FFFFFFB8h
00401040 push ebx
00401041 push esi
00401042 push edi
00401043 mov dword ptr [ebp-18h],esp
00401046 lea edi,[ebp-58h]
00401049 mov ecx,10h
0040104E mov eax,0CCCCCCCCh
00401053 rep stos dword ptr [edi]
6: _try
00401055 mov dword ptr [ebp-4],0
7: {
8: _try
0040105C mov dword ptr [ebp-4],1
9: {
10:
11: }
00401063 mov dword ptr [ebp-4],0
0040106A jmp $L53968+0Ah (0040107c)
12: _except(1)
0040106C mov eax,1
$L53969:
00401071 ret
$L53968:
00401072 mov esp,dword ptr [ebp-18h]
13: {
14:
15: }
00401075 mov dword ptr [ebp-4],0
16: }
0040107C mov dword ptr [ebp-4],0FFFFFFFFh
00401083 jmp $L53964+0Ah (00401095)
17: _except(1)
00401085 mov eax,1
$L53965:
0040108A ret
$L53964:
0040108B mov esp,dword ptr [ebp-18h]
18: {
19:
20: }
0040108E mov dword ptr [ebp-4],0FFFFFFFFh
21:
22: _try
00401095 mov dword ptr [ebp-4],2
23: {
24:
25: }
0040109C mov dword ptr [ebp-4],0FFFFFFFFh
004010A3 jmp $L53972+0Ah (004010b5)
26: _except(1)
004010A5 mov eax,1
$L53973:
004010AA ret
$L53972:
004010AB mov esp,dword ptr [ebp-18h]
27: {
28:
29: }
004010AE mov dword ptr [ebp-4],0FFFFFFFFh
30: }
004010B5 mov ecx,dword ptr [ebp-10h]
004010B8 mov dword ptr fs:[0],ecx
004010BF pop edi
004010C0 pop esi
004010C1 pop ebx
004010C2 mov esp,ebp
004010C4 pop ebp
004010C5 ret
3)总结:当我们使用编译器提供的这种格式,在一个函数中,无论我们嵌套了多少个异常处理,编译器只会往FS:[0]中挂入一次,并且指向的异常处理函数也是确定的(递归除外,递归相当于一次次调用了新的函数)
思考:编译器只挂入一次异常处理函数,它如何实现对所有异常进行处理?
答案: 拓展_EXCEPTION_REGISTRATION_RECORD结构体
拓展SEH结构体
原先的SEH结构体
typedef struct _EXCEPTION_REGISTRATION_RECORD{
struct _EXCEPTION_REGISTRATION_RECORD *Next;
PEXCEPTION_ROUTINE Handler;
}EXCEPTION_REGISTRATION_RECORD;
拓展后如下(原先的两个成员必须存在)
struct _EXCEPTION_REGISTRATION{
struct _EXCEPTION_REGISTRATION *prev;
void (*handler)(PEXCEPTION_RECORD, PEXCEPTION_REGISTRATION, PCONTEXT, PEXCEPTION_RECORD);
struct scopetable_entry *scopetable;
int trylevel;
int _ebp;
}
新的堆栈结构如下:
以实验一的汇编代码为例,该结构体在函数初始化时被压入堆栈
00401020 push ebp //ebp
00401021 mov ebp,esp
00401023 push 0FFh //trylevel
00401025 push offset string "_filbuf.c"+0FFFFFFD4h (00422020) //scopetable
0040102A push offset __except_handler3 (0040127c) //handler
0040102F mov eax,fs:[00000000]
00401035 push eax //prev,指向原来的结构体
00401036 mov dword ptr fs:[0],esp //挂入新的结构体
注意:一般情况下,当我们将代码分别编译为debug与release版本时,其汇编代码变化较大(release进行了优化),而当函数中存在_try_except这样的语法时,两者变化较小(保证异常处理语句的堆栈结构)
scopetable
描述:结构体指针,指向了一堆结构体数组
struct scopetable_entry{
DWORD previousTryLevel //上一个try{}结构编号
PDWRD lpfnFilter //过滤函数的起始地址
PDWRD lpfnHandler //异常处理程序的地址
}
/*
scopetable[0].previousTryLevel = -1;
scopetable[0].lpfnFilter = 过滤函数1;
scopetable[0].lpfnHandler = 异常处理函数1;
scopetable[1].previousTryLevel = -1;
scopetable[1].lpfnFilter = 过滤函数2;
scopetable[1].lpfnHandler = 异常处理函数2;
scopetable[2].previousTryLevel = 1;
scopetable[2].lpfnFilter = 过滤函数3;
scopetable[2].lpfnHandler = 异常处理函数3;
*/
实验三:理解scopetable
1)编译并运行以下代码
#include <stdio.h>
#include <windows.h>
int ExceptFilter()
{
return EXCEPTION_CONTINUE_EXECUTION;
}
void TestException()
{
_try
{
}
_except(EXCEPTION_EXECUTE_HANDLER)
{
printf("异常处理函数X\n");
}
_try
{
_try
{
}
_except(GetExceptionCode() == 0xC0000094?EXCEPTION_EXECUTE_HANDLER:EXCEPTION_CONTINUE_SEARCH)
{
printf("异常处理函数Y\n");
}
}
_except(ExceptFilter())
{
printf("异常处理函数Z\n");
}
}
int main()
{
TestException();
getchar();
return 0;
}
2)观察TestException函数的反汇编
9: void TestException()
10: {
00401050 push ebp
00401051 mov ebp,esp
00401053 push 0FFh
00401055 push offset string "_filbuf.c"+0FFFFFFD4h (00422020) //scopetable
0040105A push offset __except_handler3 (0040127c)
0040105F mov eax,fs:[00000000]
00401065 push eax
00401066 mov dword ptr fs:[0],esp
0040106D add esp,0FFFFFFB4h
00401070 push ebx
00401071 push esi
00401072 push edi
00401073 mov dword ptr [ebp-18h],esp
00401076 lea edi,[ebp-5Ch]
00401079 mov ecx,11h
0040107E mov eax,0CCCCCCCCh
00401083 rep stos dword ptr [edi]
11: _try
00401085 mov dword ptr [ebp-4],0
12: {
13:
14: }
0040108C mov dword ptr [ebp-4],0FFFFFFFFh
00401093 jmp $L53975+17h (004010b2)
15: _except(EXCEPTION_EXECUTE_HANDLER)
00401095 mov eax,1
$L53976:
0040109A ret
$L53975:
0040109B mov esp,dword ptr [ebp-18h]
16: {
17: printf("异常处理函数X\n");
0040109E push offset string "\xd2\xec\xb3\xa3\xb4\xa6\xc0\xed\xba\xaf\xca\xfdX\n" (00422fc8)
004010A3 call printf (0040de70)
004010A8 add esp,4
18: }
004010AB mov dword ptr [ebp-4],0FFFFFFFFh
19:
20: _try
004010B2 mov dword ptr [ebp-4],1
21: {
22: _try
004010B9 mov dword ptr [ebp-4],2
23: {
24:
25: }
004010C0 mov dword ptr [ebp-4],1
004010C7 jmp $L53983+17h (004010fa)
26: _except(GetExceptionCode() == 0xC0000094?EXCEPTION_EXECUTE_HANDLER:EXCEPTION_CONTINUE_SEARCH)
004010C9 mov eax,dword ptr [ebp-14h]
004010CC mov ecx,dword ptr [eax]
004010CE mov edx,dword ptr [ecx]
004010D0 mov dword ptr [ebp-1Ch],edx
004010D3 mov eax,dword ptr [ebp-1Ch]
004010D6 xor ecx,ecx
004010D8 cmp eax,0C0000094h
004010DD sete cl
004010E0 mov eax,ecx
$L53984:
004010E2 ret
$L53983:
004010E3 mov esp,dword ptr [ebp-18h]
27: {
28: printf("异常处理函数Y\n");
004010E6 push offset string "\xd2\xec\xb3\xa3\xb4\xa6\xc0\xed\xba\xaf\xca\xfdY\n" (00422fb8)
004010EB call printf (0040de70)
004010F0 add esp,4
29: }
004010F3 mov dword ptr [ebp-4],1
30: }
004010FA mov dword ptr [ebp-4],0FFFFFFFFh
00401101 jmp $L53979+17h (00401120)
31: _except(ExceptFilter())
00401103 call @ILT+10(ExceptFilter) (0040100f)
$L53980:
00401108 ret
$L53979:
00401109 mov esp,dword ptr [ebp-18h]
32: {
33: printf("异常处理函数Z\n");
0040110C push offset string "\xd2\xec\xb3\xa3\xb4\xa6\xc0\xed\xba\xaf\xca\xfdZ\n" (00422fa8)
00401111 call printf (0040de70)
00401116 add esp,4
34: }
00401119 mov dword ptr [ebp-4],0FFFFFFFFh
35: }
00401120 mov ecx,dword ptr [ebp-10h]
00401123 mov dword ptr fs:[0],ecx
0040112A pop edi
0040112B pop esi
0040112C pop ebx
0040112D add esp,5Ch
00401130 cmp ebp,esp
00401132 call __chkesp (00401690)
00401137 mov esp,ebp
00401139 pop ebp
0040113A ret
3)查看scopetable内存
//scopetable_entry X
00422020 FF FF FF FF .... //不存在上级异常处理
00422024 95 10 40 00 ..@.
00422028 9B 10 40 00 ..@.
//scopetable_entry Z
0042202C FF FF FF FF .... //不存在上级异常处理
00422030 03 11 40 00 ..@.
00422034 09 11 40 00 .@.
//scopetable_entry Y
00422038 01 00 00 00 .... //存在一个上级异常处理
0042203C C9 10 40 00 ..@.
00422040 E3 10 40 00
思考:如何判断当前需要调用第几个scopetable_entry?
答案:根据trylevel的值进行判断
trylevel
描述:标识当前代码处于第几个try中,从而调用对应的scopetable_entry结构体中的函数,初始值为-1(0xff)
实验四:理解trylevel
1)编译并运行以下代码
#include <stdio.h>
#include <windows.h>
int ExceptFilter()
{
return EXCEPTION_CONTINUE_EXECUTION;
}
void TestException()
{
_try
{
_try
{
}
_except(EXCEPTION_EXECUTE_HANDLER)
{
printf("异常处理函数\n");
}
}
_except(EXCEPTION_EXECUTE_HANDLER)
{
printf("异常处理函数X\n");
}
_try
{
_try
{
}
_except(GetExceptionCode() == 0xC0000094?EXCEPTION_EXECUTE_HANDLER:EXCEPTION_CONTINUE_SEARCH)
{
printf("异常处理函数Y\n");
}
}
_except(ExceptFilter())
{
printf("异常处理函数Z\n");
}
}
int main()
{
TestException();
getchar();
return 0;
}
2)查看TestException函数的反汇编
9: void TestException()
10: {
00401050 push ebp
00401051 mov ebp,esp
00401053 push 0FFh //trylevel,初始值为-1,位于[ebp-4]
00401055 push offset string "\xd2\xec\xb3\xa3\xb4\xa6\xc0\xed\xba\xaf\xca\xfd\n"+10h (00423018)
0040105A push offset __except_handler3 (0040127c)
0040105F mov eax,fs:[00000000]
00401065 push eax
00401066 mov dword ptr fs:[0],esp
0040106D add esp,0FFFFFFB4h
00401070 push ebx
00401071 push esi
00401072 push edi
00401073 mov dword ptr [ebp-18h],esp
00401076 lea edi,[ebp-5Ch]
00401079 mov ecx,11h
0040107E mov eax,0CCCCCCCCh
00401083 rep stos dword ptr [edi]
11: _try
00401085 mov dword ptr [ebp-4],0 //修改trylevel为0
12: {
13: _try
0040108C mov dword ptr [ebp-4],1 //修改trylevel为1
14: {
15:
16: }
00401093 mov dword ptr [ebp-4],0 //恢复到0
0040109A jmp $L53982+17h (004010b9)
17: _except(EXCEPTION_EXECUTE_HANDLER)
0040109C mov eax,1
$L53983:
004010A1 ret
$L53982:
004010A2 mov esp,dword ptr [ebp-18h]
18: {
19: printf("异常处理函数\n");
004010A5 push offset string "\xd2\xec\xb3\xa3\xb4\xa6\xc0\xed\xba\xaf\xca\xfd\n" (00423008)
004010AA call printf (0040de70)
004010AF add esp,4
20: }
004010B2 mov dword ptr [ebp-4],0 //恢复到0
21: }
004010B9 mov dword ptr [ebp-4],0FFFFFFFFh
004010C0 jmp $L53978+17h (004010df)
22: _except(EXCEPTION_EXECUTE_HANDLER)
004010C2 mov eax,1
$L53979:
004010C7 ret
$L53978:
004010C8 mov esp,dword ptr [ebp-18h]
23: {
24: printf("异常处理函数X\n");
004010CB push offset string "\xd2\xec\xb3\xa3\xb4\xa6\xc0\xed\xba\xaf\xca\xfdX\n" (00422fc8)
004010D0 call printf (0040de70)
004010D5 add esp,4
25: }
004010D8 mov dword ptr [ebp-4],0FFFFFFFFh //恢复到-1
26:
27: _try
004010DF mov dword ptr [ebp-4],2 //修改trylevel为2
28: {
29: _try
004010E6 mov dword ptr [ebp-4],3 //修改trylevel为3
30: {
31:
32: }
004010ED mov dword ptr [ebp-4],2 //恢复到2
004010F4 jmp $L53990+17h (00401127)
33: _except(GetExceptionCode() == 0xC0000094?EXCEPTION_EXECUTE_HANDLER:EXCEPTION_CONTINUE_SEARCH)
004010F6 mov eax,dword ptr [ebp-14h]
004010F9 mov ecx,dword ptr [eax]
004010FB mov edx,dword ptr [ecx]
004010FD mov dword ptr [ebp-1Ch],edx
00401100 mov eax,dword ptr [ebp-1Ch]
00401103 xor ecx,ecx
00401105 cmp eax,0C0000094h
0040110A sete cl
0040110D mov eax,ecx
$L53991:
0040110F ret
$L53990:
00401110 mov esp,dword ptr [ebp-18h]
34: {
35: printf("异常处理函数Y\n");
00401113 push offset string "\xd2\xec\xb3\xa3\xb4\xa6\xc0\xed\xba\xaf\xca\xfdY\n" (00422fb8)
00401118 call printf (0040de70)
0040111D add esp,4
36: }
00401120 mov dword ptr [ebp-4],2 //恢复到2
37: }
00401127 mov dword ptr [ebp-4],0FFFFFFFFh //恢复到-1
0040112E jmp $L53986+17h (0040114d)
38: _except(ExceptFilter())
00401130 call @ILT+10(ExceptFilter) (0040100f)
$L53987:
00401135 ret
$L53986:
00401136 mov esp,dword ptr [ebp-18h]
39: {
40: printf("异常处理函数Z\n");
00401139 push offset string "\xd2\xec\xb3\xa3\xb4\xa6\xc0\xed\xba\xaf\xca\xfdZ\n" (00422fa8)
0040113E call printf (0040de70)
00401143 add esp,4
41: }
00401146 mov dword ptr [ebp-4],0FFFFFFFFh //恢复到-1
42: }
0040114D mov ecx,dword ptr [ebp-10h]
00401150 mov dword ptr fs:[0],ecx
00401157 pop edi
00401158 pop esi
00401159 pop ebx
0040115A add esp,5Ch
0040115D cmp ebp,esp
0040115F call __chkesp (00401690)
00401164 mov esp,ebp
00401166 pop ebp
00401167 ret
总结
异常处理整体流程:
CPU检测到异常
↓
查中断表执行处理函数
↓
CommonDispatchException //存储异常相关信息
↓
KiDispatchException //异常分发处理函数,判断0环异常还是3环异常
//若为3环,修正EIP,指向KiUserExceptionDispatcher
↓
KiUserExceptionDispatcher //通过RtlDispatchException查找异常处理函数位置,VEH/SEH
↓
RtlDispatchException //先查找VEH,若找不到,查找SEH(从FS:[0]开始遍历)
↓
VEH/SEH
_except_handler3执行过程:
1)根据trylevel选择scopetable数组
2)调用scopetable数组中对应的lpfnFilter函数
- EXCEPTION_EXECUTE_HANDLER(1) 执行except代码
- EXCEPTION_CONTINUE_SEARCH(0) 寻找下一个
- EXCEPTION_CONTINUE_EXECUTION(-1) 重新执行
4)如果lpfnFilter函数返回0,向上遍历,直到previousTryLevel = -1
__try__finally
描述:即使try中的代码不会产生异常,finally中的代码一定会执行
语法格式:
__try
{
//可能出错的代码
}
__finally
{
//一定要执行的代码
}
实验五:理解__try__finally
1)编译并运行以下代码
#include <stdio.h>
#include <windows.h>
void TestException()
{
for(int i=0; i<10; i++)
{
__try
{
//continue;
//break;
//return;
printf("其他代码\n");
}
__finally
{
printf("一定会执行的代码\n");
}
}
}
int main()
{
TestException();
getchar();
return 0;
}
2)取消continue注释,查看执行结果
3)取消break注释,查看执行结果
4)取消return注释,查看执行结果
局部展开
展开时机:当__try __finally中__try代码提前流程代码块时会产生比如:Continue、Break、Return等
实验六:理解局部展开
1)编译并运行以下代码
#include <stdio.h>
#include <windows.h>
void TestException()
{
for(int i=0; i<10; i++)
{
__try
{
return;
printf("其他代码\n");
}
__finally
{
printf("一定会执行的代码\n");
}
}
}
int main()
{
TestException();
getchar();
return 0;
}
2)查看TestException函数的汇编代码
9: void TestException()
10: {
00401050 push ebp
00401051 mov ebp,esp
00401053 push 0FFh
00401055 push offset string "\xd2\xec\xb3\xa3\xb4\xa6\xc0\xed\xba\xaf\xca\xfd\n"+10h (00423018)
0040105A push offset __except_handler3 (0040127c)
0040105F mov eax,fs:[00000000]
00401065 push eax
00401066 mov dword ptr fs:[0],esp
0040106D add esp,0FFFFFFB4h
00401070 push ebx
00401071 push esi
00401072 push edi
00401073 lea edi,[ebp-5Ch]
00401076 mov ecx,11h
0040107B mov eax,0CCCCCCCCh
00401080 rep stos dword ptr [edi]
11: for(int i=0; i<10; i++)
00401082 mov dword ptr [ebp-1Ch],0
00401089 jmp TestException+44h (00401094)
0040108B mov eax,dword ptr [ebp-1Ch]
0040108E add eax,1
00401091 mov dword ptr [ebp-1Ch],eax
00401094 cmp dword ptr [ebp-1Ch],0Ah
00401098 jge $L53977+2 (004010c1)
12: {
13: __try
0040109A mov dword ptr [ebp-4],0
004010A1 push 0FFh
14: {
004010A3 lea ecx,[ebp-10h]
004010A6 push ecx
004010A7 call __local_unwind2 (004011c6) //局部展开,就是这个函数找到了finally中的代码
//需要中途从__try中出来时由编译器生成
004010AC add esp,8
15: return;
004010AF jmp $L53977+2 (004010c1) //跳转至退出函数前的准备工作
16: printf("其他代码\n");
17: }
18: __finally
19: {
20: printf("一定会执行的代码\n");
004010B1 push offset string "\xd2\xbb\xb6\xa8\xbb\xe1\xd6\xb4\xd0\xd0\xb5\xc4\xb4\xfa\xc2\xeb\n" (0042
004010B6 call printf (0040de70)
004010BB add esp,4
$L53975:
004010BE ret
21: }
22: }
004010BF jmp TestException+3Bh (0040108b)
23: }
004010C1 mov ecx,dword ptr [ebp-10h]
004010C4 mov dword ptr fs:[0],ecx
004010CB pop edi
004010CC pop esi
004010CD pop ebx
004010CE add esp,5Ch
004010D1 cmp ebp,esp
004010D3 call __chkesp (00401690)
004010D8 mov esp,ebp
004010DA pop ebp
004010DB ret
3)观察scopetable,可以发现异常过滤代码为0,局部展开函数由此判断except与finally
4)跟进__local_unwind2
5)执行结果
全局展开
展开时机:每次执行__except代码之前,会重新从异常发生位置遍历一次__finally,如果存在则依次调用局部展开函数
实验七:理解全局展开
1)编译并运行以下代码
#include <stdio.h>
#include <windows.h>
void TestException()
{
__try
{
__try
{
__try
{
*(int*)0 = 1;
}
__finally
{
printf("一定会执行的代码A\n");
}
}
__finally
{
printf("一定会执行的代码B\n");
}
}
__except(1)
{
printf("异常处理函数\n");
}
}
int main()
{
TestException();
getchar();
return 0;
}
2)执行结果