C++ RTTI in a Windows 64-bit VectoredExceptionHandler, MS Visual Studio 2015 -
i'm working on small windows exception handling engine trying gather maximum information system, including c++ exceptions rtti.
in 32-bit vectoredexceptionhandler compiled msvs 2015, can obtain std::type_info pointer rtti of type being thrown. can found in ((_throwinfo*) exceptionpointers->exceptionrecord->exceptioninformation[2])->pcatchabletypearray->arrayofcatchabletypes[0]
(see classic article of raymond chen, definitions ms's ehdata.h
file , many others). method based on fetching pcatchabletypearray
member of msvc built-in _throwinfo
structure data built compiler.
but in 64-bit environment, _throwinfo
contains no direct rtti: unfortunately, pcatchabletypearray
null. in disassembly window, see null before call _cxxthrowexception
, main ms throw-handler. searched through number of articles concerning new 64-bit exception-handling mechanism used in msvc, there no information on rtti. maybe missed something.
are there ways obtain std::type_info (or type name) of c++ exception being thrown in vectored exception handler working in 64-bit msvc environment?
here's output of dumping 32-bit , 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 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 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 in advance answers , advices.
to solve problem gone deeper research , discovered interesting things concerning msvc 64-bit mode. i've found can not rely on internal compiler-predefined types in 64-bit mode because of them wrong.
i compared definitions of internal structures predefined compiler, such _throwinfo
, _catchabletype
, assembly listings produced compiler being run /fas
command-line switch.
here instances of these structures extracted assembly files (below msvc 2015 64-bit version):
;--------------------------------------------------------------------------------------- ; listing generated microsoft optimizing compiler version 19.00.24213.1 ; simplified: many lines skipped, 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 similar 64-bit, little differences (flat
modifier instead of imagerel
in address fields , dd
instead of dq
in _typedescriptor
).
then, let's compare listing predefined types taken ehdata.h
file (f.e., see well-known source geoff chappell or c:\program files (x86)\microsoft visual studio 12.0\vc\crt\src\ehdata.h
file in msvc 2013; unfortunately file doesn't exist in msvc 2015 runtime sources):
typedef const struct _s__throwinfo { unsigned int attributes; _pmfn pmfnunwind; // pointer! int (__cdecl *pforwardcompat) (...); // pointer too! _catchabletypearray *pcatchabletypearray; // pointer too! } _throwinfo; typedef const struct _s__catchabletype { unsigned int properties; _typedescriptor *ptype; // pointer too! _pmd thisdisplacement; int sizeoroffset; _pmfn copyfunction; // pointer too! } _catchabletype;
in 32-bit mode, ok because pointers 32-bit , predefined internal definitions of structures correspond assembly listings.
in 64-bit mode, pointers in these structures rvas (relative virtual addresses) measured image base of module. known , well-documented feature; corresponds assembler listing above. notice imagerel
address modifiers, these mean rvas. these rvas 32-bit , defined 32-bit dd
keyword.
but in 64-bit mode, c++ side, corresponding pointers considered 64-bit. thus, c++ binary layout of internal compiler structures containing pointers (such _throwinfo
or _catchabletype
above) does not correspond assembler binary layout. sizes of these structures greater c++ side, , fields offsets wrong too.
to test this, defined own custom structures same fields represented 32-bit integer types instead of pointers:
namespace correct { struct throwinfo { __int32 attributes; __int32 pmfnunwind; // 32-bit rva __int32 pforwardcompat; // 32-bit rva __int32 pcatchabletypearray; // 32-bit rva }; struct catchabletype { __int32 properties; __int32 ptype; // 32-bit rva _pmd thisdisplacement; __int32 sizeoroffset; __int32 copyfunction; // 32-bit rva }; }
then dumped contents of _throwinfo
, _catchabletype
using both internal definitions , own definitions. here's 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 differences in sizes of whole structures (28 vs 16 bytes, 36 vs 28 bytes), of pointer members (8 vs 4 bytes) , wrong offsets.
when using correct::
definitions, it's easy correct rtti desired.
the original c:\program files (x86)\microsoft visual studio 12.0\vc\crt\src\ehdata.h
file msvc 2013 runtime sources contains conditional preprocessor directives #ifdef _eh_relative_offsets
substitute pointers __int32
offsets. predefined internal compiler types contains 64-bit wrong pointers.
thus, using internal definitions of rtti structures unreliable in 64-bit mode. 1 should use own definitions pointer members represented 32-bit integers (or #define _eh_relative_offsets
, use ehdata.h
mentioned above). after that, don't forget manually convert rvas common c++ pointers adding imagebase
address usual. one should not trust pointer members in such structures , definitions containing such pointers because they don't reflect true 64-bit binary layout.
i tested msvc 2010, , got same results.
here's code getting correct rtti in 64-bit msvc environment:
#include <stdio.h> #include <typeinfo> #include <stdexcept> #include <windows.h> //------------------------------------------------------------------------------------------------------------------------------ //! these definitions based on assembly listings produded compiler (/fas) rather 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 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 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 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 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; }
Comments
Post a Comment