Windows异常学习笔记(四)—— 编译器扩展SEH

要点回顾

当我们通过编程实现windows的异常处理方式使用SEH结构化异常时,我们必须做如下几件事情

  1. 异常处理函数挂入SEH链表
  2. 需要定义一个异常处理函数(包含异常过滤异常处理

注意:这种方式相对而言不太方便,因此现在的编译器都对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函数

  1. EXCEPTION_EXECUTE_HANDLER(1) 执行except代码
  2. EXCEPTION_CONTINUE_SEARCH(0) 寻找下一个
  3. 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)执行结果
在这里插入图片描述

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值