`

(00XX系列)摸摸Windows的SEH

阅读更多
节一:终止处理(Termination Handlers)
节二:异常处理(Exception Handling)
节三:其他参考函数和总结

(不喜欢节前代码的直接往下拉)
节一:终止处理(Termination Handlers)

__try
{
	//如果是return,会先保存当前返回值,不受__finally影响
}
__finally
{
	//除了exit,terminateProcess, terminateThread,这里必执行
}

int fun_1()
{
004114E0  push        ebp  
004114E1  mov         ebp,esp 
004114E3  push        0FFFFFFFEh 
004114E5  push        offset ___rtc_tzz+108h (418BC8h) 
004114EA  push        offset @ILT+145(__except_handler4) (411096h)  //压入异常的函数地址
004114EF  mov         eax,dword ptr fs:[00000000h] 
004114F5  push        eax  
004114F6  add         esp,0FFFFFF2Ch 
004114FC  push        ebx  
004114FD  push        esi  
004114FE  push        edi  
004114FF  lea         edi,[ebp-0E4h] 
00411505  mov         ecx,33h 
0041150A  mov         eax,0CCCCCCCCh 
0041150F  rep stos    dword ptr es:[edi] 
00411511  mov         eax,dword ptr [___security_cookie (419010h)] 
00411516  xor         dword ptr [ebp-8],eax 
00411519  xor         eax,ebp 
0041151B  push        eax  
0041151C  lea         eax,[ebp-10h] 
0041151F  mov         dword ptr fs:[00000000h],eax 
   int dwTemp = 0;
00411525  mov         dword ptr [ebp-20h],0 

   while (dwTemp < 10) {
0041152C  cmp         dword ptr [ebp-20h],0Ah 
00411530  jge         $LN12+0Bh (411592h) 

      __try {
00411532  mov         dword ptr [ebp-4],0 
         if (dwTemp == 2)
00411539  cmp         dword ptr [ebp-20h],2 
0041153D  jne         fun_1+74h (411554h) 
            continue;
0041153F  push        0FFFFFFFEh 
00411541  lea         eax,[ebp-10h] 
00411544  push        eax  
00411545  push        offset ___security_cookie (419010h) 
0041154A  call        @ILT+165(__local_unwind4) (4110AAh) //很复杂的异常调用,最后跳到finally 
0041154F  add         esp,0Ch                             //还原异常调用堆栈
00411552  jmp         fun_1+4Ch (41152Ch) 

         if (dwTemp == 3)
00411554  cmp         dword ptr [ebp-20h],3 
00411558  jne         fun_1+8Fh (41156Fh) 
            break;
0041155A  push        0FFFFFFFEh 
0041155C  lea         eax,[ebp-10h] 
0041155F  push        eax  
00411560  push        offset ___security_cookie (419010h) 
00411565  call        @ILT+165(__local_unwind4) (4110AAh) //很复杂的异常调用,最后跳到finally  
0041156A  add         esp,0Ch 
0041156D  jmp         $LN12+0Bh (411592h) 
      }
      __finally {
0041156F  mov         dword ptr [ebp-4],0FFFFFFFEh 
00411576  call        $LN9 (41157Dh)    //执行__finally里的代码               
0041157B  jmp         $LN12 (411587h)   //跳到正常的while结尾
         dwTemp++;
0041157D  mov         eax,dword ptr [ebp-20h] 
00411580  add         eax,1 
00411583  mov         dword ptr [ebp-20h],eax 
$LN10:
00411586  ret              
      }

      dwTemp++;
00411587  mov         eax,dword ptr [ebp-20h] 
0041158A  add         eax,1 
0041158D  mov         dword ptr [ebp-20h],eax 
   }
00411590  jmp         fun_1+4Ch (41152Ch) 

   dwTemp += 10;
00411592  mov         eax,dword ptr [ebp-20h] 
00411595  add         eax,0Ah 
00411598  mov         dword ptr [ebp-20h],eax 
   return(dwTemp);
0041159B  mov         eax,dword ptr [ebp-20h] 


}

try块中的return;
return i;
004117F3  mov         eax,dword ptr [ebp-20h] 
004117F6  mov         dword ptr [ebp-0ECh],eax 
004117FC  push        0FFFFFFFEh 
004117FE  lea         ecx,[ebp-10h] 
00411801  push        ecx  
00411802  push        offset ___security_cookie (41F040h) 
00411807  call        @ILT+240(__local_unwind4) (4110F5h) //执行__finally中函数


       
        可以看出SHE在一切企图退出try块但是不退出函数体,之前插入jmp到__finally中的汇编然后清空自己堆栈,再返回下文的处理;
        对于退出函数体的代码(return),拆了两部分,执行到mov eax 返回值,然后把行为交给__finally;
        对于exit之类的结束函数,直接返回。
        而__finally中的处理是把__finally中的代码当成一个函数调用,返回后(直接一个ret指令,非常的简洁),其中的行为全部按正常行为执行。(比如这里如果有return 就直接返回)。 然后跳回到try的上下文中。

        In fact, it is always best to remove all returns, continues, breaks, gotos, and so on from inside both the try and finally blocks of a termination handler and to put these statements outside the handler.

        以上就是__try的一些大致的原理.进行汇编跟调的话,很容易的发现call @ILT+240(__local_unwind4) (4110F5h)总是执行了许多繁杂的代码。这又钩了C/C++程序员效率问题唧唧喳喳的老毛病。微软提供了配套的__leave关键字:

__leave;
0041152F  jmp         fun_1+76h (411556h) 


节二:Exception Handling(异常处理)

__try
{	
}
__except( EXCEPTION_EXECUTE_HANDLER )
{
}


=========代码段一==========
int fun_finally()
{
	__try
	{
		int i=0;
		i/=i;
		cout<<"fun_finally:try"<<endl;
	}
	__finally
	{
		cout<<"fun_finally::finally"<<endl;
	}

	return 0;
}

int fun_exception()
{
	int i;
	__try
	{
		fun_finally();
	}
	__except( EXCEPTION_EXECUTE_HANDLER )
	{
		cout<<"fun_exception:except"<<endl;
	}

	return 0;
}

int _tmain(int argc, _TCHAR* argv[])
{
	fun_exception();
	return 0;
}


=========代码段二==========
void fun_exception_2();
void fun_exception_1()
{
	__try
	{
		fun_exception_2();
	}
	__except( EXCEPTION_EXECUTE_HANDLER )
	{
		cout<<"fun_exception_1"<<endl;
	}
}

void fun_exception_2()
{
	__try
	{
		int i=0;
		i/=i;
	}
	__except( EXCEPTION_EXECUTE_HANDLER )
	{
		cout<<"fun_exception_2"<<endl;	
	}	
}

int _tmain(int argc, _TCHAR* argv[])
{
	
	fun_exception_1();
	return 0;
}



1、如何跳转
       except跳转有点奇怪,不像__finally有个典型的call或者__leave的jmp式。在VC里调试,而是直接从你出现异常的语句上跳到except中。

2、except后语句从哪儿开始执行?
       一般来说,我们在except都会放一些保护的措施。但如果说try里的代码块越多,产生的错误性就越未知,except的保护措施就越没有针对性。对于有把握的可以参考 EXCEPTION_CONTINUE_HANDLER返回值。EXCEPTION_CONTINUE_HANDLER恰是认为你有把握处理出现的异常,并且重新执行产生异常的语句。
       无把握的,或许觉得直接返回或者干脆直接跳到except后更保险,可以参考EXCEPTION_EXECUTE_HANDLER 关键字。

3、except的等级规划
       具体的图在windows核心编程里非常清楚。这里稍微综述下:当异常发生的原则是,先寻找离它最近的_except块。确定返回的是EXCEPTION_EXECUTE_HANDLER,才会到原来的地方继续执行其他的一些保护函数(俗称展开)。比如except和finally一起用,并且finally是except的一个子类,则finally先调用,然后再回到except里(看代码一)。这也是应该的,finally的规则就是只要不是exit之类的函数,就必须先执行爷的,况且你调用爷,爷没执行完你敢抛弃爷?
       如果是except里有个except,比如代码二,执行完子类的except后就回不到调用函数了。


节三:其他参考函数
       前面的一些函数功能已经足已我的使用。也不用为没掌握剩余的而自我烦恼。如果你能将整本windows核心编程的内容都在一个应用程序里表示出来,除了要写一个完整的OS,我实在想不出来还有什么得需要如此的应用。
       其余的关键字有EXCEPTION_CONTINUE_HANDLER,EXCEPTION_CONTINUE_SEARCH.获取异常的信息函GetExceptionCode,GetExceptionInformation.
另注:GetExceptionInformation可以帮你打印出程序崩溃时的调用堆栈信息(附件中特意包含,不过下载处已经忘记了,向原作者表示抱歉)。比如著名的内存溢出检测库VLD等。
       在网上查找SEH的资料时,发现非常深也非常浅,深包括整个异常链表的串联等(我看了下原文是97年微软的连接),但我觉得一些机制地方如果能理解汇编的方式就算是足够了;浅包括除了搜索结果很少,一些程序员论坛提问的方式也仅仅是异常怎么用。从表现形式来说,复杂的SEH机制还不如返回值的判断来得直接。而此番学习,是因为游戏服务器的一些架构复杂的逻辑因素太多,经常会有空指针等操作。当然,这些问题都要暴露出来才能解决,但是不能以服务器当掉做为代价。况且一些复杂的多个依赖对象创建,一些异常情况的判断,用一些SEH结构可以表示得更简洁和高效。
       这里感谢《windows 核心编程 5th》一书,讲得非常全面和细致。我最喜欢的是下面这段话:
However, keep in mind that the process might be unstable because an exception was raised. So it is advisable to keep the code inside the filter relatively simple
[/size]
1
1
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics