6.编译器拓展SEH

//1.挂入链表相当于这部分
//fs[0]-> Exception
	_asm
	{
		mov eax, fs:[0]
		mov temp,eax
		lea ecx,Exception
		mov fs:[0],ecx
	}
	//为SEH成员赋值
	Exception.Next = (_EXCEPTION*)temp;
	Exception.Handler = (DWORD)&MyEexception_handler;

//下面是2,3
EXCEPTION_DISPOSITION _cdecl MyEexception_handler
(
	struct _EXCEPTION_RECORD *ExceptionRecord,	//异常结构体
	PVOID EstablisherFrame,						//SEH结构体地址
	struct _CONTEXT *ContextRecord,				//存储异常发生时的各种寄存器的值 栈位置等
	PVOID DispatcherContext
)
{
	if (ExceptionRecord->ExceptionCode == 0xC0000094)		//2.异常过滤
	{
		ContextRecord->Eip = ContextRecord->Eip + 2;			//3.异常处理
		ContextRecord->Ecx = 100;

		return ExceptionContinueExecution;
	}
	return ExceptionContinueSearch;
}

编译器支持的SEH,SEH是windows平台下特有的换在别的平台下它是不支持的。

//这里的代码底层实现就类似上面的代码。
_try						//1.挂入链表
	{

	}
	_except(过滤表达式)	//2.异常过滤
	{
		异常处理程序		//3.异常处理程序
	}

异常过滤表达式常量值
1) EXCEPTION_EXECUTE_HANDLER (1)	执行except代码
2) EXCEPTION_CONTINUE_SEARCH (0)	寻找下一个异常处理函数
3) EXCEPTION_CONTINUE_EXECUTION (-1)	返回出错位置重新执行

过滤表达式3种方式

  1. 直接写常量值
  2. 表达式
  3. 调用函数

表达式写法

