C++ EH Exception(0xe06d7363)----拋出過程


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM