编译器扩展SEH

编译器扩展的SEH

为什么需要编译器扩展的SEH

在这里插入图片描述

之前我们已经了解过SEH的异常处理流程,SEH的处理流程实际上就是在当前的堆栈中挂一个链表。

这种处理方式有一个比较麻烦的地方,如果我们想使用SEH这种方式来处理异常,那就意味着我们必须要做如下的几件事:

  1. 创建一个结构体,并把它挂到当前TEB的ExceptionList链表里
  2. 编写异常处理函数

这些操作相对来说比较复杂,而编译器对这一异常处理机制进行了拓展。

编译器支持的SEH

_try                               	1) 挂入链表
{
       
}
_except(过滤表达式)       		    2) 异常过滤
{
    异常处理程序            		   3) 异常处理程序
}

我们只需要将可能出现异常的代码放到try块里,编译器就会替我们把异常的处理程序挂到Exception链表。

过滤表达式

except里的过滤表达式用于异常过滤,只能有以下三个值:

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

过滤表达式只能有三种写法:

  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();
}

try_except的实现细节

下面我们来了解一下编译器是如何通过try_except将异常处理函数挂入链表的

手动挂入链表

_asm
{                                                  
	mov eax,FS:[0]						
	mov temp,eax						
	lea ecx,myException	
    mov FS:[0],ecx                    
}

自动挂入链表

示例代码如下:

void TestException()
{
	_try
	{

	}
	_except(1)
	{

	}
}

将程序载入OD,分析编译器生成的代码。

在这里插入图片描述

编译器会自动帮我们修改FS:[0],将异常处理函数挂入SEH链表

_try_except嵌套重复

每个使用_try_except的函数,不管其内部嵌套或反复使用多少次,_try_except都只注册一遍,即只将一个EXCEPTION_REGISTRATION_RECORD挂入当前线程的SEH链表中

对于递归函数,每一次调用都会创建一个 _EXCEPTION_REGISTRATION_RECORD,并挂入线程的异常链表中

typedef struct _EXCEPTION_REGISTRATION_RECORD 
{
    struct _EXCEPTION_REGISTRATION_RECORD *Next;
    PEXCEPTION_ROUTINE Handler;
} EXCEPTION_REGISTRATION_RECORD;

示例代码如下:

_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个链,它只挂了一个,这时怎么实现的呢?

拓展的_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;
};       

第一个成员和第二个成员和原来的EXCEPTION_REGISTRATION_RECORD含义一样,后面多了三个成员。堆栈中的SEH链也发生了变化

在这里插入图片描述

堆栈中的结构对应汇编代码如图:

在这里插入图片描述

接下来分析学习的三个成员的含义,第三个成员比较好理解,就是当前堆栈的ebp,剩下的两个成员才是关键

struct _EXCEPTION_REGISTRATION
 {
        struct _EXCEPTION_REGISTRATION *prev;
        void (*handler)(PEXCEPTION_RECORD, PEXCEPTION_REGISTRATION, PCONTEXT, PEXCEPTION_RECORD);
        struct scopetable_entry *scopetable;
        int trylevel;
        int _ebp;
}; 

scopetable成员分析

这个成员是一个结构体指针,结构体包含以下三个成员

struct scopetable_entry
{
       DWORD		previousTryLevel	    //上一个try{}结构编号 
       PDWRD        lpfnFilter       		//过滤函数的起始地址
       PDWRD        lpfnHandler  			//异常处理程序的地址    	
}

try_finally

之前的课程我们讲解了__try __except的的执行细节,但编译器还提供了另外一组程序块

__try
	{   
		//可能出错的代码
	}   
	__finally
	{   
		//一定要执行的代码
	} 

发布了109 篇原创文章 · 获赞 115 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/qq_38474570/article/details/104346489