_try
	{
		_asm
		{
			xor edx,edx
			xor ecx,ecx
			mov eax,10
			idiv ecx	//EDX = EAX /ECX
		}
	}
		//如果异常码为0xC0000094返回1否则返回0
	_except(GetExceptionCode() == 0xC0000094 ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
	{
		printf("如果出现异常 此处处理\n");
	}

调用函数写法

//参数根据需要来写,可以不要参数
int ExceptFilter(LPEXCEPTION_POINTERS pExceptionInfo)
{
	pExceptionInfo->ContextRecord->Ecx = 1;//异常处理
	return EXCEPTION_CONTINUE_EXECUTION;//返回出错位置重新执行
}

int main()
{
	_try
	{
		_asm
		{
			xor edx,edx
			xor ecx,ecx
			mov eax,10
			idiv ecx	//EDX = EAX /ECX
		}
	}//GetExceptionInformation获取异常结构指针
	_except(ExceptFilter(GetExceptionInformation()))
	{
		printf("如果出现异常 此处处理\n");
	}
	
	getchar();
}

这是没用异常的函数汇编代码的生成
在这里插入图片描述

这是用了异常的函数汇编代码生成
在这里插入图片描述

异常处理函数:_except_handler3(不同的编译器这个也是不一样的)

可以看到用了异常函数的多了很多代码,新增的代码其实就是让 fs[0] 指向新增的异常处理链。

如果我们自己写SEH,用一个异常处理函数就要手动挂入一个SEH链表,两个就挂入两次,以此类推…,但用编译器的就不需要。

_try_except嵌套重复

每个使用_try _except的函数,不管其内部嵌套或反复使用多少try_except,都只注册一遍,即只将一个_EXCEPTION_REGISTRATION_RECORD挂入当前线程的异常链表中(对于递归函数,每一次调用都会创建一个 _EXCEPTION_REGISTRATION_RECORD,并挂入线程的异常链表中)。

我这用了3个_try _except

_try
	{
		_try
		{

		}
		_except(1)
		{
	
		}
	}
	_except(1)
	{

	}

	_try
	{

	}
	_except(1)
	{

	}

只要挂一次SEH链就会修改一次 FS[0]

  push        ebp
   mov         ebp,esp
   push        0FFh
   push        offset string "i386\\chkesp.c"+0FFFFFFD4h (00422020)
   push        offset __except_handler3 (00401224)
   mov         eax,fs:[00000000]
   push        eax
   mov         dword ptr fs:[0],esp
   add         esp,0B8h
   push        ebx
   push        esi
   push        edi
   mov         dword ptr [ebp-18h],esp
   lea         edi,[ebp-58h]
   mov         ecx,10h
   mov         eax,0CCCCCCCCh
   rep stos    dword ptr [edi]
  _try
   mov         dword ptr [ebp-4],0
  {
      _try
   mov         dword ptr [ebp-4],1
      {}
   mov         dword ptr [ebp-4],0
   jmp         $L16979+0Ah (0040107c)
      _except(1)
   mov         eax,1

   ret

   mov         esp,dword ptr [ebp-18h]
      {}
   mov         dword ptr [ebp-4],0
  }
   mov         dword ptr [ebp-4],0FFFFFFFFh
   jmp         $L16975+0Ah (00401095)
  _except(1)
   mov         eax,1

   ret

   mov         esp,dword ptr [ebp-18h]
  {}
   mov         dword ptr [ebp-4],0FFFFFFFFh
  _try
   mov         dword ptr [ebp-4],2
  {}
   mov         dword ptr [ebp-4],0FFFFFFFFh
   jmp         $L16983+0Ah (004010b5)
  _except(1)
   mov         eax,1

   ret

   mov         esp,dword ptr [ebp-18h]
  {}
   mov         dword ptr [ebp-4],0FFFFFFFFh
   mov         ecx,dword ptr [ebp-10h]
   mov         dword ptr fs:[0],ecx
   pop         edi
   pop         esi
   pop         ebx
   mov         esp,ebp
   pop         ebp
   ret

不管其内部嵌套或反复使用多少try_except,都只挂一次,指向的异常处理函数也是确定的。

3个异常处理函数就应该有3个链,它只挂了一个,这时怎么实现的呢?

拓展SEH链结构

struct _EXCEPTION_REGISTRATION 
{ 
	struct _EXCEPTION_REGISTRATION* prev;//下一个异常链
	void (*handler)(PEXCEPTION_RECORD, PEXCEPTION_ REGISTRATION, PCONTEXT, PEXCEPTION_RECORD);//指向异常函数
	struct scopetable_entry scopetable;
	int trylevel;	//当前代码执行在那个try里
	int _ebp;
};

名字有变不过叫什么名字无所谓了,第一和第二个成员没变,后面加了3个成员后面再讲…

无论怎么拓展,有两个成员必须要有,一个是链表节点一个是异常处理函数。

在这里插入图片描述

在这里插入图片描述

_EXCEPTION_REGISTRATION .scopetable成员分析

struct scopetable_entry
{
	DWORD previousTryLevel;	//上一个try{}结构编号
	PDWORD lpfnFilter;		//指向过滤函数的起始地址,_except()括号内那个
	PDWORD lphnHandle;		//异常处理程序的地址
};
int ExceptionFilter()
{
	return EXCEPTION_CONTINUE_EXECUTION;
}

void a()
{
	_try
	{
		//异常点A
	}
	_except(EXCEPTION_EXECUTE_HANDLER)
	{
		printf("异常处理函数1\n");
	}

	_try
	{
		//异常点B
		_try
		{
			//异常点C	
		}
		_except(GetExceptionCode() == 0xC0000094 ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
		{
				printf("异常处理函数C \n");
		}
	}
	_except(ExceptionFilter())
	{
		printf("异常处理函数2\n");
	}

}

int main()
{
	a();
}

在这里插入图片描述
我这写了3个异常就会有3块,scopetable_entry结构的第2第3我们知道了,第一个成员是什么?

previousTryLevel第一和第二的值都是 -1 ,第三个是 1
第一个它没有上一个try它是-1,第二个它是最外层它也是-1,第三个它的上一个是 1 。(结合下面的下标理解)

scopetable[0].previousTryLevel =-1;
scopetable[0].IpfnFilter=过滤函数1;
scopetable[0].lfnHandler =异常处理函数1;

scopetable[1].previousTryLevel =-1;
scopetable[1].IpfnFilter=过滤函数2;
scopetable[1].IpfnHandler=异常处理函数2;

scopetable[2].previousTryLevel = 1;
scopetable[2].IpfnFilter=过滤函数3;
scopetable[2].lpfnHandler=异常处理函数3;

struct scopetable;这个结构已经将所有你所有的过滤函数指针,异常处理函数指针,全部放进数组里了, 所以无论它使用多少try_except,都只需要注册一次。

_EXCEPTION_REGISTRATION .trylevel

int ExceptionFilter()
{
	return 1;
}

void a()
{
	_try
	{
		_try
		{
		
		}
		_except(EXCEPTION_EXECUTE_HANDLER)
		{
			printf("异常处理函数\n");
		}
	}
	_except(EXCEPTION_EXECUTE_HANDLER)
	{
		printf("异常处理函数\n");
	}


	_try
	{
		_try
		{
			
		}
		_except(GetExceptionCode() == 0xC0000094 ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
		{
				printf("异常处理函数\n");
		}
	}
	_except(ExceptionFilter())
	{
		printf("异常处理函数\n");
	}

}

在这里插入图片描述
这个 -1 就是当前trylevel的值

struct scopetable_entry虽然记录了异常信息但异常触发了,它不知道查谁所以需要trylevel来记录当前的代码执行在哪个try里。

当它要进入try时第一件事就是将trylevel修改为对应的scopetable[ ]索引值。
在这里插入图片描述
在这里插入图片描述

退出时又将 trylevel 改为 - 1。

_except_handler3执行过程

  1. CPU检测到异常 查中断表执行处理函数CommonDispatchException -> KiDispatchException ->KiUserExceptionDispatcher -> RtIDispatchException -> VEH ->SEH
  2. 执行_except_handler3函数

<1>根据 trylevel 选择 scopetable 数组
<2>调用 scopetable 数组中对应的 IpfnFilter 函数

  1. EXCEPTION_EXECUTE_HANDLER (1) 执行except代码
  2. EXCEPTION_CONTINUE_SEARCH (0) 寻找下一个
  3. EXCEPTION_CONTINUE_EXECUTION (-1) 重新执行触发异常的代码

<3>如果IpfnFilter 函数返回1,调用lpfnHandler指向的异常处理函数
<3>如果IpfnFilter 函数返回0,寻址下一个异常处理函数
<3>如果IpfnFilter 函数返回-1,重新执行触发异常的代码

_try_finally

_try
{
	//可能出现错误的代码
}
_finally
{
	//一定会执行的代码
}

在这里插入图片描述

在这里插入图片描述
_finally里的代码就算你直接将他return或者触发异常,它一样会执行,下面看看它是然后实现的。

在这里插入图片描述

scopetable[0].previousTryLevel =-1;
scopetable[0].IpfnFilter=0;
scopetable[0].lfnHandler =0x401179;

通过IpfnFilter来判断如果它的值为0,就是 _try_finally{};

它进入try时马上就掉用了 _local_unwind2 这个函数,_local_unwind2 这个函数就叫局部展开

在这里插入图片描述
调用了lfnHandler 指向的函数。
在这里插入图片描述

全局展开_global_unwind2

void a()
{
	_try
	{
		_try
		{

			_try
			{
				*(int*)0 = 10;
			}
			_finally
			{
				printf("一定会执行的代码A\n");
			}

		}
		_finally
		{
			printf("一定会执行的代码B\n");
		}
	}	
	_except(1)
	{
		printf("异常处理函数\n");
	}

}

在这里插入图片描述

记得在_except(1)那下断,往下跟就能看到
在这里插入图片描述
_global_unwind2 全局展开

在这里插入图片描述
调用局部展开

  1. 触发异常,_except_handler3接管
  2. _except_handler3就会找异常处理函数
  3. 找自己和它的下一个发现scopetable.IpfnFilter=0,处理不了
  4. 最后那一个_except(1)后可以处理了,但它会先执行_global_unwind2
  5. _global_unwind2 会从触发异常的那个try开始,调用局部展开

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值