前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >《coredump问题原理探究》Linux x86版6.7节多继承

《coredump问题原理探究》Linux x86版6.7节多继承

作者头像
血狼debugeeker
发布2018-09-20 14:45:00
4350
发布2018-09-20 14:45:00
举报
文章被收录于专栏:debugeeker的专栏

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://cloud.tencent.com/developer/article/1344553

类的多继承大致可以分为两种情况.一种是无共同基类的.一种是有共同基类的.

先看一下第一种情况:

代码语言:javascript
复制
  1 #include <stdio.h>
  2 class xuzhina_dump_c06_s5_mother
  3 {
  4     private:
  5         int m_age;
  6         int m_beauty;
  7     public:
  8         virtual void print()
  9         {
 10             printf( "mother\n" );
 11         }
 12 
 13         virtual void setBeauty( int age, int beauty )
 14         {
 15             m_age = age - 5;
 16             m_beauty = beauty;
 17         }
 18 };
19 
 20 class xuzhina_dump_c06_s5_father
 21 {
 22     private:
 23         int m_strong;
 24         int m_age;
 25     public:
 26         virtual void print()
 27         {
 28             printf( "father\n" );
 29         }
 30         virtual void setStrong( int strong, int age )
 31         {
 32             m_strong = strong;
 33             m_age = age;
 34         }
 35 };
 36
37 class xuzhina_dump_c06_s5_child: public xuzhina_dump_c06_s5_father,
 38     public xuzhina_dump_c06_s5_mother
 39 
 40 {
 41     private:
 42         bool m_newMind;
 43     public:
 44         virtual void print()
 45         {
 46             printf( "child\n" );
 47         }
 48 
 49         virtual void setGender( bool gender )
 50         {
 51             m_newMind = true;
 52             if ( gender )
 53             {
 54                 setBeauty( 10, 10 );
 55             }
 56             else
 57             {
 58                 setStrong( 20,20 );
 59             }
 60         }
 61 };
 62
63 int main()
 64 {
 65     xuzhina_dump_c06_s5_child* child = new xuzhina_dump_c06_s5_child;
 66     child->setGender( false );
 67     child->print();
 68 
 69     xuzhina_dump_c06_s5_father* f = child;
 70     f->print();
 71 
 72     xuzhina_dump_c06_s5_mother* m = child;
 73     m->print();
 74 
 75     return 0;
 76 }

看一下main函数的汇编:

