当前位置: 首页 > news >正文

C++逆向分析--虚函数(多态的前置)

先理解一件事,在intel汇编层面来说,直接调用和间接调用的区别。

直接调用语法: call 地址   硬编码为 :e8

间接调用语法:   call [ ...]     硬编码为:    FF

那么在C++语法中,实现多态的前提是父类需要实现多态的成员方法前面加入virtual。我们先来看一个例子,这次试验用的是windows平台下的VS编译器不同编译器的细节是不一样的原理大差不差。

#include<iostream>using namespace std;class Base {
public:void func1() {printf("这是func1");}virtual void func2() {printf("这是func2");}};int main() {Base b1;b1.func1();b1.func2();return 0;
}

  这个代码示例中我们只写了一个类,并且用对象去调用成员方法。运行结果如下:


我们观察下汇编代码:

此时均为e8-call,属于直接调用。接下来我们换一种调用方式,我们通过指针的方式去调用对象方法:

#include<iostream>using namespace std;class Base {
public:void func1() {printf("这是func1");}virtual void func2() {printf("这是func2");}};int main() {Base b1;Base* p;p = &b1;p->func1();p->func2();return 0;
}


运行结果没有变化,但是此时我们观察反汇编我们就发现了不一样的地方:


fun1就是普通的成员函数,反汇编依然是那样。但是我们看加入关键字的virtual变成虚函数后,汇编代码变化了。真正调用的其实是call eax这句汇编代码,我们看到他的硬编码是FF D0。说明他是个间接调用。我们可以理解这段反汇编代码,p是指向对象的首地址的也就是this指针。将this指针的第一项放入eax,又将eax的第一项复制给edx,此时将this指针给ecx(如果是linux平台g++编译this指针是赋值给rdi的 _x64情况下)最后将edx的第一项赋值给eax。最后去执行。这段翻译可能有点绕。可以自己去理解下。

现在我们知道虚函数,如果是通过对象去调用,和普通成员方法没什么区别,但是如果通过指针或者引用去调用。那么他就是个间接调用。一些细节后面再讲。

下面我们再分析一个问题,这个类到底有多大。

#include<iostream>using namespace std;class Base {
public:int a;int b;void func1() {printf("这是func1");}void func2() {printf("这是func2");}};int main() {//Base b1;//Base* p;//p = &b1;//p->func1();//p->func2();printf("Base结构体大小为=%d", sizeof(Base));return 0;
}

这个问题应该很简单,只算数据大小,int类型是占4个字节因此这个Base对象的大小应该是8,因为成员方法是在代码区的,这里其实跟C语言的结构体没什么不一样的:


那如果现在我们将其中一个成员方法改成虚函数呢?


神奇的一幕发生了,变成了12。

那是不是说类中每多一个虚函数,就会多4字节大小呢?(32位而言,64位就是8)

我们将fun1也变成虚函数:


我们看到没有变化。也就是说,跟你在类中定义多少个虚函数木的关系。那这多出来的4字节是个什么鬼东西呢?这就是我们接下来要探究的东西。


我们观察。现在没有任何函数的情况下,我们的类是这样布局的。 0x00cffb80也就是b1对象的首地址。

下面我们加入一个虚函数:


我们观察此时对象的内存布局:


什么都没有,接着往下走:


再往下走:


再往下走:


我们发现对象首地址存的不再是1。而是一个地址。那这个地址是什么呢?这就是我们接下来要探究的东西。写一个demo:

#include<iostream>using namespace std;class Base {
public:int a;int b;virtual void func1() {printf("这是func1");}};int main() {Base b1;b1.a = 1;b1.b = 2;Base* p;p = &b1;p->func1();return 0;
}

我们跟过去反汇编:



我们现在看这两行汇编的意思就很明朗了。

1.mov eax [p]   //将对象的this指针放入eax

2.mov edx [eax] //将this指针首地址里面存的虚函数表放入edx

3.mov ecx [p]   //将this指针传给ecx

3,mov eax ,[edx] //将虚函数表里的第一项放入eax

4.call eax //调用fun1函数。


相比看懂了上面的流程就明白了。总结图如下:


那我们能验证虚函数表中的函数就是我们的想要调用的函数嘛?demo如下:

