C++ EH Exception(0xe06d7363)---捕获过程
书接上文《C++ EH Exception(0xe06d7363)----抛出过程》,下面我们讲下,VC++是如何catch到异常且处理的。
我们知道,在VC++里,C++异常实现的底层机制还是SEH,所以,我们将程序跑起来观察
上图红框框起来的部分就是编译器安装了异常处理链,且将其设置位最后一个节点,也就如下结构
struct VC_EXCEPTION_REGISTRATION { VC_EXCEPTION_REGISTRATION* prev; //前一个结构体的指针 FARPROC handler; //永远指向_exception_handler4回调函数 scopetable_entry* scopetable;//指向scpoetable数组的指针 int _index; //有的书上也叫tryLevel。scopetable项的当前项 DWORD _ebp; //当前ebp值,用于访问各成员 }
那我们就知道,编译器通过push 0C852F8h 给我们注册了异常处理回调,我们看看 0C852F8处是个什么东西
可以看到,0x0C852F8处是函数__ehhandler$_main,那么__ehhandler$_main干了些什么呢:
首先,是将FuncInfo信息提指针赋值给eax,这个结构如下
这个结构表示的是当前处于的函数的信息,收集发生异常时必须要完成的操作的所有信息。异常处理从funcinfo结构里知道了catchblock的参数,也就是catch块的地址。对于每个函数,VC还收集一份它里面的try语句的位置信息,catch语句能catch住的object type信息,以及每个可能发生异常的情况下要完成的object destructor调用的信息。这些信息构成了function info。
然后jmp 到___CxxFrameHandler3 函数,这个函数如下:
extern "C" _CRTIMP __declspec(naked) EXCEPTION_DISPOSITION __cdecl __CxxFrameHandler3( /* EAX=FuncInfo *pFuncInfo, // Static information for this frame */ EHExceptionRecord *pExcept, // Information for this exception EHRegistrationNode *pRN, // Dynamic information for this frame void *pContext, // Context info (we don't care what's in it) DispatcherContext *pDC // More dynamic info for this frame (ignored on Intel) ) { FuncInfo *pFuncInfo; EXCEPTION_DISPOSITION result; __asm { // // Standard function prolog // push ebp mov ebp, esp sub esp, __LOCAL_SIZE push ebx push esi push edi cld // A bit of paranoia -- Our code-gen assumes this // // Save the extra parameter // mov pFuncInfo, eax } EHTRACE_ENTER_FMT1("pRN = 0x%p", pRN); result = __InternalCxxFrameHandler( pExcept, pRN, pContext, pDC, pFuncInfo, 0, NULL, FALSE ); EHTRACE_HANDLER_EXIT(result); __asm { pop edi pop esi pop ebx mov eax, result mov esp, ebp pop ebp ret 0 } }
在这个函数里,通过mov pFuncInfo, eax将funcinfo赋值给本地变量pFuncInfo,传递给__InternalCxxFrameHandler函数。
函数__InternalCxxFrameHandler的代码如下
extern "C" EXCEPTION_DISPOSITION __cdecl __InternalCxxFrameHandler( EHExceptionRecord *pExcept, // Information for this exception EHRegistrationNode *pRN, // Dynamic information for this frame CONTEXT *pContext, // Context info DispatcherContext *pDC, // Context within subject frame FuncInfo *pFuncInfo, // Static information for this frame int CatchDepth, // How deeply nested are we? EHRegistrationNode *pMarkerRN, // Marker node for when checking inside // catch block BOOLEAN recursive // Are we handling a translation? ) { EHTRACE_ENTER_FMT2("%s, pRN = 0x%p", IS_UNWINDING(PER_FLAGS(pExcept)) ? "Unwinding" : "Searching", pRN); if ((cxxReThrow == false) && (PER_CODE(pExcept) != EH_EXCEPTION_NUMBER) && #if defined(_M_X64) || defined(_M_ARM) /*IFSTRIP=IGN*/ /* On the 64 bit/ARM platforms, ExceptionCode maybe set to STATUS_UNWIND_CONSOLIDATE when called from _UnwindNestedFrames during Logical Unwind. _UnwindNestedFrames will also set EH_MAGIC_NUMBER1 in the 8 element */ (!((PER_CODE(pExcept) == STATUS_UNWIND_CONSOLIDATE) && (PER_NPARAMS(pExcept) == 15) && (PER_EXCEPTINFO(pExcept)[8] == EH_MAGIC_NUMBER1))) && #endif (PER_CODE(pExcept) != STATUS_LONGJUMP) && (FUNC_MAGICNUM(*pFuncInfo) >= EH_MAGIC_NUMBER3) && ((FUNC_FLAGS(*pFuncInfo) & FI_EHS_FLAG) != 0)) { /* * This function was compiled /EHs so we don't need to do anything in * this handler. */ return ExceptionContinueSearch; } if (IS_UNWINDING(PER_FLAGS(pExcept))) { // We're at the unwinding stage of things. Don't care about the // exception itself. (Check this first because it's easier) if (FUNC_MAXSTATE(*pFuncInfo) != 0 && CatchDepth == 0) { // Only unwind if there's something to unwind // AND we're being called through the primary RN. #if defined(_M_X64) || defined(_M_ARM) /*IFSTRIP=IGN*/ if (IS_TARGET_UNWIND(PER_FLAGS(pExcept)) && PER_CODE(pExcept) == STATUS_LONGJUMP) { __ehstate_t target_state = __StateFromIp(pFuncInfo, pDC, #if defined(_M_X64) pContext->Rip #elif defined(_M_ARM) pContext->Pc #endif ); DASSERT(target_state >= EH_EMPTY_STATE && target_state < FUNC_MAXSTATE(*pFuncInfo)); __FrameUnwindToState(pRN, pDC, pFuncInfo, target_state); EHTRACE_HANDLER_EXIT(ExceptionContinueSearch); return ExceptionContinueSearch; } else if(IS_TARGET_UNWIND(PER_FLAGS(pExcept)) && PER_CODE(pExcept) == STATUS_UNWIND_CONSOLIDATE) { PEXCEPTION_RECORD pSehExcept = (PEXCEPTION_RECORD)pExcept; __ehstate_t target_state = (__ehstate_t)pSehExcept->ExceptionInformation[3]; DASSERT(target_state >= EH_EMPTY_STATE && target_state < FUNC_MAXSTATE(*pFuncInfo)); __FrameUnwindToState((EHRegistrationNode *)pSehExcept->ExceptionInformation[1], pDC, pFuncInfo, target_state); EHTRACE_HANDLER_EXIT(ExceptionContinueSearch); return ExceptionContinueSearch; } #endif // defined(_M_X64) || defined(_M_ARM) __FrameUnwindToEmptyState(pRN, pDC, pFuncInfo); } EHTRACE_HANDLER_EXIT(ExceptionContinueSearch); return ExceptionContinueSearch; // I don't think this value matters } else if (FUNC_NTRYBLOCKS(*pFuncInfo) != 0 // // If the function has no try block, we still want to call the // frame handler if there is an exception specification // || (FUNC_MAGICNUM(*pFuncInfo) >= EH_MAGIC_NUMBER2 && FUNC_PESTYPES(pFuncInfo) != NULL)) { // NT is looking for handlers. We've got handlers. // Let's check this puppy out. Do we recognize it? int (__cdecl *pfn)(...); if (PER_CODE(pExcept) == EH_EXCEPTION_NUMBER && PER_NPARAMS(pExcept) >= 3 && PER_MAGICNUM(pExcept) > EH_MAGIC_NUMBER3 && (pfn = THROW_FORWARDCOMPAT(*PER_PTHROW(pExcept))) != NULL) { // Forward compatibility: The thrown object appears to have been // created by a newer version of our compiler. Let that version's // frame handler do the work (if one was specified). #if defined(_DEBUG) || defined(_SYSCRT_DEBUG) if (_ValidateExecute((FARPROC)pfn)) { #endif EXCEPTION_DISPOSITION result = (EXCEPTION_DISPOSITION)pfn(pExcept, pRN, pContext, pDC, pFuncInfo, CatchDepth, pMarkerRN, recursive); EHTRACE_HANDLER_EXIT(result); return result; #if defined(_DEBUG) || defined(_SYSCRT_DEBUG) } else { _inconsistency(); // Does not return; TKB } #endif } else { // Anything else: we'll handle it here. FindHandler(pExcept, pRN, pContext, pDC, pFuncInfo, recursive, CatchDepth, pMarkerRN); } // If it returned, we didn't have any matches. } // NT was looking for a handler // We had nothing to do with it or it was rethrown. Keep searching. EHTRACE_HANDLER_EXIT(ExceptionContinueSearch); return ExceptionContinueSearch; } // InternalCxxFrameHandler
而__InternalCxxFrameHandler里调用FindHandler来寻找最终的catch块,FindHandler函数代码如下:
static void FindHandler( EHExceptionRecord *pExcept, // Information for this (logical) // exception EHRegistrationNode *pRN, // Dynamic information for subject frame CONTEXT *pContext, // Context info DispatcherContext *pDC, // Context within subject frame FuncInfo *pFuncInfo, // Static information for subject frame BOOLEAN recursive, // TRUE if we're handling the // translation int CatchDepth, // Level of nested catch that is being // checked EHRegistrationNode *pMarkerRN // Extra marker RN for nested catch // handling ) { EHTRACE_ENTER; BOOLEAN IsRethrow = FALSE; BOOLEAN gotMatch = FALSE; // Get the current state (machine-dependent) #if defined(_M_X64) || defined(_M_ARM_NT) /*IFSTRIP=IGN*/ __ehstate_t curState = __StateFromControlPc(pFuncInfo, pDC); EHRegistrationNode EstablisherFrame; /* * Here We find what is the actual State of current function. The way we * do this is first get State from ControlPc. * * Remember we have __GetUnwindTryBlock to remember the last State for which * Exception was handled and __GetCurrentState for retriving the current * state of the function. Please Note that __GetCurrentState is used * primarily for unwinding purpose. * * Also remember that all the catch blocks act as funclets. This means that * ControlPc for all the catch blocks are different from ControlPc of parent * catch block or function. * * take a look at this example * try { * // STATE1 = 1 * try { * // STATE2 * // THROW * } catch (...) { // CatchB1 * // STATE3 * // RETHROW OR NEW THROW * } * } catch (...) { // CatchB2 * } * * If we have an exception comming from STATE3, the FindHandler will be * called for CatchB1, at this point we do the test which State is our * real state, curState from ControlPc or state from __GetUnwindTryBlock. * Since curState from ControlPc is greater, we know that real State is * curState from ControlPc and thus we update the UnwindTryBlockState. * * On further examination, we found out that there is no handler within * this catch block, we return without handling the exception. For more * info on how we determine if we have handler, have a look at * __GetRangeOfTrysToCheck. * * Now FindHandler will again be called for parent function. Here again * we test which is real State, state from ControlPc or State from * __GetUnwindTryBlock. This time state from __GetUnwindTryBlock is correct. * * Also look at code in __CxxCallCatchBlock, you will se that as soon as we get * out of last catch block, we reset __GetUnwindTryBlock state to -1. */ _GetEstablisherFrame(pRN, pDC, pFuncInfo, &EstablisherFrame); if (curState > __GetUnwindTryBlock(pRN, pDC, pFuncInfo)) { __SetState(&EstablisherFrame, pDC, pFuncInfo, curState); __SetUnwindTryBlock(pRN, pDC, pFuncInfo, /*curTry*/ curState); } else { curState = __GetUnwindTryBlock(pRN, pDC, pFuncInfo); } #else __ehstate_t curState = GetCurrentState(pRN, pDC, pFuncInfo); #endif DASSERT(curState >= EH_EMPTY_STATE && curState < FUNC_MAXSTATE(*pFuncInfo)); // Check if it's a re-throw. Use the exception we stashed away if it is. if (PER_IS_MSVC_EH(pExcept) && PER_PTHROW(pExcept) == NULL) { if (_pCurrentException == NULL) { // Oops! User re-threw a non-existant exception! Let it propogate. EHTRACE_EXIT; return; } pExcept = _pCurrentException; pContext = _pCurrentExContext; IsRethrow = TRUE; #if _EH_RELATIVE_OFFSETS /*IFSTRIP=IGN*/ _SetThrowImageBase((ptrdiff_t)pExcept->params.pThrowImageBase); #endif DASSERT(_ValidateRead(pExcept)); DASSERT(!PER_IS_MSVC_EH(pExcept) || PER_PTHROW(pExcept) != NULL); // // We know it is a rethrow -- did we come here as a result of an // exception re-thrown from CallUnexpected() ? // if( _pCurrentFuncInfo != NULL ) { ESTypeList* pCurrentFuncInfo = _pCurrentFuncInfo; // remember it in a local variable _pCurrentFuncInfo = NULL; // and reset it immediately -- so we don't forget to do it later // Does the exception thrown by CallUnexpected belong to the exception specification? if( IsInExceptionSpec(pExcept, pCurrentFuncInfo) ) { // Yes it does -- so "continue the search for another handler at the call of the function // whose exception-specification was violated" ; } else { // Nope, it does not. Is std::bad_exception allowed by the spec? if( Is_bad_exception_allowed(pCurrentFuncInfo) ) { // yup -- so according to the standard, we need to replace the thrown // exception by an implementation-defined object of the type std::bad_exception // and continue the search for another handler at the call of the function // whose exception-specification was violated. // Just throw bad_exception -- we will then come into FindHandler for the third time -- // but make sure we will not get here again __DestructExceptionObject(pExcept, TRUE); // destroy the original object throw std::bad_exception(); } else { terminate(); } } } } if (PER_IS_MSVC_EH(pExcept)) { // Looks like it's ours. Let's see if we have a match: // // First, determine range of try blocks to consider: // Only try blocks which are at the current catch depth are of interest. unsigned curTry; unsigned end; if( FUNC_NTRYBLOCKS(*pFuncInfo) > 0 ) { TryBlockMapEntry *pEntry = __GetRangeOfTrysToCheck(pRN, pFuncInfo, CatchDepth, curState, &curTry, &end, pDC); // Scan the try blocks in the function: for (; curTry < end; curTry++, pEntry++) { HandlerType *pCatch; #if _EH_RELATIVE_OFFSETS __int32 const *ppCatchable; #else CatchableType * const *ppCatchable; #endif CatchableType *pCatchable; int catches; int catchables; if (TBME_LOW(*pEntry) > curState || curState > TBME_HIGH(*pEntry)) { continue; } // Try block was in scope for current state. Scan catches for this // try: pCatch = TBME_PCATCH(*pEntry, 0); for (catches = TBME_NCATCHES(*pEntry); catches > 0; catches--, pCatch++) { // Scan all types that thrown object can be converted to: ppCatchable = THROW_CTLIST(*PER_PTHROW(pExcept)); for (catchables = THROW_COUNT(*PER_PTHROW(pExcept)); catchables > 0; catchables--, ppCatchable++) { #if _EH_RELATIVE_OFFSETS pCatchable = (CatchableType *)(_GetThrowImageBase() + *ppCatchable); #else pCatchable = *ppCatchable; #endif if (!__TypeMatch(pCatch, pCatchable, PER_PTHROW(pExcept))) { continue; } // OK. We finally found a match. Activate the catch. If // control gets back here, the catch did a re-throw, so // keep searching. gotMatch = TRUE; CatchIt(pExcept, pRN, pContext, pDC, pFuncInfo, pCatch, pCatchable, pEntry, CatchDepth, pMarkerRN, IsRethrow #if defined (_M_X64) || defined(_M_ARM_NT) , recursive #endif ); goto NextTryBlock; } // Scan posible conversions } // Scan catch clauses NextTryBlock: ; } // Scan try blocks } // if FUNC_NTRYBLOCKS( pFuncInfo ) > 0 #if defined(_DEBUG) || defined(_SYSCRT_DEBUG) else { // // This can only happen if the function has an exception specification // but no try/catch blocks // DASSERT( FUNC_MAGICNUM(*pFuncInfo) >= EH_MAGIC_NUMBER2 ); DASSERT( FUNC_PESTYPES(pFuncInfo) != NULL ); } #endif #if defined(_M_IX86) if (recursive) { // // A translation was provided, but this frame didn't catch it. // Destruct the translated object before returning; if destruction // raises an exception, terminate. // // This is not done for Win64 platforms. On those, the translated // object is destructed in __CxxCallCatchBlock. // __DestructExceptionObject(pExcept, TRUE); } #endif #if (!defined(_M_ARM) || defined(_M_ARM_NT)) // // We haven't found the match -- let's look at the exception spec and see if our try // matches one of the listed types. // if( !gotMatch && FUNC_MAGICNUM(*pFuncInfo) >= EH_MAGIC_HAS_ES && FUNC_PESTYPES(pFuncInfo) != NULL ) { if( !IsInExceptionSpec(pExcept, FUNC_PESTYPES(pFuncInfo)) ) { // Nope, it does not. Call unexpected // // We must unwind the stack before calling unexpected -- this makes it work // as if it were inside catch(...) clause // #if defined (_M_X64) || defined(_M_ARM_NT) /*IFSTRIP=IGN*/ EHRegistrationNode *pEstablisher = pRN; EHRegistrationNode EstablisherFramePointers; pEstablisher = _GetEstablisherFrame(pRN, pDC, pFuncInfo, &EstablisherFramePointers); PVOID pExceptionObjectDestroyed = NULL; _UnwindNestedFrames(pRN, pExcept, pContext, pEstablisher, NULL, -1, pFuncInfo, pDC, recursive ); #else EHExceptionRecord *pSaveException = _pCurrentException; CONTEXT *pSaveExContext = _pCurrentExContext; _pCurrentException = pExcept; _pCurrentExContext = pContext; if (pMarkerRN == NULL) { _UnwindNestedFrames(pRN, pExcept); } else { _UnwindNestedFrames(pMarkerRN, pExcept); } __FrameUnwindToEmptyState(pRN, pDC, pFuncInfo); CallUnexpected(FUNC_PESTYPES(pFuncInfo)); _pCurrentException = pExcept; _pCurrentExContext = pContext; #endif } } #endif // !defined(_M_ARM) } // It was a C++ EH exception else { // Not ours. But maybe someone told us how to make it ours. if( FUNC_NTRYBLOCKS(*pFuncInfo) > 0 ) { if (!recursive) { FindHandlerForForeignException(pExcept, pRN, pContext, pDC, pFuncInfo, curState, CatchDepth, pMarkerRN); } else { // We're recursive, and the exception wasn't a C++ EH! // Translator threw something uninteligable. // Two choices here: we could let the new exception take over, or we could abort. We abort. terminate(); } } } // It wasn't our exception DASSERT( _pCurrentFuncInfo == NULL ); // never leave it initialized with something EHTRACE_EXIT; }
在FindHandler函数里找到合适的catch块后,就调用CatchIt函数,代码如下:
//////////////////////////////////////////////////////////////////////////////// // // CatchIt - A handler has been found for the thrown type. Do the work to // transfer control. // // Description: // Builds the catch object // Unwinds the stack to the point of the try // Calls the address of the handler (funclet) with the frame set up for that // function but without resetting the stack. // Handler funclet returns address to continue execution, or NULL if the // handler re-threw ("throw;" lexically in handler) // If the handler throws an EH exception whose exception info is NULL, then // it's a re-throw from a dynamicly enclosed scope. // // It is an open question whether the catch object is built before or after the local unwind. // // Returns: // No return value. Returns iff handler re-throws. static void CatchIt( EHExceptionRecord *pExcept, // The exception thrown EHRegistrationNode *pRN, // Dynamic info of function with catch CONTEXT *pContext, // Context info DispatcherContext *pDC, // Context within subject frame FuncInfo *pFuncInfo, // Static info of function with catch HandlerType *pCatch, // The catch clause selected CatchableType *pConv, // The rules for making the conversion TryBlockMapEntry *pEntry, // Description of the try block int CatchDepth, // How many catches are we nested in? EHRegistrationNode *pMarkerRN, // Special node if nested in catch BOOLEAN IsRethrow // Is this a rethrow ? #if defined(_M_X64) || defined(_M_ARM_NT) /*IFSTRIP=IGN*/ , BOOLEAN recursive #endif // defined(_POWERPC) ) { EHTRACE_ENTER_FMT1("Catching object @ 0x%p", PER_PEXCEPTOBJ(pExcept)); EHRegistrationNode *pEstablisher = pRN; #if defined(_M_X64) || defined(_M_ARM) /*IFSTRIP=IGN*/ EHRegistrationNode EstablisherFramePointers; pEstablisher = _GetEstablisherFrame(pRN, pDC, pFuncInfo, &EstablisherFramePointers); #else void *continuationAddress; #endif // defined(_POWERPC) // Copy the thrown object into a buffer in the handler's stack frame, // unless the catch was by elipsis (no conversion) OR the catch was by // type without an actual 'catch object'. if (pConv != NULL) { __BuildCatchObject(pExcept, pEstablisher, pCatch, pConv); } // Unwind stack objects to the entry of the try that caught this exception. #if defined(_M_X64) || defined(_M_ARM) /*IFSTRIP=IGN*/ // This call will never return. This call will end up calling CxxCallCatchBlock // through RtlUnwind (STATUS_CONSULIDATE_FRAMES) mechanism. _UnwindNestedFrames(pRN, pExcept, pContext, pEstablisher, __GetAddress(HT_HANDLER(*pCatch), pDC), TBME_LOW(*pEntry), pFuncInfo, pDC, #if defined(_M_ARM) && !defined(_M_ARM_NT) FALSE #else recursive #endif ); #else if (pMarkerRN == NULL) { _UnwindNestedFrames(pRN, pExcept); } else { _UnwindNestedFrames(pMarkerRN, pExcept); } __FrameUnwindToState(pEstablisher, pDC, pFuncInfo, TBME_LOW(*pEntry)); // Call the catch. Separated out because it introduces a new registration // node. EHTRACE_FMT2("Move from state %d to state %d", __GetUnwindState(pRN, pDC, pFuncInfo), TBME_HIGH(*pEntry) + 1); SetState(pRN, pDC, pFuncInfo, TBME_HIGH(*pEntry) + 1); continuationAddress = CallCatchBlock(pExcept, pEstablisher, pContext, pFuncInfo, __GetAddress(HT_HANDLER(*pCatch), pDC), CatchDepth, 0x100); // Transfer control to the continuation address. If no continuation then // it's a re-throw, so return. if (continuationAddress != NULL) { _JumpToContinuation(continuationAddress, pRN); // No return. } EHTRACE_EXIT; #endif }
在CatchIt函数里调用了CallCatchBlock函数,代码如下:
//////////////////////////////////////////////////////////////////////////////// // // CallCatchBlock - continuation of CatchIt. // // This is seperated from CatchIt because it needs to introduce an SEH/EH frame // in case the catch block throws. This frame cannot be added until unwind of // nested frames has been completed (otherwise this frame would be the first // to go). static void *CallCatchBlock( EHExceptionRecord *pExcept, // The exception thrown EHRegistrationNode *pRN, // Dynamic info of function with catch CONTEXT *pContext, // Context info FuncInfo *pFuncInfo, // Static info of function with catch void *handlerAddress, // Code address of handler int CatchDepth, // How deeply nested in catch blocks // are we? unsigned long NLGCode // NLG destination code ) { EHTRACE_ENTER; // Address where execution resumes after exception handling completed. // Initialized to non-NULL (value doesn't matter) to distinguish from // re-throw in __finally. void *continuationAddress = handlerAddress; BOOL ExceptionObjectDestroyed = FALSE; // The stack pointer at entry to the try must be saved, in case there is // another try inside this catch. We'll restore it on our way out. void *saveESP = PRN_STACK(pRN); // Push this catch block's frame info on a linked list FRAMEINFO FrameInfo; FRAMEINFO *pFrameInfo = _CreateFrameInfo(&FrameInfo, PER_PEXCEPTOBJ(pExcept)); // Save the current exception in case of a rethrow. Save the previous value // on the stack, to be restored when the catch exits. EHExceptionRecord *pSaveException = _pCurrentException; CONTEXT *pSaveExContext = _pCurrentExContext; _pCurrentException = pExcept; _pCurrentExContext = pContext; __try { __try { // Execute the handler as a funclet, whose return value is the // address to resume execution. continuationAddress = _CallCatchBlock2(pRN, pFuncInfo, handlerAddress, CatchDepth, NLGCode); } __except(EHTRACE_EXCEPT(ExFilterRethrow(exception_info()))) { cxxReThrow=false; // Here we are exiting the catch block on rethrow out of this // catch block. To keep the order of destruction and construction // same when the the rethrow was from function or was inline, here // we unwind to the parent state for this catch. UnwindMapEntry *pUnwindMap = pFuncInfo->pUnwindMap; int cState = GetCurrentState(pRN, handlerAddress, pFuncInfo); TryBlockMapEntry *pTryBlockMap = pFuncInfo->pTryBlockMap; unsigned int i; for (i = 0; i < pFuncInfo->nTryBlocks; i++) { if (cState > pTryBlockMap[i].tryHigh && cState <= pTryBlockMap[i].catchHigh) { cState = pTryBlockMap[i].tryHigh +1; cState = pUnwindMap[cState].toState; break; } } __FrameUnwindToState(pRN, NULL, pFuncInfo, cState); // If the handler threw a typed exception without exception info or // exception object, then it's a re-throw, so return. Otherwise // it's a new exception, which takes precedence over this one. continuationAddress = NULL; } } __finally { EHTRACE_SAVE_LEVEL; EHTRACE_FMT1("Executing __finally, %snormal termination", _abnormal_termination() ? "ab" : ""); // Restore the saved stack pointer, so the stack can be reset when // we're done. PRN_STACK(pRN) = saveESP; // Pop this catch block's frame info _FindAndUnlinkFrame(pFrameInfo); // Restore the 'current exception' for a possibly enclosing catch _pCurrentException = pSaveException; _pCurrentExContext = pSaveExContext; // Destroy the original exception object if we're not exiting on a // re-throw and the object isn't also in use by a more deeply nested // catch. Note that the catch handles destruction of its parameter. if (PER_IS_MSVC_EH(pExcept) && !ExceptionObjectDestroyed && continuationAddress != NULL && _IsExceptionObjectToBeDestroyed(PER_PEXCEPTOBJ(pExcept)) ) { __DestructExceptionObject(pExcept, abnormal_termination()); } EHTRACE_RESTORE_LEVEL(!!_abnormal_termination()); } EHTRACE_EXIT; return continuationAddress; }
在CallCatchBlock又调用_CallCatchBlock2函数,代码如下:
void *_CallCatchBlock2( EHRegistrationNode *pRN, // Dynamic info of function with catch FuncInfo *pFuncInfo, // Static info of function with catch void *handlerAddress, // Code address of handler int CatchDepth, // How deeply nested in catch blocks are we? unsigned long NLGCode ) { EHTRACE_ENTER; // // First, create and link in our special guard node: // CatchGuardRN CGRN = { NULL, (void*)CatchGuardHandler, __security_cookie ^ (UINT_PTR)&CGRN, pFuncInfo, pRN, CatchDepth + 1 #if defined(ENABLE_EHTRACE) , __ehtrace_level #endif }; __asm { mov eax, FS:[0] // Fetch frame list head mov CGRN.pNext, eax // Link this node in lea eax, CGRN // Put this node at the head mov FS:[0], eax } // // Call the catch // void *continuationAddress = _CallSettingFrame( handlerAddress, pRN, NLGCode ); // // Unlink our registration node // __asm { mov eax, CGRN.pNext // Get parent node mov FS:[0], eax // Put it at the head } EHTRACE_EXIT; return continuationAddress; }
在_CallCatchBlock2函数调用_CallSettingFrame函数来实现代码如下
_CallSettingFrame proc stdcall, funclet:IWORD, pRN:IWORD, dwInCode:DWORD ; FPO = 0 dwords locals allocated in prolog ; 3 dword parameters ; 8 bytes in prolog ; 4 registers saved (includes locals to work around debugger bug) ; 1 EBP is used ; 0 frame type = FPO .FPO (0,3,8,4,1,0) sub esp,4 push ebx push ecx mov eax,pRN add eax,0Ch ; sizeof(EHRegistrationNode) -- assumed to equal 0Ch mov dword ptr [ebp-4],eax mov eax,funclet push ebp ; Save our frame pointer push dwInCode mov ecx,dwInCode mov ebp,dword ptr [ebp-4] ; Load target frame pointer call _NLG_Notify1 ; Notify debugger push esi push edi call eax ; Call the funclet _NLG_Return:: pop edi pop esi mov ebx,ebp pop ebp mov ecx,dwInCode push ebp mov ebp,ebx cmp ecx, 0100h jne _NLG_Continue mov ecx, 02h _NLG_Continue: push ecx call _NLG_Notify1 ; Notify debugger yet again pop ebp ; Restore our frame pointer pop ecx pop ebx ret 0Ch _CallSettingFrame ENDP END
最终在_CallSettingFrame函数里通过 call eax 执行了catch块里的语句
整个捕获过程的调用链如下: