C++ EH Exception是Windows系統VC++里對c++語言的throw的分類和定義,它的代碼就是0xe06d7363。在VC++里其本質也是SEH結構化異常機制。在我們分析用戶崩潰的例子中經常會遇到它。一般情況下,遇到它,是我們代碼里用throw拋出異常后沒有處理導致程序崩潰。下面分析一下它的原理。
我們借助一段代碼來跟蹤和分析
class MyException { public: int nErr; char *szMessage; public: MyException(void) :nErr(0) , szMessage(NULL) { } MyException(int nerr,char *szMess) :nErr(nerr) , szMessage(szMess) { } ~MyException(void) { } }; int _tmain(int argc, _TCHAR* argv[]) { try
{
MyException me(1, "test exception");
throw me;
}
catch (MyException me1)
{
printf("err=%s\n",me1.szMessage);
} }
將上述代碼在VS2013里編譯調試,轉到匯編
91: try 92: { 01361797 mov dword ptr [ebp-4],0 93: MyException me(1, "test exception"); 0136179E push 1367858h 013617A3 push 1 013617A5 lea ecx,[ebp-1Ch] 013617A8 call MyException::MyException (013610D2h) 013617AD mov dword ptr [ebp-104h],eax 013617B3 mov byte ptr [ebp-4],1 94: throw me; 013617B7 mov eax,dword ptr [ebp-1Ch] 013617BA mov dword ptr [ebp-0FCh],eax 013617C0 mov ecx,dword ptr [ebp-18h] 013617C3 mov dword ptr [ebp-0F8h],ecx 013617C9 push 1369084h 013617CE lea edx,[ebp-0FCh] 013617D4 push edx 013617D5 call __CxxThrowException@8 (0136111Dh) 95: }
我們可以看到,throw 首先通過下面的代碼
013617B7 mov eax,dword ptr [ebp-1Ch] 013617BA mov dword ptr [ebp-0FCh],eax 013617C0 mov ecx,dword ptr [ebp-18h] 013617C3 mov dword ptr [ebp-0F8h],ecx
復制了一份異常對象MyException me,然后取了這份拷貝的地址作為參數傳給了__CxxThrowException函數,同時將異常類型信息也傳遞了過去
013617CE lea edx,[ebp-0FCh] 013617D4 push edx
接着調用了__CxxThrowException函數,我們進入看看
下面是改函數的代碼
///////////////////////////////////////////////////////////////////////////// // // _CxxThrowException - implementation of 'throw' // // Description: // Builds the NT Exception record, and calls the NT runtime to initiate // exception processing. // // Why is pThrowInfo defined as _ThrowInfo? Because _ThrowInfo is secretly // snuck into the compiler, as is the prototype for _CxxThrowException, so // we have to use the same type to keep the compiler happy. // // Another result of this is that _CRTIMP can't be used here. Instead, we // synthesisze the -export directive below. // // Returns: // NEVER. (until we implement resumable exceptions, that is) // // We want double underscore for CxxThrowException for ARM CE only __declspec(noreturn) extern "C" void __stdcall #if !defined(_M_ARM) || defined(_M_ARM_NT) _CxxThrowException( #else __CxxThrowException( #endif void* pExceptionObject, // The object thrown _ThrowInfo* pThrowInfo // Everything we need to know about it ) { EHTRACE_ENTER_FMT1("Throwing object @ 0x%p", pExceptionObject); static const EHExceptionRecord ExceptionTemplate = { // A generic exception record EH_EXCEPTION_NUMBER, // Exception number EXCEPTION_NONCONTINUABLE, // Exception flags (we don't do resume) NULL, // Additional record (none) NULL, // Address of exception (OS fills in) EH_EXCEPTION_PARAMETERS, // Number of parameters { EH_MAGIC_NUMBER1, // Our version control magic number NULL, // pExceptionObject NULL, #if _EH_RELATIVE_OFFSETS NULL // Image base of thrown object #endif } // pThrowInfo }; EHExceptionRecord ThisException = ExceptionTemplate; // This exception ThrowInfo* pTI = (ThrowInfo*)pThrowInfo; if (pTI && (THROW_ISWINRT( (*pTI) ) ) ) { ULONG_PTR *exceptionInfoPointer = *reinterpret_cast<ULONG_PTR**>(pExceptionObject); exceptionInfoPointer--; // The pointer to the ExceptionInfo structure is stored sizeof(void*) infront of each WinRT Exception Info. WINRTEXCEPTIONINFO** ppWei = reinterpret_cast<WINRTEXCEPTIONINFO**>(exceptionInfoPointer); pTI = (*ppWei)->throwInfo; (*ppWei)->PrepareThrow( ppWei ); } // // Fill in the blanks: // ThisException.params.pExceptionObject = pExceptionObject; ThisException.params.pThrowInfo = pTI; #if _EH_RELATIVE_OFFSETS PVOID ThrowImageBase = RtlPcToFileHeader((PVOID)pTI, &ThrowImageBase); ThisException.params.pThrowImageBase = ThrowImageBase; #endif // // If the throw info indicates this throw is from a pure region, // set the magic number to the Pure one, so only a pure-region // catch will see it. // // Also use the Pure magic number on Win64 if we were unable to // determine an image base, since that was the old way to determine // a pure throw, before the TI_IsPure bit was added to the FuncInfo // attributes field. // if (pTI != NULL) { if (THROW_ISPURE(*pTI)) { ThisException.params.magicNumber = EH_PURE_MAGIC_NUMBER1; } #if _EH_RELATIVE_OFFSETS else if (ThrowImageBase == NULL) { ThisException.params.magicNumber = EH_PURE_MAGIC_NUMBER1; } #endif } // // Hand it off to the OS: // EHTRACE_EXIT; #if defined(_M_X64) && defined(_NTSUBSET_) RtlRaiseException( (PEXCEPTION_RECORD) &ThisException ); #else RaiseException( ThisException.ExceptionCode, ThisException.ExceptionFlags, ThisException.NumberParameters, (PULONG_PTR)&ThisException.params ); #endif }
可以看到,這個函數首先是創建了一個EHExceptionRecord 對象,其實對應的就是 SEH里的結構EXCEPTION_RECORD,並且給這個結構成員賦值。
在這里通過如下代碼,0xe06d7363就賦值給ThisException.ExceptionCode
還有就是將ThrowInfo賦值給EHExceptionRecordThisException.params.pThrowInfo。_ThrowInfo 結構體定義如下:
typedef const struct _s__ThrowInfo { unsigned int attributes; _PMFN pmfnUnwind; int (__cdecl*pForwardCompat)(...); _CatchableTypeArray *pCatachableTypeArray; } _ThrowInfo;
結構體中重要的成員是_CatchableTypeArray。它包含了程序運行時拋出對象的類新信息(RTTI).
如果你的程序運行時拋出一個my_exception類型的對象,那么拋出的數據參數pCatchableTypeArray包含了兩個重要子數據信息。一個是typeid(my_exception),另外一個是typeid(std::exception)。
在我們的例子里
緊接着就調用RaiseException函數進入了異常的分發過程。
綜上,在C++ EH Exception 的異常里,EXCEPTION_RECORD結構填充如下:
ExceptionAddress: 異常地址 76d018a2 (KERNELBASE!RaiseException+0x00000062)
ExceptionCode: 異常代碼 e06d7363 (C++ EH exception)
ExceptionFlags: 標志 00000001
NumberParameters: 3 or 4 64位時是4
Parameter[0]: 19930520//魔數
Parameter[1]: 00d5f690// 拋出的異常對象指針
Parameter[2]: 00989084// 異常對象類型信息
Parameter[3]: 0000000010000000 // 64位下模塊句柄HINSTANCE