#include<iostream>using namespace std;class Base {
public:int a;int b;virtual void func1() {printf("这是func1\n");}};int main() {Base b1;b1.a = 1;b1.b = 2;Base* p;p = &b1;p->func1();printf("b1对象的地址=%p\n", &b1);printf("虚函数表地址=%p\n", *((int*)&b1));printf("func1函数地址=%p\n", *((int*)*((int*)&b1)));int p2 = *((int*)*((int*)&b1));_asm {call p2;}return 0;
}

这里因为我是用32位写的demo因此我们用内联汇编测试下。代码逻辑就是取出虚函数表中的第一项,然后用汇编调用,看是否和我们用指针调用的是同一个函数运行结果如下:


通过这个实验我们确实验证了虚函数表中存的就是我们的虚函数的地址。这也是C++编译器实现多态的一个先提条件。

相关文章:

  • 【Midjourney】绘画风格关键词
  • Python编程 从入门到实践(项目二:数据可视化)
  • Docker 配置 Gitea + Drone 搭建 CI/CD 平台
  • jQuery取整(Math.floor()、Math.ceil() 、 parseInt() )
  • Spring Boot 项目的创建和启动
  • LeetCode 刷题总结 【未完待续】
  • 应用案例:Ruff工业设备数据采集,为生产制造企业数字化转型赋能
  • 工厂方法模式-C#实现
  • 浮点数在内存中存储
  • 【mongoDB】文档 CRUD
  • VR漫游:赋予用户720度身临其境的沉浸式体验
  • 体验华为云对话机器人服务 CBS
  • 使用 Docker 部署 Nacos 并配置 MySQL 数据源
  • IS-IS:04 DIS
  • [docker] Docker的私有仓库部署——Harbor
  • 2017-09-12 前端日报
  • 230. Kth Smallest Element in a BST
  • HTTP中GET与POST的区别 99%的错误认识
  • js数组之filter
  • JWT究竟是什么呢?
  • Laravel 中的一个后期静态绑定
  • Laravel5.4 Queues队列学习
  • Laravel深入学习6 - 应用体系结构:解耦事件处理器
  • PHP变量
  • python_bomb----数据类型总结
  • Service Worker
  • Spring核心 Bean的高级装配
  • vue从创建到完整的饿了么(18)购物车详细信息的展示与删除
  • 阿里研究院入选中国企业智库系统影响力榜
  • 从setTimeout-setInterval看JS线程
  • 基于 Babel 的 npm 包最小化设置
  • 基于Vue2全家桶的移动端AppDEMO实现
  • 解析 Webpack中import、require、按需加载的执行过程
  • 免费小说阅读小程序
  • 使用 Node.js 的 nodemailer 模块发送邮件(支持 QQ、163 等、支持附件)
  • 首页查询功能的一次实现过程
  • mysql 慢查询分析工具:pt-query-digest 在mac 上的安装使用 ...
  • 仓管云——企业云erp功能有哪些?
  • 如何正确理解,内页权重高于首页?
  • ​软考-高级-系统架构设计师教程(清华第2版)【第20章 系统架构设计师论文写作要点(P717~728)-思维导图】​
  • ​油烟净化器电源安全,保障健康餐饮生活
  • #{}和${}的区别是什么 -- java面试
  • #我与Java虚拟机的故事#连载09:面试大厂逃不过的JVM
  • (1/2) 为了理解 UWP 的启动流程,我从零开始创建了一个 UWP 程序
  • (C语言)共用体union的用法举例
  • (C语言)输入一个序列,判断是否为奇偶交叉数
  • (html5)在移动端input输入搜索项后 输入法下面为什么不想百度那样出现前往? 而我的出现的是换行...
  • (附源码)springboot电竞专题网站 毕业设计 641314
  • (附源码)springboot美食分享系统 毕业设计 612231
  • (解决办法)ASP.NET导出Excel,打开时提示“您尝试打开文件'XXX.xls'的格式与文件扩展名指定文件不一致
  • (十八)用JAVA编写MP3解码器——迷你播放器
  • (算法)Travel Information Center
  • (一)搭建springboot+vue前后端分离项目--前端vue搭建
  • (转)拼包函数及网络封包的异常处理(含代码)
  • ****** 二十三 ******、软设笔记【数据库】-数据操作-常用关系操作、关系运算