我尝试使用 CXXRecordDecl 打印虚函数,但它们是按声明顺序排列的,这可能不是 vtable 中的实际顺序。
示例代码
namespace test
{
class Foo {
public:
virtual void v_func() {}
virtual void v_func2() {}
virtual ~Foo() = default;
};
class Bar : public Foo {
public:
virtual void v_func2() {}
virtual void v_func() {}
virtual ~Bar() = default;
};
} // namespace test
int main() {
test::Foo *foo = new test::Bar();
test::Bar *bar = new test::Bar();
delete foo;
delete bar;
return 0;
}
gdb打印的虚函数表如下:
(gdb) info vtbl foo
vtable for 'test::Foo' @ 0x65a450 (subobject @ 0x101a008):
[0]: 0x404402 <test::Bar::v_func()>
[1]: 0x4043f4 <test::Bar::v_func2(int, test::tmp)>
(gdb) info vtbl bar
vtable for 'test::Bar' @ 0x65a450 (subobject @ 0x101a010):
[0]: 0x404402 <test::Bar::v_func()>
[1]: 0x4043f4 <test::Bar::v_func2(int, test::tmp)>
遍历CXXRecordDecl的方法,只能按照函数声明的顺序打印
// CXXRecordDecl *cxx_record
// Dump out all the virtual methods
for (CXXRecordDecl::method_iterator first = cxx_record->method_begin();
first != cxx_record->method_end(); ++first) {
if (!first->isVirtual()) {
continue;
}
llvm::outs() << first->getQualifiedNameAsString() << " " << first->getType().getAsString() << "\n";
}
出:
v_func void (void)
v_func2 void (int, class test::tmp)
~Foo void (void) noexcept
v_func2 void (int, class test::tmp)
v_func void (void)
~Bar void (void) noexcept
我检查了 CXXRecordDecl 的文档,没有发现任何与 vtables 相关的东西
CXXRecordDecl
中,您会看到编译器“前端”中可用的信息,其中包括输入语法、静态类型以及在中指定的其他内容C++ 语言标准。
但是,C++ 标准根本不需要存在虚函数表(“vtables”)本身(原则上,虚拟调度可以通过一些完全不同的机制来完成),更不用说它们的顺序了。相反,vtables 是 Application Binary Interface (ABI) 的一个特性,它指定了如何在机器代码数据结构级别实现各种语言特性。 ABI 取决于处理器架构。
一个常用的 C++ ABI,即使在 x86_64 上,也是 Itanium C++ ABI。引用其关于 vtable 组件的部分:
虚表中虚函数指针的顺序就是类中对应成员函数的声明顺序
因此,如果您对使用该 ABI 的平台的 vtable 顺序感兴趣,您在 Clang AST 中看到的顺序就是该顺序(尽管您需要阅读完整文档,而不仅仅是我引用的部分,以获取完整的故事)。
如果你想挂钩进程clang
特别是用来生成 vtables,看看它的
VTableBuilder
类。该类是clang
“后端”的一部分,即生成机器代码的部分,这意味着您必须使用代码生成器(而不仅仅是解析器)才能使用它。