Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do vtables have sizeof(void*) * 2 bytes of 0x00 padding?

Tags:

c++

vtable

I imagine this is implementation-specific, but for armv7, arm64, and x86_64 builds using libstdc++ and libc++ (gcc or clang), it seems that vtables always have 8 bytes (16 on 64-bit) of padding at the beginning, and fetching a vtable usually looks something like this:

ldr.w r0, <address of vtable>
adds  r0, 0x8
str   r0, [r1] ; where r1 is the instance

And the vtable looks something like this:

vtable+0x00: 0x00000000
vtable+0x04: 0x00000000
vtable+0x08: 0xfirstfunc
vtable+0x0c: 0xsecondfunc
vtable+0x10: 0xthirdfunc

etc...

Anyone know why this is?

like image 723
Ryan Terry Avatar asked Nov 27 '16 05:11

Ryan Terry


1 Answers

There should be only one zero (of size void *) at the beginning (unless compiled without RTTI). It actually doesn't have to be but it commonly is, I'll explain later.

The VTable for (at least gcc originated) ABIs looks like:

class_offset
type_info
first_virtual_function
second_virtual_function
etc.

The type_info can be NULL (0) in case the code was compiled without RTTI.

The class_offset from above explains why you see zero there. This is the class offset within the owning class. I.e. having:

class A { virtual meth() {} };
class B { virtual meth() {} };
class C: public A, public B { virtual meth() {} };

would result into main class C, A starting at position 0 within class C and B starting at position 4 (or 8) within class C.

The pointer is there so you can find from any class pointer the pointer to the owning object. So for any "main" class it will be always 0 but for the B class virtual table valid in C context it will be -4 or -8. You actually need to check the the VTable for C (the second half) as the compiler usually doesn't generate the VTables separately:

_ZTV1C:
    // VTable for C and A within C
    .quad   0
    .quad   _ZTI1C
    .quad   _ZN1CD1Ev
    .quad   _ZN1CD0Ev
    .quad   _ZN1C4methEv
    // VTable for B within C
    .quad   -8
    .quad   _ZTI1C
    .quad   _ZThn8_N1CD1Ev
    .quad   _ZThn8_N1CD0Ev
    .quad   _ZThn8_N1C4methEv

In the earlier compilers the offset was used to compute the real pointer to the owning class before invoking the method. But as it slowed down the cases when invoking the method directly on the owning class, the modern compilers rather generate stub which subtracts the offset directly and jumps to the main implementation of the method (as you can guess from the method name - note the 8):

_ZThn8_N1C4methEv:
    subq    $8, %rdi
    jmp     _ZN1C4methEv
like image 92
Zbynek Vyskovsky - kvr000 Avatar answered Nov 09 '22 21:11

Zbynek Vyskovsky - kvr000