节二:异常处理(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"<
=========代码段二==========
void fun_exception_2(); void fun_exception_1() { __try { fun_exception_2(); } __except( EXCEPTION_EXECUTE_HANDLER ) { cout<<"fun_exception_1"<
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]