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

目录大纲:

  1.全局(静态)对象的识别,(全局静态全局一样的,都是编译期间检查,所以当做全局对象看即可.)

    1.1 探究本质,理解构造和析构的生成,以及调用方式(重要,如果不想知道,可以看总结.)

  2.对象做函数参数的识别 

  3.返回值为对象的识别

  4.对象为静态局部的识别

  5.堆中对象识别

      5.1. malloc和new的区别,free 和delete的区别

  6.对象数组

    6.1, delete对象和 delete[] 对象数组的区别

一丶全局对象的识别

  对于全局对象,以及全局变量等等.这些初始化,都是在ininterm中初始化的,和全局变量初始化的位置一样,如果不太懂,请看.以前博客链接:

http://www.cnblogs.com/iBinary/p/7912427.html

建立高级代码,查看其调用栈.(最后会总结)

高级代码:

class MyTest
{
    MyTest();
    ~MyTest();

};

MyTest::MyTest()
{
    printf("111\r\n");
}

MyTest::~MyTest()
{
    printf("222\r\n");

}
MyTest test();              //创建对象在全局
int main(int argc, char* argv[])
{

    return 0;
}

查看调用栈回朔:

调用栈的顺序依次是

  initterm  -> E4(代理) - > E1(代理)  ,熟悉完探究原理和本质的时候再来讲解E4 和E1代理是干啥用的.

1.1探究原理,追求本质.构造和析构的生成,构造的调用和析构的调用

1.熟悉 ininterm原理

我们以前讲过 ininterm函数里面的原理和本质(不熟悉看下方图)它会根据函数的起始和结束地址,循环遍历并且调用同一接口的函数进行初始化动作.

那么现在E4代理函数就是统一接口的,也就是说, ininterm函数循环的函数指针调用,都是调用E4代理函数

2.熟悉构造函数何时调用,E1代理, E3代理函数.

现在我们知道了ininterm函数为了统一接口,所以弄出来了一个E4代理函数,为了统一接口

E4代理函数内部:

那么E4代理里面做了什么事情.

可以看出,E4代理里面调用了E1代理和E3代理

关于E1代理,我们知道,它是为了统一参数而生成的一个代理,其内部调用我们的真正代码,(也就是构造函数)

E1函数代理内部

E3代理,E3代理稍后讲解,我们要知道E3是干什么用的要先知道一个C库函数的作用.

3.E3代理内部,以及C库函数作用

 C库函数,atexit  注册函数回调,main函数结尾的时候进行收尾动作(也就是释放资源的动作)

这个C库函数在C语言时代就是释放资源的.

看下MSDN声明.

注册一个C约定的函数回调即可.看下程序例子:

  高级代码:

void Abc()
{
    printf("1234\r\n");
}
int main(int argc, char* argv[])
{
    atexit(Abc);          //注册 C约定函数指针,当main函数结束的时候操作系统调用这个函数.
    return 0;
}

运行程序结果

正文:

  atexit可以注册多个回调,而这些会是一个线性表,里面储存了你注册的函数地址.当main函数结束的时候会调用

而内部

do exit函数内部会执行核心代码:

代码含义,一开始没有注册的时候, 线性表的头和尾都是一样的位置

当你注册了那么线性表则会增加4个字节存储你注册的函数回调地址.

可以看出上面代码逻辑

  从后往前调用,执行函数指针, 而这个函数则是你注册的函数回调.

E3代理含义:

  明白其上面的 atexit函数的原理,那么现在看看其E3内部的实现

E3内部其实是将E2函数注册进了atexit函数,当结束的时候则会调用E2

那么现在看看E2

E2函数内部:

E2函数内部则会调用析构函数,有人会说,为什么不直接将析构注册为函数回调,这样直接调用atexit不就在释放的时候,从后往前依次调用析构的了吗.

答:

  因为atexit的参数的c约定回调,而析构是thiscall,调用约定,所以内部必须包含一层才可以.

