zoukankan      html  css  js  c++  java
  • C++反汇编-继承和多重继承

    学无止尽,积土成山,积水成渊-《C++反汇编与逆向分析技术揭秘》 读书笔记

    一、单类继承

    • 在父类中声明为私有的成员,子类对象无法直接访问,但是在子类对象的内存结构中,父类私有的成员数据依然存在。C++语法规定的访问限制仅限于编译层面,在编译过程中进行语法检查,因此访问控制不会影响对象的内存结构。
    • 子类未提供构造函数或析构函数,而父类却需要构造函数与析构函数时,编译器会为子类提供默认的构造函数与析构函数。但是子类有构造函数,而父类不存在构造函数,且没有虚函数,则编译器不会为父类提供默认的构造函数。
     

    1. 内存结构:

    ①先安排父类的数据
    ②后安排子类新定义的数据
     
    说明:基于上述的内存排列方法,即父类数据成员被安排前面。不管是父类的对象,还是子类的对象,父类的数据成员在内存中相对于对象的首地址的偏移值都是一样的。而且成员数据访问都是基于this指针间接寻址的。所以,对于子类对象而言,使用父类指针或者子类指针都可以正确访问其父类数据。
     

    2. 虚表

    • 虚表的排列顺序是按虚函数在类继承层次中首次声明的顺序依次排列的。
    • 只要继承了父类,其派生类的虚函数表中的父类部分的排列就与父类一样。
    • 重载的虚函数在虚函数表中得到了更新(即override)。
    • 子类新定义的虚函数会按照声明顺序紧跟其后。
     
     

    3. 构造函数

    ①先调用父类构造函数
    ②然后按照声明顺序调用成员数据变量的构造函数和初始化列表中指定的成员
    ③最后再执行子类构造函数的函数体。
     
    说明
    ①父类构造函数,虚表指针修改为指向父类的虚表,所以在父类构造函数内调用虚函数,调用的是父类的虚函数。
    ②子类构造函数,虚表指针修改为指向子类的虚表
     

    4. 析构函数

    ①先调用子类析造函数
    ②然后成员对象的析构函数,按照声明的顺序以倒序方式依次调用成员对象的析构函数。
    ③再执行父类构造函数
     
    说明
    • 析构函数执行会首先设置虚表指针为自身虚表,再调用自身的析构函数。防止父类析构函数内调用子类对象的虚函数。
    • 类有派生与继承关系,需要声明析构函数为虚函数。若析构函数不是虚函数时,当使用父类指针指向堆中的子类对象,并使用delete释放对象空间时,编译器会按照指针类型调用父类的析构函数,从而引发错误。
     
    识别类之间的关系:
    先定位构造函数,根据构造先后顺序得到与之相关的其他类。
    再根据虚表,利用IDA中使用引用参考功能可得到所有的构造和析构函数。
     
     

    二、多重继承

    1. 内存排列:

    • 数据成员的排列顺序由继承父类的先后顺序所决定,从左向右依次排列。
    • 子类虚表指针的个数取决于所继承的父类的个数,有几个父类便对应几个虚表指针(虚基类除外)。

     2.虚表

    • 每个父类都有一个虚表,有几个父类便对应几个虚表;
    • 子类的成员函数被放到了第一个父类的表中;
    • 如果某个父类的虚函数都被子类重写(override),则该父类的虚表中会更新为子类重写(override)后的函数地址。

     3.其他

    • 将一个子类对象赋值给某个父类指针时,该父类指针便指向该父类所对应的虚表指针。

    三、单类继承与多重继承比较:

    • 单继承类
      • 在类对象占用的内存空间中,只保存一份虚表指针
      • 只有一个虚表指针,对应的也只有一个虚表
      • 虚表中各项保存了类中各虚函数的首地址
      • 构造时先构造父类,再构造自身,并且只调用一次父类构造函数
      • 析构时限析构自身,再析构父类,并且只调用一次父类析构函数

    • 多重继承类
      • 在类中所占用的内存空间中,根据继承父类的个数保存对应的虚表指针
      • 根据所保存的虚表指针的个数,对应产生相应个数的虚表。
      • 转换父类指针时,需要跳转到对象的首地址。
      • 构造时需要按照继承顺序调用多个父类构造函数。
      • 析构时先析构自身,然后以与构造函数相反的顺序调用所有父类的析构函数
      • 当对象作为成员时,整个类对象的内存结构和多重继承相似。当类中无虚函数时,整个类对象内存结构和多重继承完全一样,可酌情还原;当父类或成员对象存在虚函数时,通过过观察虚表指针的位置和构造函数、析构函数中填写虚表指针的数目及目标地址,来还原继承或成员关系。

    四、 示例

    1. 单类继承:

    C++源码
     1 #include <iostream>
     2 using namespace std;
     3  
     4 class Base {
     5 public:
     6             Base(){ nBase= 1;printf("CBase"); }
     7             ~Base(){ printf("~CBase"); }
     8             virtual void f() { printf("Base:f()");}
     9             virtual void g() { printf("Base:g()");}
    10 private:
    11             int nBase;
    12  
    13 };
    14  
    15 
    16 class Derive : public Base {
    17 public:
    18             Derive(){ nDerive=2;printf("Derive"); }
    19             ~Derive(){ printf("~Derive"); }
    20             virtual void g(){ printf("Dervie:g()");}
    21             virtual void h(){ printf("Dervie:h()");}
    22 private:
    23             int nDerive;
    24 };
    25  
    26  
    27  
    28 int main()
    29 {
    30             Derive d;
    31             Base *b = &d;
    32             b->g();
    33             return 0;

    34 } 

     汇编代码(VS2010编译)

     1. 内存分布

    1 类Derive对象
    2 0019FD30  0139583C  =>.rdata:const Derive::`vftable'
    3 0019FD34  00000001  =>Base.nBase
    4 0019FD38  00000002  =>Derive.nDerive
    5  
    6 虚函数表
    7 0139583C  01391163  Base::f(void)
    8 01395840  0139110E  Derive::g(void) ;Derive类重写(override)后的函数地址
    9 01395844  013911AE  Derive::h(void) ;Derive类重写(override)后的函数地址

     2. 构造函数

    1 pop     ecx                    ;=>this指针出栈
    2 mov     [ebp+this], ecx        ;=>保存this指针
    3 mov     ecx, [ebp+this]        
    4 call    j_??0Base@@QAE@XZ      ;=>调用基类构造函数Base::Base(void)
    5 mov     eax, [ebp+this]        ;=>eax=this指针
    6 mov     dword ptr [eax], offset ??_7Derive@@6B@ ;=>初始化虚表指针为const Derive::`vftable'

     

    3. 析构函数

     1 pop     ecx                    ;=>this指针出栈
     2 mov     [ebp+this], ecx        ;=>保存this指针
     3 mov     eax, [ebp+this]
     4 mov     dword ptr [eax], offset ??_7Derive@@6B@  ;=>重置虚表指针为const Derive::`vftable'
     5 mov     esi, esp
     6 push    offset aDerive  ; "~Derive"
     7 call    ds:__imp__printf
     8 add     esp, 4
     9 cmp     esi, esp
    10 call    j___RTC_CheckEsp
    11 mov     ecx, [ebp+this]        ;=>ecx传参this指针    
    12 call    j_??1Base@@QAE@XZ      ;=>调用基类析构函数 Base::~Base(void)

     

    2. 多重继承: 

    C++源码
     1 #include <iostream>
     2 using namespace std;
     3  
     4 class Base1 {
     5 public:
     6             virtual void f() { cout << "Base1::f" << endl; }
     7             virtual void g() { cout << "Base1::g" << endl; }
     8             Base1(){b1 = 1; printf("Base1"); }
     9             ~Base1(){ printf("~Base1"); }
    10 private:
    11             int b1;
    12  
    13 };
    14  
    15 class Base2 {
    16 public:
    17             virtual void f() { cout << "Base2::f" << endl; }
    18             virtual void g() { cout << "Base2::g" << endl; }
    19             Base2(){b2 = 2; printf("Base2"); }
    20             ~Base2(){ printf("~Base2"); }
    21 private:
    22             int b2;
    23 };
    24  
    25 class Derive : public Base1, public Base2{
    26 public:
    27             virtual void f() { cout << "Derive::f" << endl; }
    28             virtual void g1() { cout << "Derive::g1" << endl; }
    29             Derive(){ d1 = 3; printf("Derive"); }
    30             ~Derive(){ printf("~Derive"); }
    31 private:
    32             int d1;
    33             
    34 };
    35  
    36 typedef void(*Fun)(void);
    37  
    38 int main()
    39 {
    40  
    41             Derive d;
    42             Base1 *b1 = &d;
    43             b1->f();
    44             b1->g();
    45             Base2 *b2 = &d;
    46             b2->f();
    47             b2->g();
    48             return 0;
    49 }
    
    汇编代码(VS2010编译) 

     1.内存分布

    ;内存布局
    0019FA0C  008F584C    ;=>.rdata:const Derive::`vftable'{for `Base1'} 第一个虚表
    0019FA10  00000001    ;=>Base1.b1
    0019FA14  008F583C    ;=>.rdata:const Derive::`vftable'{for `Base2'} 第二个虚表
    0019FA18  00000002    ;=>Base2.b2
    0019FA1C  00000003    ;=>Derive.d1
     
     
    ;第一个虚函数表Derive::`vftable'{for `Base1'}
    00FB584C  00FB1041  ;=>Derive::f(void), Dervie类重写(override)后的函数地址
    00FB5850  00FB1118  ;=>Base1::g(void)
    00FB5854  00FB111D  ;=>Derive::g1(void), Derive类中新添加的虚函数地址
     
    ;第二个虚函数表Derive::`vftable'{for `Base2'}
    00FB583C  00FB1113   ;=>Base2::g(void)
     00FB5840  00FB1028  ;=>[thunk]:Derive::f`adjustor{8}' (void)
    ;追踪地址:00FB1028
    00FB1028 jmp     ?f@Derive@@W7AEXXZ    ;=>[thunk]:Derive::f`adjustor{8}' (void)
    ;追踪函数:?f@Derive@@W7AEXXZ
    00FB1F30 ?f@Derive@@W7AEXXZ proc near
    00FB1F30 sub     ecx, 8                 ;=>调整this指针为this+8,即ecx-->Derive::`vftable'{for `Base2'}
    00FB1F33 jmp     j_?f@Derive@@UAEXXZ    ;=>Derive::f(void),即Derive类重写后的函数地址

     

     

    2.构造函数

     1 00FB14D9 mov     [ebp-4], ecx            ;=>this指针保存在esp-4处
     2 00FB14DC mov     ecx, [ebp-4]           ;=>ecx获得this指针
     3 00FB14DF call    j_??0Base1@@QAE@XZ      ;=>调用构造函数 Base1::Base1(void)
     4 00FB14E4 mov     ecx, [ebp-4]           ;=>ecx获得this指针
     5 00FB14E7 add     ecx, 8                 ;=>ecx获得this+8
     6 00FB14EA call    j_??0Base2@@QAE@XZ      ;=>调用构造函数 Base2::Base2(void)
     7 00FB14EF mov     eax, [ebp-4]            ;=>eax获得this指针
     8 00FB14F2 mov     dword ptr [eax], offset ??_7Derive@@6BBase1@@@      ;=>初始化第一个虚表指针为const Derive::`vftable'{for `Base1'}
     9 00FB14F8 mov     eax, [ebp-4]            ;=>eax获得this指针
    10 00FB14FB mov     dword ptr [eax+8], offset ??_7Derive@@6BBase2@@@    ;=>初始化第二个虚表指针const Derive::`vftable'{for `Base2'}
    11 00FB1502 mov     eax, [ebp-4]            ;=>eax获得this指针
    12 00FB1505 mov     dword ptr [eax+10h], 3
    13 00FB150C push    offset Format   ; "Derive"
    14 00FB1511 call    ds:__imp__printf
    15 00FB1517 add     esp, 4

    3.析构函数

    00FB17C9 mov     [ebp-4], ecx        ;=>this指针保存在esp-4处
    00FB17CC mov     eax, [ebp-4]        ;=>ecx获得this指针
    00FB17CF mov     dword ptr [eax], offset ??_7Derive@@6BBase1@@@        ;=>重置第一个虚表指针为const Derive::`vftable'{for `Base1'}
    00FB17D5 mov     eax, [ebp-4]
    00FB17D8 mov     dword ptr [eax+8], offset ??_7Derive@@6BBase2@@@      ;=>重置第二个虚表指针为const Derive::`vftable'{for `Base2'}
    00FB17DF push    offset aDerive  ; "~Derive"
    00FB17E4 call    ds:__imp__printf
    00FB17EA add     esp, 4
    00FB17ED mov     ecx, [ebp-4]             ;=>ecx获得this指针
    00FB17F0 add     ecx, 8                   ;=>ec;得this+8
    00FB17F3 call    j_??1Base2@@QAE@XZ       ;=>调用虚构函数Base2::~Base2(void)
    00FB17F8 mov     ecx, [ebp-4]             ;=>ecx获得this指针
    00FB17FB call    j_??1Base1@@QAE@XZ       ;=>调用虚构函数Base1::~Base1(void)

     

    4.虚函数调用

     

    ;Base1 *b1 = &d;
    00FB1431 lea     eax, [ebp-14h]        ;=>eax获得this指针
    00FB1434 mov     [ebp-18h], eax        ;=>局部变量b1赋值为this指针
    
    ;b1->f();
    00FB1437 mov     eax, [ebp-18h]        ;=>eax获得b1值
    00FB143A mov     edx, [eax]            ;=>edx指向第一个虚表
    00FB143C mov     ecx, [ebp-18h]        ;=>ecx传递this指针
    00FB143F mov     eax, [edx]            ;=>eax获得成员函数Derive::f(void)的地址
    00FB1441 call    eax                   ;=>调用成员函数Derive::f(void)
    
    ;b1->g();
    00FB1443 mov     eax, [ebp-18h]        ;=>eax获得b1值
    00FB1446 mov     edx, [eax]            ;=>edx指向第一个虚表
    00FB1448 mov     ecx, [ebp-18h]        ;=>ecx传递this指针
    00FB144B mov     eax, [edx+4]          ;=>eax获得成员函数Derive::g(void)的地址
    00FB144E call    eax                   ;=>调用成员函数Derive::g(void)
    
    ;Base2 *b2 = &d;
    00FB1457 lea     ecx, [ebp-14h]        ;=>ecx获得this指针
    00FB145A add     ecx, 8                ;=>ecx=this+8指向第二个虚表
    00FB145D mov     [ebp-64h]             ;=>保存ecx到临时变量中
    00FB1460 jmp     short loc_FB1469;----|
                                          ;|
    00FB1469 mov     edx, [ebp-64h];<----|    
    00FB146C mov     [ebp-1Ch], edx        ;=>局部变量b2在栈[ebp-1Ch],赋值为this+8,指向第二个虚表
    
    ;b2->f();
    00FB146F mov     eax, [ebp-1Ch]        ;=>eax获得b2值
    00FB1472 mov     edx, [eax]            ;=>edx获得指向第二个虚表
    00FB1474 mov     ecx, [ebp-1Ch]        ;=>ecx传参this+8
    00FB1477 mov     eax, [edx+4]          ;=>eax为第二个虚表的第二个元素=>[thunk]:Derive::f`adjustor{8}' (void),然后间接调用Derive::f(void)
    00FB147A call    eax
    
    ;b2->g();
    00FB147C mov     eax, [ebp+b2]         ; =>eax获得b2值
    00FB147F mov     edx, [eax]            ;=>edx获得指向第二个虚表
    00FB1481 mov     ecx, [ebp+b2]         ;=>ecx传参this+8
    00FB1484 mov     eax, [edx]            ;=>eax为第二个虚表的第一个元素=>Base2::g(void)
    00FB1486 call    eax                   ;=>调用Base2::g(void)

     

     

     

     

     

  • 相关阅读:
    hdu 3335 Divisibility
    最小点覆盖,最小路径覆盖
    hdu 4109 Instrction Arrangement
    sjtu 1077 加分二叉树
    hdu 1542 Atlantis
    多线程中互斥体
    在子页面中,javascript让模板页中添加的用户控件中的控件选中focus
    模板页中用javascript判断是否为空
    控件包含代码块(即 <% ... %>),因此无法修改控件集合 asp.net
    lambda从指定集合中去除指定数据 asp.net
  • 原文地址:https://www.cnblogs.com/bigrabbit/p/3461430.html
Copyright © 2011-2022 走看看