讲解之前,了解下什么是虚函数,什么是虚表指针,了解下语法,(也算复习了)

开发知识为了不码字了,找了一篇介绍比较好的,这里我扣过来了,当然也可以看原博客链接:  http://blog.csdn.net/hackbuteer1/article/details/7558868

一丶虚函数讲解(复习开发,熟悉内存模型)

1.复习开发知识

首先:强调一个概念
定义一个函数为虚函数,不代表函数为不被实现的函数。

定义他为虚函数是为了允许用基类的指针来调用子类的这个函数。

定义一个函数为纯虚函数,才代表函数没有被实现。

定义纯虚函数是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数。

1、简介
假设我们有下面的类层次:

class A
{
public:
virtual void foo()
{
cout<<"A::foo() is called"<<endl;
}
};
class B:public A
{
public:
void foo()
{
cout<<"B::foo() is called"<<endl;
}
};
int main(void)
{
A *a = new B();
a->foo(); // 在这里,a虽然是指向A的指针,但是被调用的函数(foo)却是B的!
return ;
}

这个例子是虚函数的一个典型应用,通过这个例子,也许你就对虚函数有了一些概念。它虚就虚在所谓“推迟联编”或者“动态联编”上,一个类函数的调用并不是在编译时刻被确定的,而是在运行时刻被确定的。由于编写代码的时候并不能确定被调用的是基类的函数还是哪个派生类的函数,所以被成为“虚”函数。
虚函数只能借助于指针或者引用来达到多态的效果。

如果看明白上面的开发知识,则我们可以从内存角度看一下虚函数是怎么样存在的.

2.从内存角度看虚函数

首先我们学习C++的时候,自学或者老师教学的时候,都有谈过一个虚表指针的概念.

那我们要知道什么是虚表指针.

 2.1

不带虚表指针的高级代码:

class MyTest
{
public:
MyTest();
~MyTest();
void ShowHelloWorld();
int m_Number;
}; MyTest::MyTest()
{
printf("MyTest::MyTest()\r\n");
} MyTest::~MyTest()
{
printf("MyTest::~MyTest()\r\n");
} void MyTest::ShowHelloWorld()
{
printf("Hello World!\n");
} //类声明在上 int main(int argc, char* argv[])
{
MyTest obj; //构造obj对象
obj.ShowHelloWorld();//调用成员函数
obj.m_Number = ; //成员变量赋值
return ;
}

首先看上图高级代码,为什么我们说它没有虚表指针.我们调试查看.

首先经过我们调试

1.obj在监视窗口中只有一个成员变量,且初始化为CCCCC (Debug下)

2.看对象的所在的地址中,发现只申请了4个字节空间,用来存放成员变量.

2.2带虚表指针的高级代码

高级代码还是其高级代码,唯一不同的则是在类中给成员函数加了一个关键字, virtual,让此成员函数变为一个虚函数.

内存模型:

我们发现加了之后会额外多出4个字节空间,而且监视窗口中加了一项虚表指针变量.

构造一下继续观看内存模型.

构造之后发现已经初始化了虚表指针,那么我们进去这个地址后查看有什么内容.

其内容是一个函数指针表,里面存放了虚函数的地址.不相信的话我们打开反汇编窗口,跟进去则可以看到.

总结:

  1.没有虚表指针

    1.1没有虚函数的情况下没有虚表指针

  2.有虚表指针

    2.1虚表指针的产生是看你有没有 virtual这个关键字

    2.2虚表指针存储的是虚表的首地址,虚表可以看做是一个数组

    2.3虚表中存储的是虚函数的地址.

二丶熟悉反汇编中虚表指针,以及还原

既然上面我们熟悉了内存模型,也熟悉了虚函数的原理,那么我们从反汇编的角度下看一下.

例子是我们加了虚函数的例子

Debug下的反汇编

在我们构造的时候,会填写虚表指针,然后核心代码就在上面

1.将对象存入一个局部变量保存

2.局部变量中转给eax

3.对eax取内容填写虚表地址.

总结就是一句话:  取出对象的首4个字节,填写虚表.

