gdb如何实现info vtbl命令

一、命令使用

在gdb中,可以通过info vtbl来查看一个指定对象指针的当前虚函数表信息。这里比较简单的问题是虚函数的位置,但是在多重继承甚至虚继承的情况下这个问题其实也并不简单。另一个基础问题是一个类型的虚函数表的大小。
下面是使用gdb的一个例子
tsecer@harry: cat gdb.parse.vtable.cpp
struct Base1
{
virtual void B11(){}
virtual void B12(){}
};

struct Base2
{
virtual void B21(){}
virtual void B22(){}
};

struct Base3
{
virtual void B31(){};
};

struct Derive: public Base1, Base2
{
virtual void B11() {int x = 0; x++;}
virtual void B22() {int x = 0; x++;}
virtual void D11() {int x = 0; x++;}
};

int main(int argc, const char *argv[])
{
Derive d;
Base3 b3;
return 0;
}
tsecer@harry: g++ -g gdb.parse.vtable.cpp
tsecer@harry: gdb ./a.out
GNU gdb (GDB) 10.1
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-pc-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
/home/harry/.gdbinit:2: Error in sourced command file:
Undefined command: "et". Try "help".
Reading symbols from ./a.out...
(gdb) start
Temporary breakpoint 1 at 0x4005f2: file gdb.parse.vtable.cpp, line 27.
Starting program: /home/harry/gdb.parse.vtable/a.out

Temporary breakpoint 1, main (argc=1, argv=0x7fffffffe3a8) at gdb.parse.vtable.cpp:27
27 Derive d;
(gdb) n
28 Base3 b3;
(gdb) n
29 return 0;
(gdb) info vtbl d
vtable for 'Derive' @ 0x400720 (subobject @ 0x7fffffffe2b0):
[0]: 0x400638 <Derive::B11()>
[1]: 0x400614 <Base1::B12()>
[2]: 0x40064e <Derive::B22()>
[3]: 0x40066a <Derive::D11()>

vtable for 'Base2' @ 0x400750 (subobject @ 0x7fffffffe2b8):
[0]: 0x400620 <Base2::B21()>
[1]: 0x400664 <non-virtual thunk to Derive::B22()>
(gdb) info vtbl (Base3*)&d
vtable for 'Base3' @ 0x400720 (subobject @ 0x7fffffffe2b0):
[0]: 0x400638 <Derive::B11()>
(gdb) info vtbl (Derive*)&b3
vtable for 'Derive' @ 0x400770 (subobject @ 0x7fffffffe2a8):
[0]: 0x40062c <Base3::B31()>
[1]: 0x601098 <vtable for __cxxabiv1::__vmi_class_type_info@@CXXABI_1.3+16>
[2]: 0x4007b0 <typeinfo name for Derive>
[3]: 0x200000000

vtable for 'Base2' @ 0x400720 (subobject @ 0x7fffffffe2b0):
[0]: 0x400638 <Derive::B11()>
[1]: 0x400614 <Base1::B12()>
(gdb)

二、虚函数表的起始位置

尽管gcc的实现更复杂,但是单单对于虚函数的位置来说,就是在对象最开始的一个字长中存储,所以虚函数表的位置不太难确定。

三、虚函数表的长度

直观上理解,可能debug信息中会保存一个虚函数表的完整信息,表示虚函数表的大小,但是事实上gdb并没有使用这种方法(可能是编译器没有生成,也可能不通用?)。
1、gdb如何知道哪个函数是虚函数
在DWARF文件格式中,函数通过一个DW_TAG_subprogram结构描述,对于虚函数来说,在这个结构的内部还有一个子结构DW_AT_vtable_elem_location,表示了该虚函数在虚函数中的相对偏移量。
2、虚函数表的大小
由于每个虚函数都以一个DW_TAG_subprogram条目,所以收集一个类的所有虚函数条目,就获得了虚函数表的大小。这里是一个归纳的方法,通过逐个累积获得所有虚函数的大小。
3、数值表示
在新的gcc版本中,生成的debug信息中包含的DW_AT_vtable_elem_location信息是相对于虚函数表起始地址的相对偏移位置,所以在处理的时候通过比较获得所有条目中的最大值即可获得表的上限。

四、基类的虚函数表

同样的,gcc在处理多重继承时,把除了第一个之外的额外内容其实是作为一个成员来存储的。或者说,一个类的直接父类是直接的、线性的叠加到父类存储空间之上,而其它父类是作为分支,内存布局上可以认为是一个field。
gdb对基类虚函数表大小的计算
static void
compute_vtable_size (htab_t offset_hash,
std::vector<value_and_voffset *> *offset_vec,
struct value *value)
{
……
/* Recurse into base classes. */
for (i = 0; i < TYPE_N_BASECLASSES (type); ++i)
compute_vtable_size (offset_hash, offset_vec, value_field (value, i));
}

五、gdb输出中subobject是什么

从代码可以看到,第一个@后的内容是虚函数表的起始地址,第二个@后的地址是对象/非直接父类的对象的起始地址
static void
print_one_vtable (struct gdbarch *gdbarch, struct value *value,
int max_voffset,
struct value_print_options *opts)
{
……
printf_filtered (_("vtable for '%s' @ %s (subobject @ %s):\n"),
TYPE_SAFE_NAME (type),
paddress (gdbarch, vt_addr),
paddress (gdbarch, (value_address (value)
+ value_embedded_offset (value))));
……
}

六、一些推论

每个对象的虚函数表是从对象地址中实时获取的,而不是根据类型信息静态确定的。
当一个对象出现异常的时候,可以先通过info vtbl看下虚函数表是否异常,如果虚函数表异常,很可能整个对象都不正常了。
当有一个指针对象,不太确定它具体指向的是什么类型的时候,可以通过虚函数表看下是否有效额外信息。
虚函数表条目数量是类型相关的,使用不同的类型来显示一个地址的虚函数表大小不相同。

posted on 2021-10-30 17:33  tsecer  阅读(652)  评论(0编辑  收藏  举报

导航