总结:

  当为全局对象的时候

  1.会在ininterm里面进行初始化动作

  2.会产生代理函数,这个代理函数是为了使ininterm函数的代码正常初始化而产生的一个统一接口的函数,暂且称为E4 (名字可能不一样)

  3.E4函数代理是为了统一接口,其内部又调用了 构造函数代理 (E1),和析构函数代理(E3)

  4.E1代理函数是为了统一参数用的,其内部是调用构造的,如果是有参数构造,则在E1代理函数内部可以看到传参的.

  5.E3代理函数是为了注册析构函数的,为了使atexit函数正常运行而注册的(atexit和ininterm类似,一个从前往后,一个从后往前)

  6.E2是E3内部给atexit函数注册的回调,这样在析构的时候则调用E2即可.

  7.E2函数内部是真正的调用析构的.

调用流程图:

实战中反汇编查找全局对象

既然我们知道了atexit函数会调用析构,那么我们在IDA中搜索atexit函数,看看谁引用了它,则可以把全局对象一网打尽.

 二丶对象作为函数参数的识别

高级代码:

  PS: 为了节省篇幅,类的定义不在重复截图,重复定义了.

void foo(MyTest test)
{
    printf("333\r\n");
}

int main(int argc, char* argv[])
{
    MyTest t;                   //定义对象
    foo(t);                     //对象当做参数传递
    return 0;
}

Debug下的汇编:

很明显的特征

  1.函数调用前会调用一次构造

  2.调用函数

  3.函数结束之前调用析构. (foo函数内部,为了节省篇幅,和Release)

  4.函数结束之后继续调用构造

Release版本汇编:

上面包含了 1 2 4步,其中第三步是在 foo函数内部调用的析构

foo 函数内部

内部会有个Jmp来调用析构

总结

  当函数参数为对象的时候.

  1.会先在函数外部进行构造一次

  2.调用函数

  3.函数内部调用一次析构

  4.函数结束之后的外面调用一次析构函数.

PS: 注意,局部对象和传参的区别,局部对象会在函数内部进行调用构造,而传参的时候是在函数外面进行的初始化动作

 三丶返回值为对象的识别

当返回值为对象的时候,会有两种情况

1.定义的时候产生拷贝动作

2.使用的时候产生临时对象

例如:

  MyTest t = Getobj(); 定义t的同时,接受Getobj返回的对象,则会产生拷贝构造

  t = Getobj():    定义完obj然后使用t接受Getobj()则会产生临时对象.不产生拷贝构造

以上都是C++语言,不熟悉的同学复习一下构造析构以及拷贝构造的内容即可.

1.拷贝动作的时候其返回对象的识别.

  高级代码:

MyTest Getobj()
{
    MyTest obj;
    return obj;
}
int main(int argc, char* argv[])
{
    MyTest t = Getobj();          //定义同时,接受返回对象
    return 0;
}

Debug下的汇编代码:

1.调用的时候,当做参数传递给Getobj

3.函数结束之后调用析构

2.函数内部调用构造和析构

(其中2在Getobj里面,看Release版本)

Release下的汇编

上面是第一步和第三步

第二步函数内部:

其内部调用构造和析构

总结:

  1.this指针会当做参数传递给函数, Mytest t = Getobj() t会当做参数传递

  2.其函数内部开始的时候会调用构造函数,结束之前调用析构

  3.函数结束之后,外部会调用析构函数.

 PS: 当代吗为引用的时候,其作用域跟着引用走  Mytest &t = Getobj();

2.使用的时候产生临时对象的情况下

  高级代码:

MyTest Getobj()
{
    MyTest obj;
    return obj;
}
int main(int argc, char* argv[])
{
    MyTest t ;
    t = Getobj();      //定义完毕之后使用
    return 0;
}

Debug下的汇编

1.  T变量进行构造

2.产生临时对象,调用GetObj, 其中Getobj内部会构造和析构,然后返回临时一般来那个

3.返回的临时变量给栈变量保存,然后 mov edx,[ecx] 给edx赋值

4.临时变量拷贝给t

5.临时变量析构

6.main结束前局部变量析构

Release下的汇编

Release汇编和Debug一样,减少了变量,进行了优化.