那么现在好办了,既然找到了虚表,则可以找到构造,析构,以及虚表中存储的所有虚函数了.

PS: 此图和上图的反汇编一样,只不过高版本的图表没法看,所以用低版本,低版本可以打开.

对其位置下一个引用图表,谁引用了我,则可以看到调用它的所有构造以及析构了,

1.构造的时候会填写虚表

2.析构的时候会填写虚表

图表:

可以看出分别有个构造和析构.那个是构造那个是析构,我们需要跟过去看一下.

根据以前所讲的认识构造和析构的方法,可以很简单的判别出来.

识别虚函数

  既然我们找到了虚表指针,则可以双击过去,可以找到虚函数了.

有一个虚函数,确实只有一个,我们跳转过去看看是不是我们定义的

Debug下有跳转表

里面的跳转则是我们的虚函数

总结:

  1.识别虚表指针可以在构造中或者析构中查看

  2.虚表指针双击过去则可以看到所有的虚函数的地址

  3.对虚表指针来个引用,(谁引用我)可以看到所有的构造和析构

三丶识别虚函数的调用

熟悉了虚表指针, 通过虚表指针找构造,析构,以及虚表指针指向的虚表找虚函数,那么我们看一下普通成员函数调用和虚函数调用有什么区别.

PS:类声明不截图,其普通成员函数不加关键字 virtual,其虚函数加virtual

高级代码:

int main(int argc, char* argv[])
{
MyTest test;
MyTest &obj = test; //可以虚调用
obj.print(); //普通成员函数
obj.ShowHelloWorld(); //虚函数调用
return ;
}

Debug下的反汇编观察.

  

认真观察可以看出

  1.普通成员函数调用,直接Call

  2.虚函数调用

    2.1 首先获得虚表指针

    2.2 间接调用虚表指针指向的虚表的内容(虚成员函数地址)

总结:

  识别调用普通成员函数和虚函数的特征则是

  1.普通成员函数直接调用Call

  2.虚函数会通过虚表指针指向的虚表来间接调用.

转载于:

作者:IBinary
出处:http://www.cnblogs.com/iBinary/

C++反汇编第二讲,反汇编中识别虚表指针,以及指向的虚函数地址的更多相关文章

  1. C++反汇编第三讲,反汇编中识别虚表指针,以及指向的虚函数地址

    C++反汇编第三讲,反汇编中识别虚表指针,以及指向的虚函数地址 讲解之前,了解下什么是虚函数,什么是虚表指针,了解下语法,(也算复习了) 开发知识为了不码字了,找了一篇介绍比较好的,这里我扣过来了,当 ...

  2. C++反汇编第二讲,不同作用域下的构造和析构的识别

    C++反汇编第二讲,不同作用域下的构造和析构的识别 目录大纲: 1.全局(静态)对象的识别,(全局静态全局一样的,都是编译期间检查,所以当做全局对象看即可.) 1.1 探究本质,理解构造和析构的生成, ...

  3. C++函数中那些不可以被声明为虚函数的函数

    转自C++函数中那些不可以被声明为虚函数的函数 常见的不不能声明为虚函数的有:普通函数(非成员函数):静态成员函数:内联成员函数:构造函数:友元函数. 1.为什么C++不支持普通函数为虚函数? 普通函 ...

  4. C++中的动态类型与动态绑定、虚函数、运行时多态的实现

    动态类型与静态类型 静态类型 是指不需要考虑表达式的执行期语义,仅分析程序文本而决定的表达式类型.静态类型仅依赖于包含表达式的程序文本的形式,而在程序运行时不会改变.通俗的讲,就是上下文无关,在编译时 ...

  5. C++中继承 声明基类析构函数为虚函数作用,单继承和多继承关系的内存分布

    1,基类析构函数不为虚函数 #include "pch.h" #include <iostream> class CBase { public: CBase() { m ...

  6. 从0 开始 WPF MVVM 企业级框架实现与说明 ---- 第二讲 WPF中 绑定

    说到WPF, 当然得从绑定说起,这也是WPF做的很成功的一个地方,这也是现在大家伙都在抛弃使用winform的其中一个主要原因,Binding这个东西从早说到完其实都说不完的,我先就做一些基本的介绍, ...

  7. 内核开发知识第一讲.内核中的数据类型.重要数据结构.常用内核API函数.

    一丶内核中的数据类型 在内核中.程序的编写不能简单的用基本数据类型了. 因为操作系统不同.很有可能造成数据类型的长度不一.而产生重大问题.所以在内核中. 数据类型都一定重定义了. 数据类型 重定义数据 ...

  8. C++中为什么要将析构函数定义成虚函数

    构造函数不可以是虚函数的,这个很显然,毕竟虚函数都对应一个虚函数表,虚函数表是存在对象内存空间的,如果构造函数是虚的,就需要一个虚函数表来调用,但是类还没实例化没有内存空间就没有虚函数表,这根本就是个 ...

  9. 《C++反汇编与逆向分析技术揭秘》之11——虚函数

    虚函数的机制 当类中定义有虚函数时,编译器会将该类中所有虚函数的首地址保存在一张地址表中,这张表被称为虚函数地址表.编译器还会在类中添加一个虚表指针. 举例: CVirtual类的构造函数中没有进行任 ...

