我知道这是编译器/ABI 相关的,不一定是标准化的,等等。我一直假设,从我在几个地方读到的内容(例如这里的答案或维基百科中的示例),编译器所做的典型事情是Class在类型为 的对象的开头有一个指向 vtable 的指针Class。
我现在正在调试和检测一个接收Class*参数的函数。Class有虚函数。我想确定它是否属于我关心的类。我可能还想用它做其他事情,所以我试图得到一个很好的理解。
我查看了 Ghidra 中的反汇编,我发现有关该类的 vtable 的内容(粘贴来自我编写的示例,但它等效于我想要 RE 的真正二进制文件):
**************************************************************
* vtable for DerivedClass *
**************************************************************
_ZTV12DerivedClass XREF[3]: Entry Point(*),
DerivedClass::vtable operator():001039d1(*),
_elfSectionHeaders::00000650(*)
00107c70 00 00 00 ptrdiff_
00 00 00
00 00
00107c70 [0] 0h
00107c78 88 7c 10 addr DerivedClass::typeinfo =
00 00 00
00 00
PTR_ARRAY_00107c80 XREF[2]: operator():001039d8(*),
operator():001039dc(*)
00107c80 94 42 10 addr[1]
00 00 00
00 00
00107c80 94 42 10 00 00 addr DerivedClass::someVirt [0] XREF[2]: operator():001039d8(*),
00 00 00 operator():001039dc(*)
这就是我的惊喜所在:
- 我得到了
_ZTV12DerivedClass(例如通过 Frida,Module.findExportByName(null, "_ZTV12DerivedClass")或在 GDB 中info address _ZTV12DerivedClass)的地址。 - 我再次在调试器中或使用 Frida 获取对象的 vtable 的地址(它是 64 位,所以是对象的第一个字节 8 个)。
- 我比较了两者,我发现它们相距 16 个字节,即 2 个指针的大小:对象实例中的指针不是指向vtable 的开头,而是在指向虚函数的指针数组的开头!
这当然不是为了比较指针而惊天动地。只需添加 16。我已经看到前两个指针应该是什么的解释。但我有一些疑问:
- 假设 16 的差异在同一个二进制文件中的所有类中保持一致是否可靠?
- 这在更多的编译器中很常见吗?
- 表中的两个位置(它的最开始和虚函数指针数组的开始)是否有任何命名约定?我发现令人惊讶的是,有一个指向某物的指针,而不是在反汇编中发现的 vtable 的开始。
- 可能是一些历史遗产?如果 C++ 首先有虚函数但没有虚继承或 RTTI,这可能解释了这两个额外的指针被添加到顶部以不改变其他一些假设。