C++ Logo


Advanced search

Re: [std-proposals] Extend std::type_info with more information

From: Frederick Virchanza Gotham <cauldwell.thomas_at_[hidden]>
Date: Tue, 9 Apr 2024 08:05:25 +0100
On Mon, Apr 8, 2024 at 2:53 PM Arthur O'Dwyer wrote:
> Now, if you were going to redesign type_info from scratch (which we're never going to)

I'm suggesting two things:

(1) Extend the original std::type_info as much as we can without
causing an ABI break (i.e. use the common subset of information that
is already there on Itanium ABI and Microsoft ABI)
(2) Consider making a new type, std::type_info_extended, and a new
function, std::get_type_info_extended, with all the extra stuff we

We regard to (1), well the Itanium ABI has some more stuff for us such
as IsClass, IsPointerToMember as shown in this GodBolt:

When it comes to the Microsoft ABI, well I can't find any info on what
else they have inside their type_info -- I emailed a guy in Microsoft
but nothing back yet (tick tock tick tock). So in the meantime, I
wrote the following function in Visual Studio 2022:

    ostream *volatile p;

    __declspec(noinline) istream *Func(void)
        return dynamic_cast<istream*>(p);

I call this function from 'main' and put a breakpoint right after it,
and then hit Ctrl + Alt + D to see the following assembler (the
comments are mine). Note in the Microsoft x86 calling convention, the
first four arguments go in RCX, RDX, R8, R9.

        sub rsp,38h ; Allocate 56 bytes of
stack space
        mov r8,qword ptr [p] ; Set r8 = p
        test r8,r8 ; If p is a nullptr
        je Func+1Ch ; Jump if p is a nullptr
        mov rax,qword ptr [r8] ; Set rax = vtable pointer of *p
        movsxd rcx,dword ptr [rax+4] ; Set rcx = type_info
offset from vtable of *p
        add rcx,r8 ; Set rcx = p + offset
(address of type_info)
        jmp Func+1Eh ; Skip the next instruction
        xor ecx,ecx ; Set 1st argument = 0
        mov edx,ecx ; Set 2nd argument =
1st argument (address of type_info)
        mov dword ptr [rsp+20h],0 ; Set stack variable = 0
        sub edx,r8d ; Set 2nd argument =
address of object
        lea r9,[std::basic_istream<char,std::char_traits<char>
> RTTI] ; r9 = address of istream's type_info
        lea r8,[std::basic_ostream<char,std::char_traits<char>
> RTTI] ; r8 = address of ostream's type_info
        call __RTDynamicCast ; Call the
__RTDynamicCast function

So we can see that the function "__RTDynamicCast" is being invoked as follows:

    __RTDynamicCast( &typeid(*p), p, &typeid(ostream), &typeid(istream) );

Previously when I was exploring how Microsoft does exceptions, they
had a 'ThrowInfo' struct which was separate from the 'type_info'.
However, when it comes to dynamic_cast, they only need the 'type_info'
-- so this means that there must be extra info there, there must at
least be an array of pointers to all the type_info's of all the base
classes. So now I just need to print the machine code of

    for ( unsigned i = 0u; i < 128u; ++i ) cout << hex << setfill('0')
<< setw('2') << ((char unsigned*)__RTDynamicCast)[i];

This comes back with an immediate jump:

    ff 25 de 0f 00 00 jmp QWORD PTR [rip+0xfde]

Okay so I need to see what machine code is located at the address
specified by: *(progam_counter + 0xfde). So I wrote this little

int main(void)
    cout << "Machine Code: ";
    char unsigned const *const p = (char unsigned*)&__RTDynamicCast;
    for ( unsigned i = 0u; i < 16u; ++i ) cout << hex << setfill('0')
<< setw(2) << (unsigned)p[i];
    cout << endl;

    int32_t offset;
    memcpy(&offset, p+2, sizeof offset);
    cout << "Offset = 0x" << hex << offset << endl;

    cout << "Machine Code: ";
    void **address_of_pointer = (void**)((char*)&__RTDynamicCast +
offset + 6); // 6 is the size of the jump instruction
    char unsigned *address_of_jump_destination = (char
    for ( unsigned i = 0u; i < 512u; ++i ) cout << hex << setfill('0')
<< setw(2) << (unsigned)address_of_jump_destination[i];
    cout << endl;

Received on 2024-04-09 07:05:38