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

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

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

开发知识为了不码字了,找了一篇介绍比较好的,这里我扣过来了,当然也可以看原博客链接:  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 0;  
}  

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

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

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 = 1;    //成员变量赋值
    return 0;
}            

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

首先经过我们调试

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 0;
}

Debug下的反汇编观察.

认真观察可以看出

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

  2.虚函数调用

    2.1 首先获得虚表指针

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

总结:

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

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

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏软件开发

C语言 第二章 数据类型、变量和输入函数

一、数据类型简介 在 C 语言中,数据类型指的是用于声明不同类型的变量或函数的一个广泛的系统。变量的类型决定了变量存储占用的空间,以及如何解释存储的位模式。 ?...

28550
来自专栏小文博客

写出这个数——《C语言代码笔记》

30720
来自专栏五分钟学算法

五分钟学会一个有意思的排序:计数排序

由于LeetCode上的算法题很多涉及到一些基础的数据结构,为了更好的理解后续更新的一些复杂题目的动画,推出一个新系列 -----《图解数据结构》,主要使用动画...

13260
来自专栏专注数据中心高性能网络技术研发

[Effective Modern C++(11&14)]Chapter 4: Smart Pointers

30020
来自专栏个人随笔

房上的猫:while循环与do-while循环,debug的调试运用

一.循环结构  1.循环不是无休止进行的,满足一定条件的时候循环才会继续,称为"循环条件",循环条件不满足的时候,循环退出  2.循环结构是反复进行相同的或类似...

414110
来自专栏沈唁志

PHP中系统函数http_build_query系统函数使用方法

18440
来自专栏小白的技术客栈

Python基础语法-内置数据结构概览

本文内容非常简单,主要介绍接下来要讲的内容。那就是Python的内置数据结构。今天只是简单介绍一下Python都有哪些内置数据结构,这样就可以循序渐进地进行学习...

36470
来自专栏张俊红

python中的小魔法(一)

? 总第101篇 if-else的简洁写法 #常规写法 if a>b: c=a else: c=b #简洁写法 c=a if a>b...

28240
来自专栏大闲人柴毛毛

剑指offer代码解析——面试题14调整数组顺序使奇数在偶数之前

本题详细解析都已在代码中注释了: /** * 题目:输入一个数组,要求将奇数放在数组的前半段,偶数放在数组的后半段 * @author 大闲人柴毛毛 *...

32450
来自专栏软件开发 -- 分享 互助 成长

C++ STL 中erase()的使用需要小心

C++ STL极大的方便了用户编写程序,但是同时一不小心也会犯一些错误,如erase()造成迭代器失效经常会引起错误。 错误示例: std::lis...

275110

扫码关注云+社区

领取腾讯云代金券