在GDB中打印对象vptr地址需用x/gx &obj(栈对象)或x/gx obj(堆指针),再用p/x *(void**)obj安全解引用;解析vtable须结合p/a *(void**)vptr@n、c++filt及-fdump-class-hierarchy生成的静态布局对照。

gdb里怎么打印对象的vptr地址
对象的虚表指针(vptr)就藏在对象内存布局最开头,通常是前 8 字节(x86_64 下)。只要拿到对象变量地址,就能直接读它。
常见错误是直接 p obj —— 这输出的是对象内容(含成员),不是 vptr 值本身;或者误用 p &obj,那得到的是栈上对象变量的地址,不是对象首字节地址。
- 对栈对象:用
x/gx &obj或x/gx obj(若obj是变量名且非指针) - 对堆对象指针:用
x/gx obj(obj本身就是地址) - 确认类型安全:先
p/x *(void**)obj,强制按 void* 数组解引用,避免 gdb 类型推导干扰
gdb里怎么解析vtable内容并对应到函数
拿到 vptr 地址后,它指向的是一块连续内存,每个条目是函数指针。但裸看十六进制地址没意义,得映射回函数名。
关键点:g++ 的 vtable 条目顺序严格按类中虚函数声明顺序排列,第一个是第一个虚函数,第二个是第二个……析构函数可能占两个槽(带 nothrow 和不带的),要小心。
- 查前 4 个函数地址:
p /a *(void**)0x7fffffffe000@4(把0x7fffffffe000换成你实际的vptr值) - 用
c++filt解码符号:echo "_ZN7Derived4showEv" | c++filt→Derived::show() - 如果地址不在符号表里(比如被优化掉或内联),
info symbol 0x555555759e10可能返回No symbol matches,这时得结合objdump -C -t a.out | grep show手动比对
为什么info vtbl有时不显示完整函数列表
info vtbl 是 gdb 内置命令,但它依赖调试信息质量与编译器实现细节。在多重继承、虚继承或模板特化场景下,它可能只显示主基类的 vtable,漏掉次级虚表。
更可靠的做法是绕过 info vtbl,直接内存读取 + 符号比对:
- 关掉 PIE:
g++ -g -fno-pie -no-pie编译,否则vptr地址每次运行都变,无法和objdump结果对齐 - 虚继承会引入额外的偏移调整 thunk 函数,它们也存在 vtable 中,但名字像
_ZThn8_N7Derived4showEv,c++filt能识别,但容易被当成“奇怪的函数”忽略 - RTTI 信息紧挨着 vtable 开头存放(
*(void**)vptr - 1),常被误认为第一个虚函数地址——其实那是type_info指针
用-fdump-class-hierarchy提前看vtable结构
与其在 gdb 里反复猜偏移,不如编译期就生成 vtable 布局快照。GCC 的 -fdump-class-hierarchy 会输出人类可读的类结构,包含每个虚函数在 vtable 中的索引。
执行:g++ -g -fdump-class-hierarchy virtual_function_example.cpp,会生成 virtual_function_example.cpp.002t.class 文件。
- 文件里明确写有:
VTable for Derived→0 (int (*)(...))0→1 (int (*)(...))(& _ZTI7Derived)→2 (int (*)(...))Derived::show - 注意:索引从 0 开始,但第 0 项通常是 RTTI,真正第一个虚函数在索引 2(单继承无虚基类时)
- 这个文件是静态视图,不反映运行时地址,但能帮你快速定位 gdb 中该看第几个条目
虚表偏移不是固定数字,它取决于继承链深度、虚函数声明顺序、是否含虚基类——哪怕只加一个未实现的纯虚函数,整个 vtable 索引都会位移。动手前先 dump 一眼,省掉一半 gdb 试错时间。


















