SEH 学习笔记二
2010-12-21 17:38 curer 阅读(2591) 评论(2) 编辑 收藏 举报上一篇总结了SEH的基础知识,这一篇我们稍微深入一点。
之前我们知道了异常是什么样的,以及我们写好了出现异常时执行的代码。那么windows是如何调用我们的代码呢?在了解这个之后,global unwind,异常嵌套也就容易了解了。不得不说,如果之前没有了解SEH,这的确是一件非常有挑战的事情。当然,如果从根上,也就是从硬件到os kernel再到user mode这个就不是挑战,而是不可能一下子完成的任务了。我们还是一步步来,从异常跑到user mode开始。准备好了么?
首先想一个问题,上一篇我们的第一个例子,我们保护了一段代码,但是同时,为了修正这段代码,我们又引入了一段代码。事实上,我们的确没有解决问题,谁能保证我们又引入的代码本身不会再产生异常呢?而且,这次是在发生异常的“拯救”过程中又产生异常。让我们先看一个简单的例子,我对上一篇的第一个例子,加了一点点修饰。
EXCEPTION_DISPOSITION __cdecl _except_handler( struct _EXCEPTION_RECORD *ExceptionRecord, void * EstablisherFrame, struct _CONTEXT *ContextRecord, void * DispatcherContext ) { unsigned i; // Indicate that we made it to our exception handler printf( "Hello from an exception handler\n" ); if (ExceptionRecord->ExceptionFlags & 0x10) { printf( "bad except\n" ); } else { // bad happen __asm { mov eax, 0 mov [eax], 1 } } // // Change EAX in the context record so that it points to someplace // where we can successfully write ContextRecord->Eax = (DWORD)&scratch; // Tell the OS to restart the faulting instruction return ExceptionContinueExecution; }
结果
Hello from an exception handler
Hello from an exception handler
bad except
After writing!
如果我们把ExceptionRecord->ExceptionFlags & 0x10 判断去掉,无条件的执行 bad ,那么我们好像陷入了死循环中,不停的输出Hello from an exception handler,而整个线程也死在了栈溢出,栈溢出是一个非常严重的异常,他会导致我们的finally block 无法执行,我们获得的同步变量没有被释放掉,即使我们填入了finally block。 我们一切的梦似乎还没有开始就结束了,而原因仅仅是因为我们在异常中又产生了一个异常。异常本身已经很让人头痛了,现在又来了一个。为了彻底了解,我们必须从了解OS是如何调用我们的代码,如何分配异常开始。
异常user mode 从KiUserExceptionDispatcher 开始。
VOID KiUserExceptionDispatcher( PEXCEPTION_RECORD pExcptRec, CONTEXT * pContext ) { DWORD retValue; // Note: If the exception is handled, RtlDispatchException() never returns if ( RtlDispatchException( pExceptRec, pContext ) ) retValue = NtContinue( pContext, 0 ); else retValue = NtRaiseException( pExceptRec, pContext, 0 ); EXCEPTION_RECORD excptRec2; excptRec2.ExceptionCode = retValue; excptRec2.ExceptionFlags = EXCEPTION_NONCONTINUABLE; excptRec2.ExceptionRecord = pExcptRec; excptRec2.NumberParameters = 0; RtlRaiseException( &excptRec2 ); }
BOOLEAN NTAPI RtlDispatchException(IN PEXCEPTION_RECORD ExceptionRecord, IN PCONTEXT Context) { PEXCEPTION_REGISTRATION_RECORD RegistrationFrame, NestedFrame = NULL; DISPATCHER_CONTEXT DispatcherContext; EXCEPTION_RECORD ExceptionRecord2; EXCEPTION_DISPOSITION Disposition; ULONG_PTR StackLow, StackHigh; ULONG_PTR RegistrationFrameEnd; /* Perform vectored exception handling if we are in user mode */ if (RtlpGetMode() != KernelMode) { /* Call any registered vectored handlers */ if (RtlCallVectoredExceptionHandlers(ExceptionRecord, Context)) { /* Exception handled, continue execution */ return TRUE; } } /* Get the current stack limits and registration frame */ RtlpGetStackLimits(&StackLow, &StackHigh); RegistrationFrame = RtlpGetExceptionList(); /* Now loop every frame */ while (RegistrationFrame != EXCEPTION_CHAIN_END)//#define EXCEPTION_CHAIN_END -1 { /* Find out where it ends */ RegistrationFrameEnd = (ULONG_PTR)RegistrationFrame + sizeof(EXCEPTION_REGISTRATION_RECORD); /* Make sure the registration frame is located within the stack */ if ((RegistrationFrameEnd > StackHigh) || ((ULONG_PTR)RegistrationFrame < StackLow) || ((ULONG_PTR)RegistrationFrame & 0x3)) { /* Check if this happened in the DPC Stack */ if (RtlpHandleDpcStackException(RegistrationFrame, RegistrationFrameEnd, &StackLow, &StackHigh)) { /* Use DPC Stack Limits and restart */ continue; } /* Set invalid stack and return false */ ExceptionRecord->ExceptionFlags |= EXCEPTION_STACK_INVALID; return FALSE; } /* Check if logging is enabled */ RtlpCheckLogException(ExceptionRecord, Context, RegistrationFrame, sizeof(*RegistrationFrame)); //这里应该有判断SEH是否有效,reactos这里并没有检查。
/* Call the handler */ Disposition = RtlpExecuteHandlerForException(ExceptionRecord, RegistrationFrame, Context, &DispatcherContext, RegistrationFrame-> Handler); /* Check if this is a nested frame */ if (RegistrationFrame == NestedFrame) { /* Mask out the flag and the nested frame */ ExceptionRecord->ExceptionFlags &= ~EXCEPTION_NESTED_CALL; NestedFrame = NULL; } /* Handle the dispositions */ switch (Disposition) { /* Continue searching */ case ExceptionContinueExecution: /* Check if it was non-continuable */ if (ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE) { /* Set up the exception record */ ExceptionRecord2.ExceptionRecord = ExceptionRecord; ExceptionRecord2.ExceptionCode = STATUS_NONCONTINUABLE_EXCEPTION; ExceptionRecord2.ExceptionFlags = EXCEPTION_NONCONTINUABLE; ExceptionRecord2.NumberParameters = 0; /* Raise the exception */ RtlRaiseException(&ExceptionRecord2); } else { /* Return to caller */ return TRUE; } /* Continue searching */ case ExceptionContinueSearch: break; /* Nested exception */ case ExceptionNestedException: /* Turn the nested flag on */ ExceptionRecord->ExceptionFlags |= EXCEPTION_NESTED_CALL; /* Update the current nested frame */ if (DispatcherContext.RegistrationPointer > NestedFrame) { /* Get the frame from the dispatcher context */ NestedFrame = DispatcherContext.RegistrationPointer; } break; /* Anything else */ default: /* Set up the exception record */ ExceptionRecord2.ExceptionRecord = ExceptionRecord; ExceptionRecord2.ExceptionCode = STATUS_INVALID_DISPOSITION; ExceptionRecord2.ExceptionFlags = EXCEPTION_NONCONTINUABLE; ExceptionRecord2.NumberParameters = 0; /* Raise the exception */ RtlRaiseException(&ExceptionRecord2); break; } /* Go to the next frame */ RegistrationFrame = RegistrationFrame->Next; } /* Unhandled, return false */ return FALSE; }
上面的代码来自ReactOS,和我们xp2上的代码已经很接近了(除了SEH的安全机制),RtlDispatchException将处理异常的部分交给了RtlpExecuteHandlerForException。
看一下RtlUnwind,同样来自ReactOS。同样把脏活给了RtlpExecuteHandlerForUnwind来做。
VOID NTAPI RtlUnwind(IN PVOID TargetFrame OPTIONAL, IN PVOID TargetIp OPTIONAL, IN PEXCEPTION_RECORD ExceptionRecord OPTIONAL, IN PVOID ReturnValue) { PEXCEPTION_REGISTRATION_RECORD RegistrationFrame, OldFrame; DISPATCHER_CONTEXT DispatcherContext; EXCEPTION_RECORD ExceptionRecord2, ExceptionRecord3; EXCEPTION_DISPOSITION Disposition; ULONG_PTR StackLow, StackHigh; ULONG_PTR RegistrationFrameEnd; CONTEXT LocalContext; PCONTEXT Context; /* Get the current stack limits */ RtlpGetStackLimits(&StackLow, &StackHigh); /* Check if we don't have an exception record */ if (!ExceptionRecord) { /* Overwrite the argument */ ExceptionRecord = &ExceptionRecord3; /* Setup a local one */ ExceptionRecord3.ExceptionFlags = 0; ExceptionRecord3.ExceptionCode = STATUS_UNWIND; ExceptionRecord3.ExceptionRecord = NULL; ExceptionRecord3.ExceptionAddress = _ReturnAddress(); ExceptionRecord3.NumberParameters = 0; } /* Check if we have a frame */ if (TargetFrame) { /* Set it as unwinding */ ExceptionRecord->ExceptionFlags |= EXCEPTION_UNWINDING; } else { /* Set the Exit Unwind flag as well */ ExceptionRecord->ExceptionFlags |= (EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND); } /* Now capture the context */ Context = &LocalContext; LocalContext.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL | CONTEXT_SEGMENTS; RtlpCaptureContext(Context); /* Pop the current arguments off */ Context->Esp += sizeof(TargetFrame) + sizeof(TargetIp) + sizeof(ExceptionRecord) + sizeof(ReturnValue); /* Set the new value for EAX */ Context->Eax = (ULONG)ReturnValue; /* Get the current frame */ RegistrationFrame = RtlpGetExceptionList(); /* Now loop every frame */ while (RegistrationFrame != EXCEPTION_CHAIN_END) { /* If this is the target */ if (RegistrationFrame == TargetFrame) ZwContinue(Context, FALSE); /* Check if the frame is too low */ if ((TargetFrame) && ((ULONG_PTR)TargetFrame < (ULONG_PTR)RegistrationFrame)) { /* Create an invalid unwind exception */ ExceptionRecord2.ExceptionCode = STATUS_INVALID_UNWIND_TARGET; ExceptionRecord2.ExceptionFlags = EXCEPTION_NONCONTINUABLE; ExceptionRecord2.ExceptionRecord = ExceptionRecord; ExceptionRecord2.NumberParameters = 0; /* Raise the exception */ RtlRaiseException(&ExceptionRecord2); } /* Find out where it ends */ RegistrationFrameEnd = (ULONG_PTR)RegistrationFrame + sizeof(EXCEPTION_REGISTRATION_RECORD); /* Make sure the registration frame is located within the stack */ if ((RegistrationFrameEnd > StackHigh) || ((ULONG_PTR)RegistrationFrame < StackLow) || ((ULONG_PTR)RegistrationFrame & 0x3)) { /* Check if this happened in the DPC Stack */ if (RtlpHandleDpcStackException(RegistrationFrame, RegistrationFrameEnd, &StackLow, &StackHigh)) { /* Use DPC Stack Limits and restart */ continue; } /* Create an invalid stack exception */ ExceptionRecord2.ExceptionCode = STATUS_BAD_STACK; ExceptionRecord2.ExceptionFlags = EXCEPTION_NONCONTINUABLE; ExceptionRecord2.ExceptionRecord = ExceptionRecord; ExceptionRecord2.NumberParameters = 0; /* Raise the exception */ RtlRaiseException(&ExceptionRecord2); } else { /* Call the handler */ Disposition = RtlpExecuteHandlerForUnwind(ExceptionRecord, RegistrationFrame, Context, &DispatcherContext, RegistrationFrame-> Handler); switch(Disposition) { /* Continue searching */ case ExceptionContinueSearch: break; /* Collission */ case ExceptionCollidedUnwind : /* Get the original frame */ RegistrationFrame = DispatcherContext.RegistrationPointer; break; /* Anything else */ default: /* Set up the exception record */ ExceptionRecord2.ExceptionRecord = ExceptionRecord; ExceptionRecord2.ExceptionCode = STATUS_INVALID_DISPOSITION; ExceptionRecord2.ExceptionFlags = EXCEPTION_NONCONTINUABLE; ExceptionRecord2.NumberParameters = 0; /* Raise the exception */ RtlRaiseException(&ExceptionRecord2); break; } /* Go to the next frame */ OldFrame = RegistrationFrame; RegistrationFrame = RegistrationFrame->Next; /* Remove this handler */ RtlpSetExceptionList(OldFrame); } } /* Check if we reached the end */ if (TargetFrame == EXCEPTION_CHAIN_END) { /* Unwind completed, so we don't exit */ ZwContinue(Context, FALSE); } else { /* This is an exit_unwind or the frame wasn't present in the list */ ZwRaiseException(ExceptionRecord, Context, FALSE); } }
RtlpExecuteHandlerForUnwind 和RtlpExecuteHandlerForException 是汇编写的代码,这个函数的尾部会跳转到ExecuteHandler。
PUBLIC _RtlpExecuteHandlerForException@20 _RtlpExecuteHandlerForException@20: /* Copy the routine in EDX */ mov edx, offset _RtlpExceptionProtector /* Jump to common routine */ jmp _RtlpExecuteHandler@20 PUBLIC _RtlpExecuteHandlerForUnwind@20 _RtlpExecuteHandlerForUnwind@20: /* Copy the routine in EDX */ mov edx, offset _RtlpUnwindProtector _RtlpExecuteHandler@20: /* Save non-volatile */ push ebx push esi push edi /* Clear registers */ xor eax, eax xor ebx, ebx xor esi, esi xor edi, edi /* Call the 2nd-stage executer */ push [esp+32] push [esp+32] push [esp+32] push [esp+32] push [esp+32] call _RtlpExecuteHandler2@20 /* Restore non-volatile */ pop edi pop esi pop ebx ret 20 PUBLIC _RtlpExecuteHandler2@20 _RtlpExecuteHandler2@20: /* Set up stack frame */ push ebp mov ebp, esp /* Save the Frame */ push [ebp+12] /* Push handler address */ push edx /* Push the exception list */ push [fs:TEB_EXCEPTION_LIST] /* Link us to it */ mov [fs:TEB_EXCEPTION_LIST], esp //这里我们构造了一个nt_EXCEPTION_REGISTRATION /* Call the handler */ push [ebp+20] push [ebp+16] push [ebp+12] push [ebp+8] mov ecx, [ebp+24] call ecx /* Unlink us */ mov esp, [fs:TEB_EXCEPTION_LIST] /* Restore it */ pop [fs:TEB_EXCEPTION_LIST] /* Undo stack frame and return */ mov esp, ebp pop ebp ret 20
我们看到了,其实,当我们真正执行filter(其实是vc的运行时库函数_except_handler)之前,windows 已经为我们提前构造了一个nt_EXCEPTION_REGISTRATION, 和我们之前的vc_EXCEPTION_REGISTRATION,不同的是,在基本的EXCEPTION_REGISTRATION结构体之后,只是加了一个成员PEXCEPTION_REGISTRATION_RECORD RegistrationFrame,这个成员的意义则就是判断异常嵌套。当然,这依然没有解决问题(异常再产生异常),我们需要知道_RtlpExceptionProtector,_RtlpUnwindProtector。不过,我们这个担心有点多余,因为这个是os本身的代码,如果他自己还不能保证正确,那么后面还有什么意义呢?
_RtlpExceptionProtector: /* Assume we'll continue */ mov eax, ExceptionContinueSearch /* Put the exception record in ECX and check the Flags */ mov ecx, [esp+4] test dword ptr [ecx+EXCEPTION_RECORD_EXCEPTION_FLAGS], EXCEPTION_UNWINDING + EXCEPTION_EXIT_UNWIND jnz return /* Save the frame in ECX and Context in EDX */ mov ecx, [esp+8] mov edx, [esp+16] /* Get the nested frame */ mov eax, [ecx+8] /* Set it as the dispatcher context */ mov [edx], eax /* Return nested exception */ mov eax, ExceptionNestedException return: ret 16 _RtlpUnwindProtector: /* Assume we'll continue */ mov eax, ExceptionContinueSearch /* Put the exception record in ECX and check the Flags */ mov ecx, [esp+4] test dword ptr [ecx+EXCEPTION_RECORD_EXCEPTION_FLAGS], EXCEPTION_UNWINDING + EXCEPTION_EXIT_UNWIND jz .return /* Save the frame in ECX and Context in EDX */ mov ecx, [esp+8] mov edx, [esp+16] /* Get the nested frame */ mov eax, [ecx+8] /* Set it as the dispatcher context */ mov [edx], eax /* Return collided unwind */ mov eax, ExceptionCollidedUnwind .return: ret 16
我们看到了,当异常嵌套发生时,windows和处理之前的异常一样,依然会走这个流程。来自Matt Pietrek,之前介绍的文章。
KiUserExceptionDispatcher() RtlDispatchException() RtlpExecuteHandlerForException() ExecuteHandler() // Normally goes to __except_handler3 --------- __except_handler3() scopetable filter-expression() __global_unwind2() RtlUnwind() RtlpExecuteHandlerForUnwind() scopetable __except block()
只是不同的是,嵌套发生时,fs:[0]上的frame,已经不是我们的代码,而是nt_frame,回调函数的事情也很简单,判断异常时候是在unwind或是unwind_exit,如果不是,那么我们知道了这个是异常传递的第一次,而这个是在正常情况下,不可能发生的(正常情况指的是异常没有嵌套,执行nt_frame的只可能是第二次,也就是unwind 或是 exit_unwind,nt_frame返回ExceptionContinueSearch,让异常继续传递给我们的代码)。那么很显然,现在遇到了异常嵌套,nt_frame返回了 ExceptionNestedException,并且将frame 保存在了edx中,也就是修改了DispatcherContext,RtlpExecuteHandlerForException的第四个参数。那么当返回时,windows 就可以知道是那个frame 在处理异常的时候,干了坏事(又产生了异常)。好吧。流程又恢复一样,继续的去遍历 fs:[0],直到我们发现了这个干坏事的frame,然后我们把异常嵌套标志位去掉,ExceptionRecord->ExceptionFlags &= ~EXCEPTION_NESTED_CALL; NestedFrame = NULL。
让我们看一个简单的例子。对上一篇的Matt Pietrek的例子做了些修改。
void WalkSEHFrames( void ) { VC_EXCEPTION_REGISTRATION * pVCExcRec; printf( "\n" ); // Get a pointer to the head of the chain at FS:[0] __asm mov eax, FS:[0] __asm mov [pVCExcRec], EAX // Walk the linked list of frames. 0xFFFFFFFF indicates the end of list while ( 0xFFFFFFFF != (unsigned)pVCExcRec ) { ShowSEHFrame( pVCExcRec ); pVCExcRec = (VC_EXCEPTION_REGISTRATION *)(pVCExcRec->prev); } } EXCEPTION_DISPOSITION __cdecl _except_handler( struct _EXCEPTION_RECORD *ExceptionRecord, void * EstablisherFrame, struct _CONTEXT *ContextRecord, void * DispatcherContext ) { unsigned i; // Indicate that we made it to our exception handler printf( "Hello from an exception handler\n" ); WalkSEHFrames(); if (ExceptionRecord->ExceptionFlags & 0x10) { printf( "bad except\n" ); } else { // bad happen __asm { mov eax, 0 mov [eax], 1 } } // // Change EAX in the context record so that it points to someplace // where we can successfully write ContextRecord->Eax = (DWORD)&scratch; // Tell the OS to restart the faulting instruction return ExceptionContinueExecution; } int _tmain(int argc, _TCHAR* argv[]) { DWORD handler = (DWORD)_except_handler; __try { __asm { // Build EXCEPTION_REGISTRATION record: push handler // Address of handler function push FS:[0] // Address of previous handler mov FS:[0],ESP // Install new EXECEPTION_REGISTRATION } WalkSEHFrames(); __asm { mov eax,0 // Zero out EAX mov [eax], 1 // Write to EAX to deliberately cause a fault } printf( "After writing!\n" ); __asm { // Remove our EXECEPTION_REGISTRATION record mov eax,[ESP] // Get pointer to previous record mov FS:[0], EAX // Install previous record add esp, 8 // Clean our EXECEPTION_REGISTRATION off stack } WalkSEHFrames(); } __except(EXCEPTION_EXECUTE_HANDLER) { printf("never happen\n"); } return 0; }
产生的结果
Frame: 0022FDD8 Handler: 0118110E Prev: 0022FEC0 Scopetable: 00000000 Frame: 0022FEC0 Handler: 01181091 Prev: 0022FF10 Scopetable: 01186C80 Frame: 0022FF10 Handler: 01181096 Prev: 0022FF64 Scopetable: 81F63D92 Frame: 0022FF64 Handler: 7712D74D Prev: FFFFFFFF Scopetable: 021DFE40 Hello from an exception handler Frame: 0022FA04 Handler: 7715660D Prev: 0022FDD8 Scopetable: 0022FDD8 Frame: 0022FDD8 Handler: 0118110E Prev: 0022FEC0 Scopetable: 00000000 Frame: 0022FEC0 Handler: 01181091 Prev: 0022FF10 Scopetable: 01186C80 Frame: 0022FF10 Handler: 01181096 Prev: 0022FF64 Scopetable: 81F63D92 Frame: 0022FF64 Handler: 7712D74D Prev: FFFFFFFF Scopetable: 021DFE40 Hello from an exception handler Frame: 0022F540 Handler: 7715660D Prev: 0022FA04 Scopetable: 0022FDD8 Frame: 0022FA04 Handler: 7715660D Prev: 0022FDD8 Scopetable: 0022FDD8 Frame: 0022FDD8 Handler: 0118110E Prev: 0022FEC0 Scopetable: 00000000 Frame: 0022FEC0 Handler: 01181091 Prev: 0022FF10 Scopetable: 01186C80 Frame: 0022FF10 Handler: 01181096 Prev: 0022FF64 Scopetable: 81F63D92 Frame: 0022FF64 Handler: 7712D74D Prev: FFFFFFFF Scopetable: 021DFE40 bad except After writing! Frame: 0022FEC0 Handler: 01181091 Prev: 0022FF10 Scopetable: 01186C80 Frame: 0022FF10 Handler: 01181096 Prev: 0022FF64 Scopetable: 81F63D92 Frame: 0022FF64 Handler: 7712D74D Prev: FFFFFFFF Scopetable: 021DFE40
0x0118110E这个是我们自己的handler地址,也就是handler。0x01181091则是vc_handler的地址, 0x7715660D, 这个地址,就是我们的nt_frame的地址。0x01181096是CRT main函数时加的, 0x7712D74D 地址是KERNEL32.DLL 的 BaseProcessStart加的。
在发生异常之后,windows为了保证vc_exception_hander抛出异常可以处理,加了nt_frame,但是运行时,再次引发异常,那么则会继续走KiUserExceptionDispatcher…那么则会再加入一个nt_frame在stack上。这时异常没有继续抛出,最后,windows 会逐个卸载掉那些frame。
再搞明白这些之后,就很容易理解一开始的例子了,为什么死在栈溢出,如果我们在异常嵌套的时候,继续产生异常,那么windows会不断的去走KiUserExceptionDispatcher…而SEH的frame是建立在stack上的,那么stack overflow 实在是不可避免的事情了。所以,我们最好按照msdn上的建议,filter的代码一定要简洁(我们在遍历和unwind的时候,执行2次),而且一定不能产生任何异常,否则,后果十分严重(可能死在stack overflow)。
但是,事实上,我们却很难写出不再产生异常的代码,即使代码很简洁,而且逻辑上看上去并没有问题。那时因为我们的惯性思维停留在了像,c,c++这些高级语言上了(相对汇编)。比如下面的例子。来自《windows 核心编程》。
char g_szBuffer[100]; void FunclinRoosevelt1() { int x = 0; char *pchBuffer = NULL; __try { *pchBuffer = 'J'; x = 5 / x; } __except(OilFilter1(&pchBuffer)) { MessageBox(NULL, "An exception occurred", NULL, MB_OK); } MessageBox(NULL, "Function completed", NULL, MB_OK); } LONG OilFilter1(char **ppchBuffer) { if(*ppchBuffer == NULL) { *ppchBuffer = g_szBuffer; return(EXCEPTION_CONTINUE_EXECUTION); } return(EXCEPTION_EXECUTE_HANDLER); }
一段看似,没有问题的代码。但是这个确实是一个问题很隐晦的代码。我们看似修改了pchBuffer,使得pchBuffer 指向一个合法的地址,但是继续执行依然会有可能产生异常。原因在于,编译器有可能给我们产生如下代码,对*pchBuffer = 'J'; 来说。
MOV EAX, [pchBuffer] // Move the address into a register MOV [EAX], 'J' // Move 'J' into the address
我们只是修改了pchBuffer,并没有修改eax的值,程序并不能真正的继续执行。所以,如果想使用EXCEPTION_CONTINUE_EXECUTION,Jeffrey Richter告诉我们一定要小心,小心。但是我相信,即使这个功能很cool,没有人会愿意每次编译之后,查看下汇编代码,看看是否生成了我们想要的代码。所以,我大胆的说,想使用EXCEPTION_CONTINUE_EXECUTION,最简单的方法就是在汇编下跑,c,c++下,就不用想了。而Jeffrey Richter 告诉我们系统在处理访问违规的时候,有类似的使用,那么系统那部分的代码,也很有可能是汇编直接写的。
所以,在c++下,MS自己都劝开发者使用c++自己的异常语法,而不是直接使用SEH。这个不仅能使代码有强的移植性,而且也能避免EXCEPTION_CONTINUE_EXECUTION。
SEH就像ReactOS上写的一样,“SEH is a game which is played between OS and Compiler (Keywords: __try, __except, __finally)”。vc通过这些关键字,使得开发者只需要了解一点点知识,便可以体验到SEH的强大。当然,强大的封装之后,必然会给我们理解带来了不少困难。如果你也对这些感兴趣,那么真的可以继续下去。因为我现在所知道的有关SEH的部分仅仅是最最基础的部分,这些部分早在10几年前就已经存在。
下一篇将开始真正的接触SEH。
最后写给自己。
本来应该更详细的阐述一些细节,特别是local unwind, 他在执行我们的代码之前也构造了一个自己的frame,有兴趣的同学可以自己研究下。这个和AbnormalTermination()的实现息息相关。只是我发现vs2008 和vc6 在这上面似乎有些不同,vs2008似乎很强大的把这个完全优化掉了(也许不是因为这个原因,或是其他原因,了解一点vs的应该都知道vs这方面很强大)。在和他纠结了半个多小时后,我也实在是没有兴趣去和他比下去了。
对于像我这样长期处在user mode的开发者来说。了解到这一地步,在实现上已经是足够了。但是即使已经了解大部分的SEH核心行为后(除去安全机制,这个同样对大多数开发者是透明的),依然很难说清楚什么时候改抛出异常,什么时候该使用返回值。(唯一可以肯定的是,不能有时候返回值,有时候又抛异常 :P)
这里先记录一下自己的想法吧。当然,这里的异常主要还是SEH,c++概念不在考虑之内(即使在windows 底层实现可能会很相像和SEH)。
首先看SEH的finally,这个的确看上去是一个很美好的东东,Jeffrey Richter给了我们几点使用finally的理由。
- They simplify error processing because all cleanup is in one location and is guaranteed to execute.
- They improve program readability.
- They make code easier to maintain.
- They have minimal speed and size overhead if used correctly.
在我看来首先第一条就有问题,finally中的代码能够肯定保证执行么?显然不行,至少现在不可以。在一些严重的异常下,如stack overflow 或是进程,线程直接被Terminate。都不能直接执行。
2和3条,这个的确是完美,但是并不是非常完美,因为能够做到这一点的不仅仅是finally,使用良好的编程规范,如合理的goto语句,等等。我们依然能够做到在一个地方释放空间。来增强程序的可读性。比如pthread中的一段。
result = pthread_mutex_init (&rwl->mtxExclusiveAccess, NULL); if (result != 0) { goto FAIL0; } result = pthread_mutex_init (&rwl->mtxSharedAccessCompleted, NULL); if (result != 0) { goto FAIL1; } result = pthread_cond_init (&rwl->cndSharedAccessCompleted, NULL); if (result != 0) { goto FAIL2; }
最后一条,前半句非常对,的确SEH的机制非常迅速(相对,没有绝对),在目前看来在不发生异常的时候,我们的确很享受这个过程,但是当你知道SEH背后的安全机制之后,你可能就不会这么认为了,那可不是点点CPU周期可以搞定的,而且还有后半句 if used correctly。使用异常,那么我们需要理解更多的有关异常本身的问题,包括异常是什么?异常如何调度?什么时候效率影响大?等等问题,也会带来更多的对程序员的心智上的负担。
其次 except。同之前说的一样,由于使用异常,的确造成了非常大的知识的负担和程序运行上的负担。但是,当你去编写一个需要长期运行,而且要保证高效稳定性的程序之下。没有异常机制,实在是一件不可能的事情。当整运算一个大数据量的时候(已经算了几个小时了),若是来一点意外,总不能就推到重来计算等。而避免这些的最好的方法就是处理异常。
但是在一些情景下,我们却不能使用异常,比如在一些硬件不够高的地方,嵌入式平台等。抛出异常是被禁止的。在一些运算密集性场景,如游戏引擎上,异常依然是禁区。
但是在看到一些.net 的源代码上,比如Dictionary,我记得是抛出异常的。55,扯的实在是太远了。
之前描述的不清楚。 如果没有发生异常,SEH的机制比较迅速。只是修改了stack 上的临时变量和线程的 exceptionList(或没有修改)。
只是当抛出异常的时候,整个运行的效率才会降下来。