c++visual-studioexceptionx86-64rtti

C++ RTTI in a Windows 64-bit VectoredExceptionHandler, MS Visual Studio 2015


I'm working on small Windows Exception handling engine trying to gather maximum information from the system, including C++ exceptions RTTI.

In a 32-bit VectoredExceptionHandler compiled by MSVS 2015, I successfully can obtain std::type_info pointer to RTTI of the type being thrown. It can easily be found in ((_ThrowInfo*) ExceptionPointers->ExceptionRecord->ExceptionInformation[2])->pCatchableTypeArray->arrayOfCatchableTypes[0] (see classic article of Raymond Chen, some definitions from MS's ehdata.h file and many others). This method is based on fetching pCatchableTypeArray member of MSVC built-in _ThrowInfo structure data which is built by the compiler.

But in 64-bit environment, _ThrowInfo contains no direct RTTI: unfortunately, pCatchableTypeArray is NULL. In disassembly window, I see that it is NULL even before a call to _CxxThrowException, main MS throw-handler. I searched through a number of articles concerning new 64-bit exception-handling mechanism used in MSVC, but there is no information on RTTI. But maybe I missed something.

Are there any ways to obtain std::type_info (or simply type name) of the C++ exception being thrown in a vectored exception handler working in 64-bit MSVC environment?

Here's the output of dumping 32-bit and 64-bit exception info:

32-bit (RTTI success):

VectoredExceptionHandler(): Start

exc->ExceptionCode               = 0xE06D7363
exc->ExceptionAddress            = 0x74E2C54F
exc->NumberParameters            = 3
exc->ExceptionInformation[0]     = 0x19930520 (sig)
exc->ExceptionInformation[1]     = 0x004FFD9C (object)
exc->ExceptionInformation[2]     = 0x003AD85C (throwInfo)
exc->ExceptionInformation[3]     = 0x005B18F8 (module)

throwInfo->attributes            = 0x00000000
throwInfo->pmfnUnwind            = 0x00000000
throwInfo->pForwardCompat        = 0x00000000
throwInfo->pCatchableTypeArray   = 0x003AD870

object    = 0x004FFD9C
throwInfo = 0x003AD85C
module    = 0x00000000

throwInfo->pCatchableTypeArray   = 0x003AD870
cArray                           = 0x003AD870

cArray->arrayOfCatchableTypes[0] = 0x003AD878
cType                            = 0x003AD878

cType->pType                     = 0x003AFA70
type                             = 0x003AFA70

type->name()                     = "struct `int __cdecl main(void)'::`2'::meow_exception"
cType->sizeOrOffset              = 4

VectoredExceptionHandler(): End

main(): catch (meow_exception { 3 })

64-bit (RTTI failure)

VectoredExceptionHandler(): Start

exc->ExceptionCode               = 0xE06D7363
exc->ExceptionAddress            = 0x000007FEFCE0A06D
exc->NumberParameters            = 4
exc->ExceptionInformation[0]     = 0x0000000019930520 (sig)
exc->ExceptionInformation[1]     = 0x000000000025FBE0 (object)
exc->ExceptionInformation[2]     = 0x000000013FC52AB0 (throwInfo)
exc->ExceptionInformation[3]     = 0x000000013FBE0000 (module)

module                           = 0x000000013FBE0000

throwInfo->attributes            = 0x00000000
throwInfo->pmfnUnwind            = 0x0000000000000000
throwInfo->pForwardCompat        = 0x0000000000072AD0
throwInfo->pCatchableTypeArray   = 0x0000000000000000

VectoredExceptionHandler(): End

main(): catch (meow_exception { 3 })

The code used to get these dumps:

#include <stdio.h>
#include <typeinfo>
#include <windows.h>

//--------------------------------------------------------------------------------------------------

const unsigned EXCEPTION_CPP_MICROSOFT                  = 0xE06D7363,  // '?msc'
               EXCEPTION_CPP_MICROSOFT_EH_MAGIC_NUMBER1 = 0x19930520,  // '?msc' version magic, see ehdata.h

               EXCEPTION_OUTPUT_DEBUG_STRING            = 0x40010006,  // OutputDebugString() call
               EXCEPTION_THREAD_NAME                    = 0x406D1388;  // Passing name of thread to the debugger

void OutputDebugPrintf (const char* format, ...);

//--------------------------------------------------------------------------------------------------

long WINAPI VectoredExceptionHandler (EXCEPTION_POINTERS* pointers)
    {
    const EXCEPTION_RECORD* exc = pointers->ExceptionRecord;

    if (exc->ExceptionCode == EXCEPTION_OUTPUT_DEBUG_STRING ||
        exc->ExceptionCode == EXCEPTION_THREAD_NAME)
        return EXCEPTION_CONTINUE_SEARCH;

    OutputDebugPrintf ("\n%s(): Start\n\n", __func__);

    OutputDebugPrintf ("exc->ExceptionCode    = 0x%X\n", exc->ExceptionCode);
    OutputDebugPrintf ("exc->ExceptionAddress = 0x%p\n", exc->ExceptionAddress);

    if (exc->ExceptionInformation[0] == EXCEPTION_CPP_MICROSOFT_EH_MAGIC_NUMBER1 && 
        exc->NumberParameters >= 3)
        {
        OutputDebugPrintf ("exc->NumberParameters = %u\n", exc->NumberParameters);

        OutputDebugPrintf ("exc->ExceptionInformation[0] = 0x%p (sig)\n",       (void*) exc->ExceptionInformation[0]);
        OutputDebugPrintf ("exc->ExceptionInformation[1] = 0x%p (object)\n",    (void*) exc->ExceptionInformation[1]);
        OutputDebugPrintf ("exc->ExceptionInformation[2] = 0x%p (throwInfo)\n", (void*) exc->ExceptionInformation[2]);
        OutputDebugPrintf ("exc->ExceptionInformation[3] = 0x%p (module)\n",    (void*) exc->ExceptionInformation[3]);
        OutputDebugPrintf ("\n");

        HMODULE module = (exc->NumberParameters >= 4)? (HMODULE) exc->ExceptionInformation[3] : NULL;

        if (module)
            {
            OutputDebugPrintf ("module = 0x%p\n", module);
            OutputDebugPrintf ("\n");
            }

        const _ThrowInfo* throwInfo = (const _ThrowInfo*) exc->ExceptionInformation[2];

        if (throwInfo)
            {
            OutputDebugPrintf ("throwInfo->attributes          = 0x%08X\n", throwInfo->attributes);
            OutputDebugPrintf ("throwInfo->pmfnUnwind          = 0x%p\n",   throwInfo->pmfnUnwind);
            OutputDebugPrintf ("throwInfo->pForwardCompat      = 0x%p\n",   throwInfo->pForwardCompat);
            OutputDebugPrintf ("throwInfo->pCatchableTypeArray = 0x%p\n",   throwInfo->pCatchableTypeArray);
            OutputDebugPrintf ("\n");
            }

        if (throwInfo && throwInfo->pCatchableTypeArray)
            {              
            #define RVA_TO_VA_(type, addr)  ( (type) ((uintptr_t) module + (uintptr_t) (addr)) )

            OutputDebugPrintf ("object    = 0x%p\n", (void*) exc->ExceptionInformation[1]);
            OutputDebugPrintf ("throwInfo = 0x%p\n", (void*) throwInfo);
            OutputDebugPrintf ("module    = 0x%p\n", (void*) module);
            OutputDebugPrintf ("\n");

            const _CatchableTypeArray* cArray = RVA_TO_VA_(const _CatchableTypeArray*, throwInfo->pCatchableTypeArray);

            OutputDebugPrintf ("throwInfo->pCatchableTypeArray = 0x%p\n",   (void*) throwInfo->pCatchableTypeArray);
            OutputDebugPrintf ("cArray                         = 0x%p\n\n", (void*) cArray);

            const _CatchableType* cType = RVA_TO_VA_(const _CatchableType*, cArray->arrayOfCatchableTypes[0]);

            OutputDebugPrintf ("cArray->arrayOfCatchableTypes[0] = 0x%p\n",   (void*) cArray->arrayOfCatchableTypes[0]);
            OutputDebugPrintf ("cType                            = 0x%p\n\n", (void*) cType);

            const std::type_info* type = RVA_TO_VA_(const std::type_info*, cType->pType);

            OutputDebugPrintf ("cType->pType = 0x%p\n",   (void*) cType->pType);
            OutputDebugPrintf ("type         = 0x%p\n\n", (void*) type);

            OutputDebugPrintf ("type->name()        = \"%s\"\n", type->name());
            OutputDebugPrintf ("cType->sizeOrOffset = %zu\n\n",  (size_t) cType->sizeOrOffset);

            #undef RVA_TO_VA_
            }
        }

    OutputDebugPrintf ("%s(): End\n", __func__);
    return EXCEPTION_CONTINUE_SEARCH;
    }

//--------------------------------------------------------------------------------------------------

void OutputDebugPrintf (const char* format, ...)
    {
    static char buf [1024] = "";

    va_list arg; va_start (arg, format);
    _vsnprintf_s (buf, sizeof (buf) - 1, _TRUNCATE, format, arg);
    va_end (arg);

    OutputDebugString (buf);
    printf ("%s", buf);
    }

//--------------------------------------------------------------------------------------------------

int main()
    {    
    OutputDebugPrintf ("\n%s(): Start\n", __func__);

    AddVectoredExceptionHandler (1, VectoredExceptionHandler);

    struct meow_exception { int code = 3; };

    try
        {
        throw meow_exception();
        }

    catch (const meow_exception& e)
        {
        OutputDebugPrintf ("\n%s(): catch (meow_exception { %d })\n", __func__, e.code);
        }

    catch (...)
        {
        OutputDebugPrintf ("\n%s(): catch (...)\n", __func__);
        }

    OutputDebugPrintf ("\n%s(): End\n", __func__);
    return 0;
    }

Build options:

// Microsoft (R) C/C++ Optimizing Compiler Version 19.00.24213.1 (part of VS 2015 SP3)

cl   /c code.cpp /EHsc /W4
link    code.obj kernel32.lib /machine:x86 /subsystem:console /debug

Thank you in advance for the answers and advices.


Solution

  • To solve this problem I gone deeper with the research and discovered some interesting things concerning MSVC 64-bit mode. I've found that we can not rely on internal compiler-predefined types in 64-bit mode because some of them are wrong.

    I compared definitions of some internal structures predefined by the compiler, such as _ThrowInfo and _CatchableType, with assembly listings produced by the compiler being run with /FAs command-line switch.

    Here are the instances of these structures extracted from the assembly files (below is MSVC 2015 64-bit version):

    ;---------------------------------------------------------------------------------------
    ; Listing generated by Microsoft Optimizing Compiler Version 19.00.24213.1 
    ; Simplified: many lines skipped, some sections reordered etc -- Ded
    ;---------------------------------------------------------------------------------------
    
    main proc
    
    ;   struct meow_exception { int code = 3; };
    ;   
    ;   try
    ;       {
    ;       throw meow_exception();
    
        ...
        lea  rdx, OFFSET FLAT:_TI1?AUmeow_exception@?1??main@@YAHXZ@  ; lea &_ThrowInfo
        lea  rcx, QWORD PTR $T1[rsp]
        call _CxxThrowException
    
    ;---------------------------------------------------------------------------------------
    _TI1?AUmeow_exception@?1??main@@YAHXZ@                            ; _ThrowInfo
        DD  0
        DD  0
        DD  0
        DD  imagerel _CTA1?AUmeow_exception@?1??main@@YAHXZ@          ; &_CatchableTypeArray
    
    ;---------------------------------------------------------------------------------------
    _CTA1?AUmeow_exception@?1??main@@YAHXZ@                           ; _CatchableTypeArray
        DD  1
        DD  imagerel _CT??_R0?AUmeow_exception@?1??main@@YAHXZ@@84    ; &_CatchableType
    
    ;---------------------------------------------------------------------------------------
    _CT??_R0?AUmeow_exception@?1??main@@YAHXZ@@84                     ; _CatchableType
        DD  0
        DD  imagerel ??_R0?AUmeow_exception@?1??main@@YAHXZ@@8        ; &_TypeDescriptor
        DD  0
        DD  0ffffffffh
        ORG $+4
        DD  04h
        DD  0
    
    ;---------------------------------------------------------------------------------------
    ??_R0?AUmeow_exception@?1??main@@YAHXZ@@8         ; _TypeDescriptor (aka std::type_info)
        DQ  FLAT:??_7type_info@@6B@
        DQ  0
        DB  '.?AUmeow_exception@?1??main@@YAHXZ@', 0  ; Mangled type name
    
    ;---------------------------------------------------------------------------------------
    

    The binary layout of 32-bit version of these structures is similar to 64-bit, with some little differences (FLAT modifier instead of imagerel in address fields and DD instead of DQ in _TypeDescriptor).

    Then, let's compare this listing to the predefined types taken from ehdata.h file (f.e., see well-known source by Geoff Chappell or C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\crt\src\ehdata.h file in MSVC 2013; unfortunately this file doesn't exist in MSVC 2015 runtime sources):

    typedef const struct _s__ThrowInfo
        {
        unsigned int         attributes;
        _PMFN                pmfnUnwind;            // this is a pointer!
        int (__cdecl        *pForwardCompat) (...); // this is a pointer too!
        _CatchableTypeArray *pCatchableTypeArray;   // this is a pointer too!
        }
        _ThrowInfo;
    
    typedef const struct _s__CatchableType
        {
        unsigned int     properties;
        _TypeDescriptor *pType;                     // this is a pointer too!
        _PMD             thisDisplacement;
        int              sizeOrOffset;
        _PMFN            copyFunction;              // this is a pointer too!
        }
        _CatchableType;
    

    In 32-bit mode, everything is OK because pointers are 32-bit and predefined internal definitions of structures correspond with assembly listings.

    In 64-bit mode, pointers in these structures are RVAs (Relative Virtual Addresses) measured from image base of the module. This is known and well-documented feature; it truly corresponds with assembler listing above. Notice imagerel address modifiers, these mean RVAs. These RVAs are 32-bit and defined as 32-bit DD keyword.

    But in 64-bit mode, from the C++ side, the corresponding pointers are considered to be 64-bit. Thus, C++ binary layout of the internal compiler structures containing pointers (such as _ThrowInfo or _CatchableType above) does not correspond to the Assembler binary layout. Sizes of these structures are greater from C++ side, and the fields offsets are wrong too.

    To test this, I defined my own custom structures with the same fields represented as 32-bit integer types instead of pointers:

    namespace CORRECT
        {
        struct ThrowInfo
            {
            __int32 attributes;
            __int32 pmfnUnwind;           // now this is 32-bit RVA
            __int32 pForwardCompat;       // now this is 32-bit RVA
            __int32 pCatchableTypeArray;  // now this is 32-bit RVA
            };
    
        struct CatchableType
            {
            __int32 properties;
            __int32 pType;                // now this is 32-bit RVA
            _PMD    thisDisplacement;
            __int32 sizeOrOffset;
            __int32 copyFunction;         // now this is 32-bit RVA
            };
        }
    

    Then I dumped the contents of _ThrowInfo and _CatchableType using both internal definitions and my own definitions. Here's the result (MSVC 2015 64-bit):

    exc->ExceptionCode               = 0xE06D7363
    exc->ExceptionAddress            = 0x000007FEFD69A06D
    exc->NumberParameters            = 4
    exc->ExceptionInformation[0]     = 0x0000000019930520 (sig)
    exc->ExceptionInformation[1]     = 0x00000000002BF8B0 (object)
    exc->ExceptionInformation[2]     = 0x000000013F9C4210 (throwInfo)
    exc->ExceptionInformation[3]     = 0x000000013F950000 (module)
    
    Built-in: _ThrowInfo, size 28
    _throwInfo->attributes           = 0x00000000 [ofs:  0, size: 4, type: unsigned int]
    _throwInfo->pmfnUnwind           = 0x00000000 [ofs:  4, size: 8, type: void (__cdecl*)(void * __ptr64)]
    _throwInfo->pForwardCompat       = 0x00074230 [ofs: 12, size: 8, type: int (__cdecl*)(void)]
    _throwInfo->pCatchableTypeArray  = 0x00000000 [ofs: 20, size: 8, type: struct _s__CatchableTypeArray const * __ptr64]
    
    Custom: CORRECT::ThrowInfo, size 16
    throwInfo->attributes            = 0x00000000 [ofs:  0, size: 4, type: int]
    throwInfo->pmfnUnwind            = 0x00000000 [ofs:  4, size: 4, type: int]
    throwInfo->pForwardCompat        = 0x00000000 [ofs:  8, size: 4, type: int]
    throwInfo->pCatchableTypeArray   = 0x00074230 [ofs: 12, size: 4, type: int]
    
    throwInfo->pCatchableTypeArray   = 0x0000000000074230
    cArray                           = 0x000000013F9C4230
    
    Built-in: _CatchableType, size 36
    _cType->properties               = 0x00000000 [ofs:  0, size: 4, type: unsigned int]
    _cType->pType                    = 0x00075D58 [ofs:  4, size: 8, type: struct _TypeDescriptor * __ptr64]
    _cType->thisDisplacement.mdisp   = 0xFFFFFFFF [ofs: 12, size: 4, type: int]
    _cType->thisDisplacement.pdisp   = 0x00000000 [ofs: 16, size: 4, type: int]
    _cType->thisDisplacement.vdisp   = 0x00000004 [ofs: 20, size: 4, type: int]
    _cType->sizeOrOffset             = 0x00000000 [ofs: 24, size: 4, type: int]
    _cType->copyFunction             = 0x00000000 [ofs: 28, size: 8, type: void (__cdecl*)(void * __ptr64)]
    
    Custom: CORRECT::CatchableType, size 28
    cType->properties                = 0x00000000 [ofs:  0, size: 4, type: int]
    cType->pType                     = 0x00075D58 [ofs:  4, size: 4, type: int]
    cType->thisDisplacement.mdisp    = 0x00000000 [ofs:  8, size: 4, type: int]
    cType->thisDisplacement.pdisp    = 0xFFFFFFFF [ofs: 12, size: 4, type: int]
    cType->thisDisplacement.vdisp    = 0x00000000 [ofs: 16, size: 4, type: int]
    cType->sizeOrOffset              = 0x00000004 [ofs: 20, size: 4, type: int]
    cType->copyFunction              = 0x00000000 [ofs: 24, size: 4, type: int]
    
    cArray->arrayOfCatchableTypes[0] = 0x0000000000074240
    cType                            = 0x000000013F9C4240
    
    cType->pType                     = 0x0000000000075D58
    type                             = 0x000000013F9C5D58
    
    type->name()                     = "struct `int __cdecl main(void)'::`2'::meow_exception"
    cType->sizeOrOffset              = 4
    

    See the differences in sizes of the whole structures (28 vs 16 bytes, 36 vs 28 bytes), of the pointer members (8 vs 4 bytes) and wrong offsets.

    When using CORRECT:: definitions, it's easy to get correct RTTI desired.

    The original C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\crt\src\ehdata.h file from MSVC 2013 runtime sources contains conditional preprocessor directives #ifdef _EH_RELATIVE_OFFSETS which substitute pointers to __int32 offsets. But predefined internal compiler types always contains 64-bit wrong pointers.

    Thus, using internal definitions of RTTI structures are unreliable in 64-bit mode. One should use its own definitions where pointer members are represented as 32-bit integers (or #define _EH_RELATIVE_OFFSETS and use ehdata.h mentioned above). After that, don't forget manually convert RVAs to the common C++ pointers by adding ImageBase address as usual. But one should not trust pointer members in such structures and definitions containing such pointers because they don't reflect the true 64-bit binary layout.

    I tested it with MSVC 2010, and got the same results.

    Here's the code for getting correct RTTI in 64-bit MSVC environment:

    #include <stdio.h>
    #include <typeinfo>
    #include <stdexcept>
    #include <windows.h>
    
    //------------------------------------------------------------------------------------------------------------------------------
    //! These definitions are based on assembly listings produded by the compiler (/FAs) rather than built-in ones
    //! @{
    
    #pragma pack (push, 4)
    
    namespace CORRECT
        {
    
        struct CatchableType
            {
            __int32 properties;
            __int32 pType;
            _PMD    thisDisplacement;
            __int32 sizeOrOffset;
            __int32 copyFunction;
            };
    
        struct ThrowInfo
            {
            __int32 attributes;
            __int32 pmfnUnwind;
            __int32 pForwardCompat;
            __int32 pCatchableTypeArray;
            };
    
        }
    
    #pragma pack (pop)
    
    //! @}
    //------------------------------------------------------------------------------------------------------------------------------
    
    const unsigned EXCEPTION_CPP_MICROSOFT                  = 0xE06D7363,  // '?msc'
                   EXCEPTION_CPP_MICROSOFT_EH_MAGIC_NUMBER1 = 0x19930520,  // '?msc' version magic, see ehdata.h
    
                   EXCEPTION_OUTPUT_DEBUG_STRING            = 0x40010006,  // OutputDebugString() call
                   EXCEPTION_THREAD_NAME                    = 0x406D1388;  // Passing name of thread to the debugger
    
    void OutputDebugPrintf (const char* format, ...);
    
    //------------------------------------------------------------------------------------------------------------------------------
    
    long WINAPI VectoredExceptionHandler (EXCEPTION_POINTERS* pointers)
        {
        const EXCEPTION_RECORD* exc = pointers->ExceptionRecord;
    
        if (exc->ExceptionCode == EXCEPTION_OUTPUT_DEBUG_STRING ||
            exc->ExceptionCode == EXCEPTION_THREAD_NAME)
            return EXCEPTION_CONTINUE_SEARCH;
    
        OutputDebugPrintf ("\n%s(): Start\n\n", __FUNCTION__);
    
        OutputDebugPrintf         ("exc->ExceptionCode               = 0x%X\n", exc->ExceptionCode);
        OutputDebugPrintf         ("exc->ExceptionAddress            = 0x%p\n", exc->ExceptionAddress);
    
        if (exc->ExceptionInformation[0] == EXCEPTION_CPP_MICROSOFT_EH_MAGIC_NUMBER1 && 
            exc->NumberParameters >= 3)
            {
            OutputDebugPrintf     ("exc->NumberParameters            = %u\n", exc->NumberParameters);
    
            OutputDebugPrintf     ("exc->ExceptionInformation[0]     = 0x%p (sig)\n",       (void*) exc->ExceptionInformation[0]);
            OutputDebugPrintf     ("exc->ExceptionInformation[1]     = 0x%p (object)\n",    (void*) exc->ExceptionInformation[1]);
            OutputDebugPrintf     ("exc->ExceptionInformation[2]     = 0x%p (throwInfo)\n", (void*) exc->ExceptionInformation[2]);
    
            if (exc->NumberParameters >= 4)
                OutputDebugPrintf ("exc->ExceptionInformation[3]     = 0x%p (module)\n",    (void*) exc->ExceptionInformation[3]);
    
            OutputDebugPrintf ("\n");
    
            HMODULE module = (exc->NumberParameters >= 4)? (HMODULE) exc->ExceptionInformation[3] : NULL;
    
            #define RVA_TO_VA_(type, addr)  ( (type) ((uintptr_t) module + (uintptr_t) (addr)) )
    
            const         _ThrowInfo* _throwInfo = (const         _ThrowInfo*) exc->ExceptionInformation[2];
            const CORRECT::ThrowInfo*  throwInfo = (const CORRECT::ThrowInfo*) exc->ExceptionInformation[2];
    
            #define DUMP_(var, struc, field)  OutputDebugPrintf ("%-32s = 0x%08X [ofs: %2u, size: %u, type: %s]\n",  \
                                                                 #var "->" #field, (var)->field,                 \
                                                                 offsetof (struc, field), sizeof ((var)->field), \
                                                                 typeid ((var)->field) .name());
            if (_throwInfo)
                {
                OutputDebugPrintf ("Built-in: _ThrowInfo, size %u\n", sizeof (_ThrowInfo));
                DUMP_ (_throwInfo, _ThrowInfo, attributes);
                DUMP_ (_throwInfo, _ThrowInfo, pmfnUnwind);
                DUMP_ (_throwInfo, _ThrowInfo, pForwardCompat);
                DUMP_ (_throwInfo, _ThrowInfo, pCatchableTypeArray);
                OutputDebugPrintf ("\n");
                }
            else
                OutputDebugPrintf ("_throwInfo is NULL\n");
    
            if (throwInfo)
                {
                OutputDebugPrintf ("Custom: CORRECT::ThrowInfo, size %u\n", sizeof (CORRECT::ThrowInfo));
                DUMP_ ( throwInfo, CORRECT::ThrowInfo, attributes);
                DUMP_ ( throwInfo, CORRECT::ThrowInfo, pmfnUnwind);
                DUMP_ ( throwInfo, CORRECT::ThrowInfo, pForwardCompat);
                DUMP_ ( throwInfo, CORRECT::ThrowInfo, pCatchableTypeArray);
                OutputDebugPrintf ("\n");
                }
            else
                OutputDebugPrintf ("throwInfo is NULL\n");
    
            if (throwInfo)
                {              
                const _CatchableTypeArray* cArray = RVA_TO_VA_(const _CatchableTypeArray*, throwInfo->pCatchableTypeArray);
    
                OutputDebugPrintf ("throwInfo->pCatchableTypeArray   = 0x%p\n",   (void*)(ptrdiff_t) throwInfo->pCatchableTypeArray);
                OutputDebugPrintf ("cArray                           = 0x%p\n\n", (void*)            cArray);
    
                const         _CatchableType* _cType = RVA_TO_VA_(const         _CatchableType*, cArray->arrayOfCatchableTypes[0]);
                const CORRECT::CatchableType*  cType = RVA_TO_VA_(const CORRECT::CatchableType*, cArray->arrayOfCatchableTypes[0]);
    
                OutputDebugPrintf ("Built-in: _CatchableType, size %u\n", sizeof (_CatchableType));
                DUMP_ (_cType, _CatchableType, properties);
                DUMP_ (_cType, _CatchableType, pType);
                DUMP_ (_cType, _CatchableType, thisDisplacement.mdisp);
                DUMP_ (_cType, _CatchableType, thisDisplacement.pdisp);
                DUMP_ (_cType, _CatchableType, thisDisplacement.vdisp);
                DUMP_ (_cType, _CatchableType, sizeOrOffset);
                DUMP_ (_cType, _CatchableType, copyFunction);
                OutputDebugPrintf ("\n");
    
                OutputDebugPrintf ("Custom: CORRECT::CatchableType, size %u\n", sizeof (CORRECT::CatchableType));
                DUMP_ ( cType, CORRECT::CatchableType, properties);
                DUMP_ ( cType, CORRECT::CatchableType, pType);
                DUMP_ ( cType, CORRECT::CatchableType, thisDisplacement.mdisp);
                DUMP_ ( cType, CORRECT::CatchableType, thisDisplacement.pdisp);
                DUMP_ ( cType, CORRECT::CatchableType, thisDisplacement.vdisp);
                DUMP_ ( cType, CORRECT::CatchableType, sizeOrOffset);
                DUMP_ ( cType, CORRECT::CatchableType, copyFunction);
                OutputDebugPrintf ("\n");
    
                OutputDebugPrintf ("cArray->arrayOfCatchableTypes[0] = 0x%p\n",   (void*) cArray->arrayOfCatchableTypes[0]);
                OutputDebugPrintf ("cType                            = 0x%p\n\n", (void*) cType);
    
                const std::type_info* type = RVA_TO_VA_(const std::type_info*, cType->pType);
    
                OutputDebugPrintf ("cType->pType                     = 0x%p\n",   (void*)(ptrdiff_t) cType->pType);
                OutputDebugPrintf ("type                             = 0x%p\n\n", (void*)            type);
    
                OutputDebugPrintf ("type->name()                     = \"%s\"\n", type->name());
                OutputDebugPrintf ("cType->sizeOrOffset              = %u\n\n",   (unsigned) cType->sizeOrOffset);
    
                }
    
            #undef DUMP_
            #undef RVA_TO_VA_
            }
    
        OutputDebugPrintf ("%s(): End\n", __FUNCTION__);
        return EXCEPTION_CONTINUE_SEARCH;
        }
    
    //------------------------------------------------------------------------------------------------------------------------------
    
    void OutputDebugPrintf (const char* format, ...)
        {
        static char buf [1024] = "";
    
        va_list arg; va_start (arg, format);
        _vsnprintf_s (buf, sizeof (buf) - 1, _TRUNCATE, format, arg);
        va_end (arg);
    
        OutputDebugString (buf);
        printf ("%s", buf);
        }
    
    //------------------------------------------------------------------------------------------------------------------------------
    
    int main()
        {    
        OutputDebugPrintf ("\nCompiled with MSVC %d, %d-bit\n", _MSC_VER, 8 * sizeof (void*));
        OutputDebugPrintf ("\n%s(): Start\n", __FUNCTION__);
    
        AddVectoredExceptionHandler (1, VectoredExceptionHandler);
    
        struct meow_exception { int code; meow_exception() : code (3) {} };
    
        try
            {
            throw meow_exception();
            }
    
        catch (const meow_exception& e)
            {
            OutputDebugPrintf ("\n%s(): catch (meow_exception { %d })\n", __FUNCTION__, e.code);
            }
    
        catch (...)
            {
            OutputDebugPrintf ("\n%s(): catch (...)\n", __FUNCTION__);
            }
    
        OutputDebugPrintf ("\n%s(): End\n", __FUNCTION__);
        return 0;
        }