Почему в vtables есть sizeof (void *) * 2 байта заполнения 0x00?

Я предполагаю, что это специфично для реализации, но для armv7, arm64 и x86_64 строит с использованием libstdС++ и libС++ (gcc или clang), кажется, что vtables всегда имеет 8 байтов (16 на 64-битных) отступов в начале, и выборка vtable обычно выглядит примерно так:

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

И vtable выглядит примерно так:

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

и т.д...

Кто-нибудь знает, почему это?

Ответ 1

В начале должен быть только один нуль (размер void *) (если не скомпилирован без RTTI). Это на самом деле не обязательно, но обычно это я расскажу позже.

VTable для (по крайней мере, gcc) ABI выглядит следующим образом:

class_offset
type_info
first_virtual_function
second_virtual_function
etc.

Тип_info может быть NULL (0), если код был скомпилирован без RTTI.

class_offset сверху объясняет, почему вы видите нуль. Это смещение класса внутри класса-владельца. То есть имея:

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

приведет к основному классу C, A, начиная с позиции 0 в классе C и B, начиная с позиции 4 (или 8) в классе C.

Указатель находится там, поэтому вы можете найти из любого указателя класса указатель на объект-владелец. Поэтому для любого "основного" класса он всегда будет 0, но для виртуальной таблицы класса B, действительной в контексте C, это будет -4 или -8. Вам действительно нужно проверить VTable для C (вторая половина), поскольку компилятор обычно не генерирует VTables отдельно:

_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

В предыдущих компиляторах смещение использовалось для вычисления реального указателя на собственный класс перед вызовом метода. Но поскольку это замедляет случаи при вызове метода непосредственно на собственном классе, современные компиляторы скорее генерируют заглушку, которая вычитает смещение напрямую и переходит к основной реализации метода (как вы можете догадаться из имени метода - обратите внимание на 8):

_ZThn8_N1C4methEv:
    subq    $8, %rdi
    jmp     _ZN1C4methEv