总结:

  使用时获得对象则产生临时对象

  1.局部对象进行构造

  2.调用函数的时候产生临时对象,其内部产生构造和析构

  3.返回的时候返回值给使用的对象赋值

  4.临时对象析构

  5.main结束时局部对象析构.

MyTest Getobj()
{
    MyTest obj;
    return obj;
}
int main(int argc, char* argv[])
{
    MyTest *t ;
    t = &Getobj();      //定义完毕之后使用
    t->m_dwNumber = 1;
    return 0;
}

Debug下反汇编

我们会发现

返回的临时对象会给t保存

但是紧接着析构了,但是此时指针调用了临时对象里面的成员,并且给它赋值了.所以以后写代码要注意,这种错误编译器检测不出来.虽然支持这个语法.但是肯定会出错,而且是莫名其妙的错误

 四丶对象为静态局部的识别

  高级代码:

int main(int argc, char* argv[])
{
    static MyTest t ;

    return 0;
}

Debug下的汇编

会生成一个检查标志,根据这个标志判断,是否调用构造和析构

会跳过一个 构造和注册析构的一块区域

总结:

   生成检查标志,跳过构造和注册析构代理.

.堆中对象识别

高级代码:

MyTest *t = new MyTest ;

Debug下的汇编:

new 和malloc是一样的,new是对malloc的一个封装. 只会申请空间,但是会产生额外的代码,中间会判断标志,申请成功的返回值为0或者为1,如果为0则不构造,如果为1则构造

但是注意:这里的额外代码只是判断是否进行构造,你自己也要进行判断.

Delete语法

Delete语法会调用析构,也会生成额外语法.

当Delete的时候会传入1, 这个是按位来的, 如果最低位为1,则是代表释放内存,那么就调用析构并且释放,如果为0,则仅仅代表了调用析构.

为什么会这样:

  在早期,硬件资源匮乏,内存想重复利用.

所以会有人显示的调用构造(vc6.0中可以)然后显示的调用析构进行管理,示例:

加上类域则可以调用构造了,那么析构我们是显示调用,所以看看汇编代码,会传入0,不会释放内存的.

总结:

  1.new malloc 一样,new是对malloc的一个封装,但是会产生额外代码,用来判断是否进行构造

  2.delete的时候,会传入0 1来判断是否是调用析构并释放内存(1) ,或者只调用析构(0)

转载于:

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

原文地址:https://www.cnblogs.com/gd-luojialin/p/11219907.html

时间: 2025-01-17 05:16:30

C++反汇编第一讲,不同作用域下的构造和析构的识别的相关文章

C++反汇编第一讲,认识构造函数,析构函数,以及成员函数