代码语言:javascript
复制
(gdb) disassemble main
Dump of assembler code for function main:
   0x080485b0 <+0>:     push   %ebp
   0x080485b1 <+1>:     mov    %esp,%ebp
   0x080485b3 <+3>:     push   %ebx
   0x080485b4 <+4>:     and    $0xfffffff0,%esp
   0x080485b7 <+7>:     sub    $0x20,%esp
   0x080485ba <+10>:    movl   $0x1c,(%esp)
   0x080485c1 <+17>:    call   0x8048490 <_Znwj@plt>
   0x080485c6 <+22>:    mov    %eax,%ebx
   0x080485c8 <+24>:    mov    %ebx,(%esp)
   0x080485cb <+27>:    call   0x8048746 <_ZN25xuzhina_dump_c06_s5_childC2Ev>
   0x080485d0 <+32>:    mov    %ebx,0x1c(%esp)
   0x080485d4 <+36>:    mov    0x1c(%esp),%eax
   0x080485d8 <+40>:    mov    (%eax),%eax
   0x080485da <+42>:    add    $0x8,%eax
   0x080485dd <+45>:    mov    (%eax),%eax
   0x080485df <+47>:    movl   $0x0,0x4(%esp)
   0x080485e7 <+55>:    mov    0x1c(%esp),%edx
   0x080485eb <+59>:    mov    %edx,(%esp)
   0x080485ee <+62>:    call   *%eax
   0x080485f0 <+64>:    mov    0x1c(%esp),%eax
   0x080485f4 <+68>:    mov    (%eax),%eax
   0x080485f6 <+70>:    mov    (%eax),%eax
   0x080485f8 <+72>:    mov    0x1c(%esp),%edx
   0x080485fc <+76>:    mov    %edx,(%esp)
   0x080485ff <+79>:    call   *%eax
   0x08048601 <+81>:    mov    0x1c(%esp),%eax
   0x08048605 <+85>:    mov    %eax,0x18(%esp)
   0x08048609 <+89>:    mov    0x18(%esp),%eax
   0x0804860d <+93>:    mov    (%eax),%eax
   0x0804860f <+95>:    mov    (%eax),%eax
   0x08048611 <+97>:    mov    0x18(%esp),%edx
   0x08048615 <+101>:   mov    %edx,(%esp)
   0x08048618 <+104>:   call   *%eax
   0x0804861a <+106>:   cmpl   $0x0,0x1c(%esp)
   0x0804861f <+111>:   je     0x804862a <main+122>
   0x08048621 <+113>:   mov    0x1c(%esp),%eax
   0x08048625 <+117>:   add    $0xc,%eax
   0x08048628 <+120>:   jmp    0x804862f <main+127>
   0x0804862a <+122>:   mov    $0x0,%eax
   0x0804862f <+127>:   mov    %eax,0x14(%esp)
   0x08048633 <+131>:   mov    0x14(%esp),%eax
   0x08048637 <+135>:   mov    (%eax),%eax
   0x08048639 <+137>:   mov    (%eax),%eax
   0x0804863b <+139>:   mov    0x14(%esp),%edx
   0x0804863f <+143>:   mov    %edx,(%esp)
   0x08048642 <+146>:   call   *%eax
   0x08048644 <+148>:   mov    $0x0,%eax
   0x08048649 <+153>:   mov    -0x4(%ebp),%ebx
   0x0804864c <+156>:   leave  
   0x0804864d <+157>:   ret    
End of assembler dump.

由上面的汇编,可以看到,对象child的地址存放在esp+0x1c

而下面这几条指令

代码语言:javascript
复制
   0x08048601 <+81>:    mov    0x1c(%esp),%eax
   0x08048605 <+85>:    mov    %eax,0x18(%esp)
   0x08048609 <+89>:    mov    0x18(%esp),%eax
   0x0804860d <+93>:    mov    (%eax),%eax
   0x0804860f <+95>:    mov    (%eax),%eax
   0x08048611 <+97>:    mov    0x18(%esp),%edx
   0x08048615 <+101>:   mov    %edx,(%esp)
   0x08048618 <+104>:   call   *%eax

   0x08048621 <+113>:   mov    0x1c(%esp),%eax
   0x08048625 <+117>:   add    $0xc,%eax
   0x0804862f <+127>:   mov    %eax,0x14(%esp)
   0x08048633 <+131>:   mov    0x14(%esp),%eax
   0x08048637 <+135>:   mov    (%eax),%eax
   0x08048639 <+137>:   mov    (%eax),%eax
   0x0804863b <+139>:   mov    0x14(%esp),%edx
   0x0804863f <+143>:   mov    %edx,(%esp)
   0x08048642 <+146>:   call   *%eax

由于是和代码

代码语言:javascript
复制
69     xuzhina_dump_c06_s5_father* f = child;
 70     f->print();
 71 
 72     xuzhina_dump_c06_s5_mother* m = child;
 73     m->print();

相对应的.

代码语言:javascript
复制
   0x08048625 <+117>:   add    $0xc,%eax

可以看到非常奇怪的现象,当类xuzhina_dump_c06_s5_child的指针转换成类xuzhina_dump_c06_s5­_mother的指针时,并不是直接赋值过去,而是比预料的地址加了一个偏移值.

那么这个偏移值0xC是怎么来的呢?根据上一节的经验,看一下构造函数的汇编:

