节一:终止处理(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]