C++反汇编-继承和多重继承
学无止尽,积土成山,积水成渊-《C++反汇编与逆向分析技术揭秘》 读书笔记
一、单类继承
- 在父类中声明为私有的成员,子类对象无法直接访问,但是在子类对象的内存结构中,父类私有的成员数据依然存在。C++语法规定的访问限制仅限于编译层面,在编译过程中进行语法检查,因此访问控制不会影响对象的内存结构。
- 子类未提供构造函数或析构函数,而父类却需要构造函数与析构函数时,编译器会为子类提供默认的构造函数与析构函数。但是子类有构造函数,而父类不存在构造函数,且没有虚函数,则编译器不会为父类提供默认的构造函数。
1. 内存结构:
2. 虚表:
- 虚表的排列顺序是按虚函数在类继承层次中首次声明的顺序依次排列的。
- 只要继承了父类,其派生类的虚函数表中的父类部分的排列就与父类一样。
- 被重载的虚函数在虚函数表中得到了更新(即override)。
- 子类新定义的虚函数会按照声明顺序紧跟其后。
3. 构造函数:
4. 析构函数:
- 析构函数执行会首先设置虚表指针为自身虚表,再调用自身的析构函数。防止父类析构函数内调用子类对象的虚函数。
- 类有派生与继承关系,需要声明析构函数为虚函数。若析构函数不是虚函数时,当使用父类指针指向堆中的子类对象,并使用delete释放对象空间时,编译器会按照指针类型调用父类的析构函数,从而引发错误。
二、多重继承
1. 内存排列:
- 数据成员的排列顺序由继承父类的先后顺序所决定,从左向右依次排列。
- 子类虚表指针的个数取决于所继承的父类的个数,有几个父类便对应几个虚表指针(虚基类除外)。
2.虚表
- 每个父类都有一个虚表,有几个父类便对应几个虚表;
- 子类的成员函数被放到了第一个父类的表中;
- 如果某个父类的虚函数都被子类重写(override),则该父类的虚表中会更新为子类重写(override)后的函数地址。
3.其他
- 将一个子类对象赋值给某个父类指针时,该父类指针便指向该父类所对应的虚表指针。
三、单类继承与多重继承比较:
- 单继承类
- 在类对象占用的内存空间中,只保存一份虚表指针
- 只有一个虚表指针,对应的也只有一个虚表
- 虚表中各项保存了类中各虚函数的首地址
- 构造时先构造父类,再构造自身,并且只调用一次父类构造函数
- 析构时限析构自身,再析构父类,并且只调用一次父类析构函数
- 多重继承类
- 在类中所占用的内存空间中,根据继承父类的个数保存对应的虚表指针
- 根据所保存的虚表指针的个数,对应产生相应个数的虚表。
- 转换父类指针时,需要跳转到对象的首地址。
- 构造时需要按照继承顺序调用多个父类构造函数。
- 析构时先析构自身,然后以与构造函数相反的顺序调用所有父类的析构函数
- 当对象作为成员时,整个类对象的内存结构和多重继承相似。当类中无虚函数时,整个类对象内存结构和多重继承完全一样,可酌情还原;当父类或成员对象存在虚函数时,通过过观察虚表指针的位置和构造函数、析构函数中填写虚表指针的数目及目标地址,来还原继承或成员关系。
四、 示例
1. 单类继承:
C++源码
- 1 #include <iostream>
- 2 using namespace std;
- 3
- 4 class Base {
- 5 public:
- 6 Base(){ nBase= ;printf("CBase"); }
- 7 ~Base(){ printf("~CBase"); }
- 8 virtual void f() { printf("Base:f()");}
- 9 virtual void g() { printf("Base:g()");}
- private:
- int nBase;
- };
- class Derive : public Base {
- public:
- Derive(){ nDerive=;printf("Derive"); }
- ~Derive(){ printf("~Derive"); }
- virtual void g(){ printf("Dervie:g()");}
- virtual void h(){ printf("Dervie:h()");}
- private:
- int nDerive;
- };
- int main()
- {
- Derive d;
- Base *b = &d;
- b->g();
- return ;
34 }
汇编代码(VS2010编译)
1. 内存分布
- 类Derive对象
- 0019FD30 0139583C =>.rdata:const Derive::`vftable'
- 0019FD34 00000001 =>Base.nBase
- 0019FD38 00000002 =>Derive.nDerive
- 虚函数表
- 0139583C 01391163 Base::f(void)
- 01395840 0139110E Derive::g(void) ;Derive类重写(override)后的函数地址
- 01395844 013911AE Derive::h(void) ;Derive类重写(override)后的函数地址
2. 构造函数
- pop ecx ;=>this指针出栈
- mov [ebp+this], ecx ;=>保存this指针
- mov ecx, [ebp+this]
- call j_??0Base@@QAE@XZ ;=>调用基类构造函数Base::Base(void)
- mov eax, [ebp+this] ;=>eax=this指针
- mov dword ptr [eax], offset ??_7Derive@@6B@ ;=>初始化虚表指针为const Derive::`vftable'
3. 析构函数
- pop ecx ;=>this指针出栈
- mov [ebp+this], ecx ;=>保存this指针
- mov eax, [ebp+this]
- mov dword ptr [eax], offset ??_7Derive@@6B@ ;=>重置虚表指针为const Derive::`vftable'
- mov esi, esp
- push offset aDerive ; "~Derive"
- call ds:__imp__printf
- add esp,
- cmp esi, esp
- call j___RTC_CheckEsp
- mov ecx, [ebp+this] ;=>ecx传参this指针
- call j_??1Base@@QAE@XZ ;=>调用基类析构函数 Base::~Base(void)
2. 多重继承:
C++源码
- #include <iostream>
- using namespace std;
- class Base1 {
- public:
- virtual void f() { cout << "Base1::f" << endl; }
- virtual void g() { cout << "Base1::g" << endl; }
- Base1(){b1 = ; printf("Base1"); }
- ~Base1(){ printf("~Base1"); }
- private:
- int b1;
- };
- class Base2 {
- public:
- virtual void f() { cout << "Base2::f" << endl; }
- virtual void g() { cout << "Base2::g" << endl; }
- Base2(){b2 = ; printf("Base2"); }
- ~Base2(){ printf("~Base2"); }
- private:
- int b2;
- };
- class Derive : public Base1, public Base2{
- public:
- virtual void f() { cout << "Derive::f" << endl; }
- virtual void g1() { cout << "Derive::g1" << endl; }
- Derive(){ d1 = ; printf("Derive"); }
- ~Derive(){ printf("~Derive"); }
- private:
- int d1;
- };
- typedef void(*Fun)(void);
- int main()
- {
- Derive d;
- Base1 *b1 = &d;
- b1->f();
- b1->g();
- Base2 *b2 = &d;
- b2->f();
- b2->g();
- return ;
- }
汇编代码(VS2010编译)
1.内存分布
- ;内存布局
- 0019FA0C 008F584C ;=>.rdata:const Derive::`vftable'{for `Base1'} 第一个虚表
- 0019FA10 ;=>Base1.b1
- 0019FA14 008F583C ;=>.rdata:const Derive::`vftable'{for `Base2'} 第二个虚表
- 0019FA18 ;=>Base2.b2
- 0019FA1C ;=>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, ;=>调整this指针为this+8,即ecx-->Derive::`vftable'{for `Base2'}
- 00FB1F33 jmp j_?f@Derive@@UAEXXZ ;=>Derive::f(void),即Derive类重写后的函数地址
2.构造函数
- 00FB14D9 mov [ebp-], ecx ;=>this指针保存在esp-4处
- 00FB14DC mov ecx, [ebp-] ;=>ecx获得this指针
- 00FB14DF call j_??0Base1@@QAE@XZ ;=>调用构造函数 Base1::Base1(void)
- 00FB14E4 mov ecx, [ebp-] ;=>ecx获得this指针
- 00FB14E7 add ecx, ;=>ecx获得this+8
- 00FB14EA call j_??0Base2@@QAE@XZ ;=>调用构造函数 Base2::Base2(void)
- 00FB14EF mov eax, [ebp-] ;=>eax获得this指针
- 00FB14F2 mov dword ptr [eax], offset ??_7Derive@@6BBase1@@@ ;=>初始化第一个虚表指针为const Derive::`vftable'{for `Base1'}
- 00FB14F8 mov eax, [ebp-] ;=>eax获得this指针
- 00FB14FB mov dword ptr [eax+], offset ??_7Derive@@6BBase2@@@ ;=>初始化第二个虚表指针const Derive::`vftable'{for `Base2'}
- 00FB1502 mov eax, [ebp-] ;=>eax获得this指针
- 00FB1505 mov dword ptr [eax+10h],
- 00FB150C push offset Format ; "Derive"
- 00FB1511 call ds:__imp__printf
- 00FB1517 add esp,
3.析构函数
- 00FB17C9 mov [ebp-], ecx ;=>this指针保存在esp-4处
- 00FB17CC mov eax, [ebp-] ;=>ecx获得this指针
- 00FB17CF mov dword ptr [eax], offset ??_7Derive@@6BBase1@@@ ;=>重置第一个虚表指针为const Derive::`vftable'{for `Base1'}
- 00FB17D5 mov eax, [ebp-]
- 00FB17D8 mov dword ptr [eax+], offset ??_7Derive@@6BBase2@@@ ;=>重置第二个虚表指针为const Derive::`vftable'{for `Base2'}
- 00FB17DF push offset aDerive ; "~Derive"
- 00FB17E4 call ds:__imp__printf
- 00FB17EA add esp,
- 00FB17ED mov ecx, [ebp-] ;=>ecx获得this指针
- 00FB17F0 add ecx, ;=>ec;得this+8
- 00FB17F3 call j_??1Base2@@QAE@XZ ;=>调用虚构函数Base2::~Base2(void)
- 00FB17F8 mov ecx, [ebp-] ;=>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+] ;=>eax获得成员函数Derive::g(void)的地址
- 00FB144E call eax ;=>调用成员函数Derive::g(void)
- ;Base2 *b2 = &d;
- 00FB1457 lea ecx, [ebp-14h] ;=>ecx获得this指针
- 00FB145A add ecx, ;=>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+] ;=>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)
C++反汇编-继承和多重继承的更多相关文章
- C++ 深入理解 虚继承、多重继承和直接继承
[摘要] 本文从5段代码实例出发,通过类中类的普通继承,类的虚继承,类的多重继承,多个虚函数类的普通继承.虚继承与多重继承,几个交叉概念,详细的阐释了继承.虚函数与虚继承的基本概念,深入剖析了继承于虚 ...
- Python 在子类中调用父类方法详解(单继承、多层继承、多重继承)
Python 在子类中调用父类方法详解(单继承.多层继承.多重继承) by:授客 QQ:1033553122 测试环境: win7 64位 Python版本:Python 3.3.5 代码实践 ...
- [javase学习笔记]-9.2 单继承与多重继承
这一节我们来看java中的单继承和多重继承. 在java语言中,支持的是单继承,不直接支持多继承,可是对C++中的多继承进行了改良. 那么什么是单继承和多继承呢? 单继承:一个子类仅仅能有一个直接父类 ...
- 《挑战30天C++入门极限》C++类的继承与多重继承的访问控制
C++类的继承与多重继承的访问控制 在前面的练习中我们一直在使用public的继承方式,即共有继承方式,对于protected和private继承方式,即保护继承与私有继承方式我们并没有讨论. ...
- JS中的原型继承和多重继承
概念:1原型继承是创建新类型对象----子类型,子类型基于父类型,子类型拥有父类型所有的属性和方法(从父类型继承得到),然后修改其中的部分内容或者添加新的内容.继承最好在子类型模型可以被视为父类型对象 ...
- python 继承与多重继承
当然,如果不支持python继承,语言特性就不值得称为“类”.派生类定义的语法如下所示: <statement-1> . . . <statement-N> 名称 BaseCl ...
- Golang之继承,多重继承(struct)
热乎的代码来了 package main import "fmt" /* 继承 一个结构体嵌到另一个结构体,称作组合 匿名和组合的区别 如果一个struct嵌套了另一个匿名结构体, ...
- c#继承、多重继承
c#类 1.c#类的继承 在现有类(基类.父类)上建立新类(派生类.子类)的处理过程称为继承.派生类能自动获得基类的除了构造函数和析构函数以外的所有成员,可以在派生类中添加新的属性和方法扩展其功能.继 ...
- C++继承,多重继承,虚继承的构造函数以及析构函数的调用顺序问题
#include <iostream> using namespace std; class A{ int data_a; public: A(){ data_a = ; cout < ...
随机推荐
- 好用的工具---screen命令
问 题场景:要在服务器上配置环境,但是我的电脑无法直接连到服务器上,通常要经过好几次ssh跳转.配环境需要设置好几个用户,这自然需要同时打开好几个连 接服务器的终端窗口,每个连接到服务器的终端窗口都要 ...
- linux系统iostat命令详解
iostat -k 3 5 (以KB为单位,每3秒统计一次,共统计5次) • avg-cpu: 总体cpu使用情况统计信息,对于多核cpu,这里为所有cpu的平均值 %user 用户空 ...
- Owin WebApi版本控制
public class WebApiControllerSelector : IHttpControllerSelector { private const string NamespaceKey ...
- 用js面向对象思想封装插件
js是基于原型的面向对象语言,如果你学过java,c#等正统面向对象语言,你会难以理解js的面向对象,他和普通的面向对象不太一样,今天,我们通过封装一个toast插件,来看看js面向对象是如何运行的. ...
- java基础60 JavaScript字符串转换成数字(网页知识)
1.字符串转换成数字 <!doctype html> <html> <head> <meta charset="utf-8"> &l ...
- HttpRunner接口自动化测试框架
简介 2018年python开发者大会上,了解到HttpRuuner开源自动化测试框架,采用YAML/JSON格式管理用例,能录制和转换生成用例功能,充分做到用例与测试代码分离,相比excel维护测试 ...
- (五)HttpClient 连接超时及读取超时
第一节: HttpClient 连接超时及读取超时 HttpClient连接超时及读取超时 httpClient在执行具体http请求时候 有一个连接的时间和读取内容的时间: HttpClient连接 ...
- Java之反转排序
顾名思义,反转排序就是以相反的顺序把原来的数组内容重新进行排序.反转排序算法在我们的程序开发中也是经常用到的.而反转排序的基本思想也很简单,就是把数组最后一个元素与第一个元素进行交换,倒数第二个与第二 ...
- 为django平台生成模拟用户,建立用户组,并将用户加入组
书接上篇BLOG. 当我们可以用manage.py自定义命令来生成模拟数据时, 我们面对的就是如何操作ORM的问题了. 这两天,我为我们的内部系统的所有数据表,都生成了模拟数据. 有几个心得,记录于此 ...
- Asp.net mvc 实时生成缩率图到硬盘
之前对于缩率图的处理是在图片上传到服务器之后,同步生成两张不同尺寸的缩率供前端调用,刚开始还能满足需求,慢慢的随着前端展示的多样化,缩率图已不能前端展示的需求,所以考虑做一个实时生成图片缩率图服务. ...