代码语言:javascript
复制
(gdb) disassemble _ZN25xuzhina_dump_c06_s5_childC2Ev
Dump of assembler code for function _ZN25xuzhina_dump_c06_s5_childC2Ev:
   0x08048746 <+0>:     push   %ebp
   0x08048747 <+1>:     mov    %esp,%ebp
   0x08048749 <+3>:     sub    $0x18,%esp
   0x0804874c <+6>:     mov    0x8(%ebp),%eax
   0x0804874f <+9>:     mov    %eax,(%esp)
   0x08048752 <+12>:    call   0x804872a <_ZN26xuzhina_dump_c06_s5_fatherC2Ev>
   0x08048757 <+17>:    mov    0x8(%ebp),%eax
   0x0804875a <+20>:    add    $0xc,%eax
   0x0804875d <+23>:    mov    %eax,(%esp)
   0x08048760 <+26>:    call   0x8048738 <_ZN26xuzhina_dump_c06_s5_motherC2Ev>
   0x08048765 <+31>:    mov    0x8(%ebp),%eax
   0x08048768 <+34>:    movl   $0x8048848,(%eax)
   0x0804876e <+40>:    mov    0x8(%ebp),%eax
   0x08048771 <+43>:    movl   $0x804885c,0xc(%eax)
   0x08048778 <+50>:    leave  
   0x08048779 <+51>:    ret    
End of assembler dump.

由上面,类xuzhina_dump_c06_s5_child的对象实际就是两个类xuzhina_dump_c06_s5_father,xuzhina_dump_c06_s5_mother的对象组合,偏移值0xC刚好是类xuzhina_dump_c06_s5_father的大小(虚函数表指针+两个成员变量m_strong,m_age).

且由0x08048768, 0x08048771两条指令来看,类xuzhina_dump_c06_s5_child存放两个虚函数表,分别存放在0x8048848, 0x804885c.

看一下这两个表分别存放着什么东西.

第一个:

代码语言:javascript
复制
(gdb) x /4x 0x8048848
0x8048848 <_ZTV25xuzhina_dump_c06_s5_child+8>:  0x080486a8      0x08048690      0x080486c4      0xfffffff4
(gdb) info symbol 0x080486a8
xuzhina_dump_c06_s5_child::print() in section .text of /home/buckxu/work/6/5/xuzhina_dump_c6_s5
(gdb) info symbol 0x08048690
xuzhina_dump_c06_s5_father::setStrong(int, int) in section .text of /home/buckxu/work/6/5/xuzhina_dump_c6_s5
(gdb) info symbol 0x080486c4
xuzhina_dump_c06_s5_child::setGender(bool) in section .text of /home/buckxu/work/6/5/xuzhina_dump_c6_s5

第二个:

代码语言:javascript
复制
(gdb) x /4x 0x804885c
0x804885c <_ZTV25xuzhina_dump_c06_s5_child+28>: 0x080486bc      0x08048662      0x00000000      0x00000000
(gdb) info symbol 0x080486bc
non-virtual thunk to xuzhina_dump_c06_s5_child::print() in section .text of /home/buckxu/work/6/5/xuzhina_dump_c6_s5
(gdb) info symbol 0x08048662
xuzhina_dump_c06_s5_mother::setBeauty(int, int) in section .text of /home/buckxu/work/6/5/xuzhina_dump_c6_s5
(gdb) disassemble 0x080486bc
Dump of assembler code for function _ZThn12_N25xuzhina_dump_c06_s5_child5printEv:
   0x080486bc <+0>:     subl   $0xc,0x4(%esp)
   0x080486c1 <+5>:     jmp    0x80486a8 <_ZN25xuzhina_dump_c06_s5_child5printEv>
End of assembler dump. 

从上面看,类xuzhina_dump_c06_s5_child的对象内存分布如下:

由上可知,在多继承情况下,有这样的结论:

1.     有多个虚函数表.

2.     类对象的大小就是等于各个基类的大小(虚函数表指针+成员变量)与自身成员变量之和.

3.     各个基类在子类里的”隐含对象”顺序是按照继承顺序来排列,和基类的声明/定义顺序无关.

4.     当子类对象指针转换成基类指针,实际上是把子类对象的对应基类”隐含对象”地址赋值给基类指针.

对于有共同基类的多继承,也可以按照上面思路来探索.对于基类和子类有同名虚函数,也可以这样探索.

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2015年02月08日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档