Compiling with /d1reportAllClassLayout, even an empty file, dumps many compiler-intrinsic types. Six of these are interesting in the present context: _PMD, _TypeDescriptor, _s__RTTIBaseClassDescriptor2, _s__RTTIBaseClassArray, _s__RTTIClassHierarchyDescriptor, and _s__RTTICompleteObjectLocator.
Most of this machinery exists solely for multiple inheritance, and virtual inheritance in particular – which modern languages eschew and is considered (I hope) a painful legacy by all current C++ professionals, and so I won’t go further in. Go here for an excellent survey on these deep internals of multiple inheritance and RTTI.
Turns out that for viewing types in sane (== single inheritance) code, two of these intrinsic types suffice:
1> class _TypeDescriptor size(8):
1> 0 | pVFTable
1> 4 | spare
1> 8 | name
1> class _s__RTTICompleteObjectLocator size(20):
1> 0 | signature
1> 4 | offset
1> 8 | cdOffset
1> 12 | pTypeDescriptor
1> 16 | pClassDescriptor
The gem mentioned above tells us that an RTTICompleteObjectLocator pointer is placed right before the vftable. (This struct is responsible for locating the adjoint vftable within a larger vftable in the case of multiple inheritence, where method dispatching requires adjustor thunks, dynamic-casts require traversal of the full inheritance tree and things get hairy in general. Not another word on that, I swear).
RTTICompleteObjectLocator contains a pointer to a TypeDescriptor, which happily points to a name! To view this in the debugger, take a typed pointer (say, pb -sticking with the setup from last time), and type at a watch window
(_s__RTTICompleteObjectLocator**)((int*)pb->__vfptr – 1)
Here’s what you’d see:
That is (naturally) a decorated name, so pop open a VS command prompt and call undname.exe with the 0x2000 flag (decipher types) and that name string, omitting the leading ‘.’ :
And now, for something completely useful.
All the above may seem like a very roundabout – albeit cool – way of seeing what the debugger already shows, but this does present a very useful trick:
What if you don’t have symbols?
It is very easy to recognize dispatching of virtual functions (call on a register argument), and typically a few instructions before that $ecx would be populated with the ‘this’ pointer. Now, you can know the name of the type whose methods you call, even without symbols! (assuming the code was built with RTTI on).
E.g., around this virtual method call:
Type this at a watch window:
(some complexity added to conform also to x64 systems, thanks @Martin Ridgers!), and you’ll be greeted be a magnificent view:
Pingback: Viewing types, part 3: Exceptions « Ofek's Visual C++ stuff
One small comment – you should really cast to uintptr_t* rather than int*. Then the technique doesn’t make assumptions about pointer size and will work for both x86 and x64 code.
True, thanks. The ‘4’ offset should also really be ‘sizeof(int*)’. I was actually aware of this when writing but decided to drop the extra generality. I’ll fix it soon.