随机推荐

  1. C#winform如何实现文本编辑框(TextBox)的Hint提示文字效果

    C#winform如何实现文本编辑框(TextBox)的Hint提示文字效果 private const int EM_SETCUEBANNER = 0x1501; [DllImport(" ...

  2. mysql 常见面试题

    附录: https://mp.weixin.qq.com/s/pC0_Y7M7BkoUmlRwneZZdA 一.为什么用自增列作为主键 1.如果我们定义了主键(PRIMARY KEY),那么InnoD ...

  3. Netty实践场景

    数据通信 如果需要考虑的是两台机器(甚至多台)怎么使用Netty进行通信.大体上分为三种: 1 第一种:使用长连接通道不断开的形式进行通信.也就是服务端和客户端的通道一直处于开启状态. 如果服务器性能 ...

  4. vue实现全选框效果

    vue实现全选框效果 一.总结 一句话总结: 全选的checkbox点击的时候判断这个checkbox的状态,如果没选中,说明下一个操作是选中所有 下面的每个checkbox判断一下是否所有的chec ...

  5. [Java复习] JVM

    Part1:Java类加载机制:类加载器,类加载机制,双亲委派模型 1. Java 类加载过程? 类加载过程即是指JVM虚拟机把.class文件中类信息加载进内存,并进行解析生成对应的class对象的 ...

  6. centos7.4出现yum command not found

    购买的云服务器运行yum命令出现yum command not found. 通过将云主机自带的yum和python卸载掉,并且同时需要关注/usr/bin/yum文件的首行解释.我定义其为" ...

  7. fastjson在将Map<Integer, String>转换成JSON字符串时,出现中文乱码问题

    fastjson在将Map<Integer, String>转换成JSON字符串时,出现中文乱码问题. 先记下这个坑,改天在看看是怎么导致的,暂时通过避免使用Integer作为键(使用St ...

  8. DSS分发压力实验

    DSS分发压力实验 昨天为验证依托DSS搭建流媒体直播监控系统的可行性,及确定实时流画面出现严重花屏的原因,做了一个压力实验. 网络拓扑如图: 1.DVR上配置4路视频(CIF / 25fps / 1 ...

  9. [ML] Bayesian Logistic Regression

    简单概率分类 Ref: 逻辑回归与朴素贝叶斯有什么区别? Ref: 机器学习笔记——逻辑回归(对数几率回归)和朴素贝叶斯分类器的对比 首先,搞清楚一个问题. naive bayes 能分类:逻辑回归也 ...

  10. python3 高级编程(三) 使用@property

    @property装饰器就是负责把一个方法变成属性调用的. @property广泛应用在类的定义中,可以让调用者写出简短的代码,同时保证对参数进行必要的检查,这样,程序运行时就减少了出错的可能性 cl ...