C++反汇编第一讲,认识构造函数,析构函数,以及成员函数 以前说过在C系列下的汇编,怎么认识函数.那么现在是C++了,隐含有构造和析构函数 一丶认识构造函数 高级代码: class MyTest { public: MyTest(); ~MyTest(); public: DWORD m_dwTest; }; MyTest::MyTest() { printf("1111\r\n"); //构造的时候先打印 } MyTest::~MyTest() { printf("2222

逆向实用干货分享,Hook技术第一讲,之Hook Windows API

逆向实用干货分享,Hook技术第一讲,之Hook Windows API 作者:IBinary出处:http://www.cnblogs.com/iBinary/版权所有,欢迎保留原文链接进行转载:) 一丶什么是Hook,以及Hook能干啥 首先这一个小标题主要介绍神马是Hook,如果知道的,则不用看了. 这里我偷袭啊懒,贴出Hook的意思  https://baike.baidu.com/item/%E9%92%A9%E5%AD%90%E7%A8%8B%E5%BA%8F Hook,英文单词中成

16位汇编第一讲简介

汇编第一讲 汇编简介 一.什么是汇编 汇编语言他是计算机语言,计算机语言通俗点说就是人类和计算机(也就是CPU)沟通的桥梁,计算机不认识人类的语言,只认得二进制(0和1)但是我们想让你算计完成我们的工作,每次都是0,和1,那样会崩溃的,(老一辈的是这样的)所以后面汇编语言出现了,用一些简单的助记符来替代机器语言(二进制)通用的语言,比如 + - * / 这种助记符, add....汇编语言是和机器语言一一对应的. 二.汇编语言有什么用处好处     1.学好汇编了,你可以理解计算机更为透彻  

日更第6期-2015-1-29-如何科学地使用因特网-第一讲-总之先爬墙

哟哟,我又来日更了啊!O(∩_∩)O哈哈~不过,其实是隔了两天,不过比起上次,是要好了不少. 先说个好消息吧!我1月31日就要放假啦,然后就可以回家啦. 然后,回家之后,我就要正式的日更啦!现在为止我可是一直在隐藏实力哦.我要从几天一更进化为一天几更. 先试试一夜七次...... 恩,说点正经的:我接下来会更新些什么内容. 第一部分,OpenFrameworks的使用,即OpenFrameworks系列教程,里面包括example的解释,tutorial的 翻译,api的详解以及最新例子的展示.

日更第2期-2015-1-15-openFrameworks系列第一讲-手把手制作openFrameworks上的第一个程序!

恩,今天和朋友打球来着,于是今天的案例程序就做一个球吧!O(∩_∩)O哈哈~ 首先,没有看过上一篇教程的同学,还有还没有下载好VS和OpenFrameworks的同学,都去下一下. 传送地址:http://www.cnblogs.com/linongbo/p/4227552.html 那么,开始今天的日更啦! Hello OpenFrameworks! VS的安装部分我就不说了,不过我个人建议——默认是安装在C盘的,不过你要是手动改到别的盘上的话,C盘上 依然会有6G左右的内容.......Σ(

《上古天真论》第一讲文字版

上古天真论篇第一讲主讲:徐文兵  主持:梁  冬播出时间:2008-12-06  23:00—24:00  经文:昔在黄帝,生而神靈,弱而能言,幼而徇齐,长而敦敏,成而等天.乃问于天师曰:余闻上古之人,春秋皆度百岁,而动作不衰:今时之人,年半百而动作皆衰者,时世异耶?人将失之耶?岐伯对曰:上古之人,其知道者,法于阴阳,和于术数, 梁冬:我是梁冬.梁某人.而做在我对面的是我的在求学中医方面的偶像级的老师徐文兵老师,徐老师你好!徐文兵:梁冬好!听众朋友们大家好!梁冬:啊呀,一看就很有这个电台风范,从

第一讲:Android开发环境的搭建

一.Android简介 Android 是基于Linux内核的软件平台和操作系统.Android构架主要由3部分组成,linux内核层,类库.虚拟机和核心组件库层,应用程序框架层Android应用程序使用JAVA语言进行开发. 二.开发环境的搭建 软件的准备:JAVA  JDK 1.6Eclipse 3.6           (eclipse-java-helios-win32.zip)ADT 0.9.7             (Android Development Tools)SDK T

斯坦福大学深度学习与自然语言处理第一讲:引言

斯坦福大学在三月份开设了一门"深度学习与自然语言处理"的课程:CS224d: Deep Learning for Natural Language Processing ,授课老师是青年才俊Richard Socher,他本人是德国人,大学期间涉足自然语言处理,在德国读研时又专攻计算机视觉,之后在斯坦福大学攻读博士学位,拜师NLP领域的巨牛 Chris Manning和Deep Learning 领域的巨牛 Andrew Ng ,其博士论文是< Recursive Deep Le

Bia 娘 js 教程第一讲——“亲爱的,我们来聊聊天”

JavaScript 一种直译式脚本语言,是一种动态类型.弱类型.基于原型的语言,内置支持类型.它的解释器被称为 JavaScript 引擎,为浏览器的一部分,广泛用于客户端的脚本语言,最早是在 HTML(标准通用标记语言下的一个应用)网页上使用,用来给 HTML 网页增加动态功能. 巴拉巴拉巴拉,你把上面的东西读完了,那你也就晕了.因为我看了 n 遍,我就只明白一点,js这东西,好牛逼哇.我不仅又想,它牛逼,那么它一定要帮我做一件事,这件事,我用 html完成不了,这件事就是——让我的网页“活