IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    使用GDB查找虚函数表

    armsword发表于 2014-11-23 09:51:54
    love 0

    大学时候刚接触编程时候,用的是微软系列的编译器,现在虽然经常在linux 下折腾代码,但是还是认为微软系列的编译器对用户来说,真的是太方便了。现在许多人都是啥vim党,emacs党派,各种秀操作,秀技巧,还有索性从来不用鼠标,其实我感觉对于普通人来说,这是没什么意义的,鼠标和图形发明出来就是为了更好的工作和生活,何乐而不为呢,用了2天的Sublime Text 2,我感觉很棒,这里推荐下。 以上是瞎唠叨两句,主要是我想用gdb看下C++的虚函数表,折腾了半天还没折腾明白,我记得当我刚接触VC6.0的时候,在Memory窗口很容易找到虚函数表啊。下面写点无逻辑的关于GDB调试虚函数的文章吧,本文无逻辑可言,可能一些关键点是给自己备忘用的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    #include
    #include
    using namespace std;
    class Base
    {
    public:
    virtual void A()
    {
    printf("This is Base A\n");
    }
    void B()
    {
    printf("This is Base B\n");
    }
    };
    class D:public Base
    {
    public:
    void A()
    {
    printf("This is D A\n");
    }
    void B()
    {
    printf("This is D B\n");
    }
    };
    int main()
    {
    D cClass;
    Base *pClass = &cClass;
    pClass->A();
    pClass->B();
    return 0;
    }

    编译,运行后,结果为:

    1
    2
    3
    imlinuxer@imlinuxer:~/source$ ./Virtual
    This is D A
    This is Base B

    我们使用GDB调试下,使用disassemble 查看下main函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    Reading symbols from ./Virtual...done.
    (gdb) disassemble main
    Dump of assembler code for function main():
    0x0804865d <+0>: push %ebp
    0x0804865e <+1>: mov %esp,%ebp
    0x08048660 <+3>: and $0xfffffff0,%esp
    0x08048663 <+6>: sub $0x20,%esp
    0x08048666 <+9>: lea 0x18(%esp),%eax
    0x0804866a <+13>: mov %eax,(%esp)
    0x0804866d <+16>: call 0x8048744
    0x08048672 <+21>: lea 0x18(%esp),%eax
    0x08048676 <+25>: mov %eax,0x1c(%esp)
    0x0804867a <+29>: mov 0x1c(%esp),%eax
    0x0804867e <+33>: mov (%eax),%eax
    0x08048680 <+35>: mov (%eax),%eax
    0x08048682 <+37>: mov 0x1c(%esp),%edx
    0x08048686 <+41>: mov %edx,(%esp)
    0x08048689 <+44>: call *%eax
    0x0804868b <+46>: mov 0x1c(%esp),%eax
    0x0804868f <+50>: mov %eax,(%esp)
    0x08048692 <+53>: call 0x804870e
    0x08048697 <+58>: mov $0x0,%eax
    0x0804869c <+63>: leave
    0x0804869d <+64>: ret
    End of assembler dump.
    (gdb)

    其实为了让代码对应相应的汇编,可以用 disassemble /m main 查看,我们查看下0x0804866d <+16>:call 0x8048744 ,我们也看到了其调用了基类构造函数,对应的汇编代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    (gdb) disassemble /m 0x8048744
    Dump of assembler code for function D::D():
    19 class D:public Base
    0x08048744 <+0>: push %ebp
    0x08048745 <+1>: mov %esp,%ebp
    0x08048747 <+3>: sub $0x18,%esp
    0x0804874a <+6>: mov 0x8(%ebp),%eax
    0x0804874d <+9>: mov %eax,(%esp)
    0x08048750 <+12>: call 0x8048736 // 0x8048736 : 0x8be58955 0xc70845 0x8048838 <_ZTV4Base+8>
    0x08048755 <+17>: mov 0x8(%ebp),%eax
    0x08048758 <+20>: movl $0x8048828,(%eax) // 0x8048828 <_ZTV1D+8>: 0x8048722 0x0 0x0
    0x0804875e <+26>: leave
    0x0804875f <+27>: ret
    End of assembler dump.
    (gdb) disassemble 0x8048736
    Dump of assembler code for function Base::Base():
    0x08048736 <+0>: push %ebp
    0x08048737 <+1>: mov %esp,%ebp
    0x08048739 <+3>: mov 0x8(%ebp),%eax
    0x0804873c <+6>: movl $0x8048838,(%eax)
    0x08048742 <+12>: pop %ebp
    0x08048743 <+13>: ret
    End of assembler dump.
    (gdb)

    我们查看下0x8048838,使用x/3aw命令,这个命令需要解释下,这个命令表示从内存地址读取内容,w表示以四字节为一个单位,3表示连续读取三个单位,a表示按照十进制显示:

    1
    2
    3
    (gdb) x/3aw 0x8048838
    0x8048838 <_ZTV4Base+8>:0x80486fa &lt;Base::A()&gt; 0x4431 0x804a088 &lt;_ZTVN10__cxxabiv120__si_class_type_infoE@@CXXABI_1.3+8&gt;
    (gdb)

    继续查看:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    (gdb) disassemble 0x8048838
    Dump of assembler code for function _ZTV4Base:
    0x08048830 <+0>: add %al,(%eax)
    0x08048832 <+2>: add %al,(%eax)
    0x08048834 <+4>: push %esp
    0x08048835 <+5>: mov %al,(%eax,%ecx,1)
    0x08048838 <+8>: cli
    0x08048839 <+9>: xchg %al,(%eax,%ecx,1)
    End of assembler dump.
    (gdb) disassemble 0x80486fa
    Dump of assembler code for function Base::A():
    0x080486fa <+0>: push %ebp
    0x080486fb <+1>: mov %esp,%ebp
    0x080486fd <+3>: sub $0x18,%esp
    0x08048700 <+6>: movl $0x80487f0,(%esp)
    0x08048707 <+13>: call 0x8048550
    0x0804870c <+18>: leave
    0x0804870d <+19>: ret
    End of assembler dump.

    _ZTV4Base 是什么东西?我们用c++filt查看下:

    1
    2
    imlinuxer@imlinuxer:~$ c++filt _ZTV4Base
    vtable for Base

    嘿,原来这个东西是Base的虚函数表,嘿嘿,终于找到了~

    刚才笔记本电脑CPU温度过高,死机了,于是刚才写的东西部分丢失了,看了上文好些没有缺少太多东西,博客自动保持不是太靠谱,思路也就乱了。算了,不再分析了。用objdump查看下虚函数表的位置吧:

    1
    2
    3
    4
    5
    6
    imlinuxer@imlinuxer:~/source$ objdump -s -x -d Virtual | c++filt | grep "vtable"
    08048820 w O .rodata 0000000c vtable for D
    0804a040 w O .bss 0000002c vtable for __cxxabiv1::__class_type_info@@CXXABI_1.3
    0804a080 w O .bss 0000002c vtable for __cxxabiv1::__si_class_type_info@@CXXABI_1.3
    08048830 w O .rodata 0000000c vtable for Base

    我们发现虚函数表vtable在Linux/Unix中存放在可执行文件的只读数据段中(rodata),而微软的编译器将虚函数表存放在常量段,这是存在一些差别的。



沪ICP备19023445号-2号
友情链接