• 《C++反汇编与逆向分析技术揭秘》之十——构造函数


    对象生成时会自动调用构造函数。只要找到了定义对象的地方,就找到了构造函数调用的时机。不同作用域的对象的生命周期不同,如局部对象、全局对象、静态对象等的生命周期各不相同,只要知道了对象的生命周期,便可以推断出构造函数的调用时机

    • 局部对象

    反汇编:

    获取对象首地址并调用构造函数:

    对象的地址为:

    进入构造函数,先是push一堆寄存器:

     

    还原ecx寄存器,并初始化:

     构造函数属于成员函数,在调用时要用到this指针。

    如何识别?1、构造函数时这个对象在作用域内调用的第一个成员函数,根据this指针可以区分每个对象;2、返回this指针是构造函数的特征之一(这是识别局部变量构造函数的必要条件)

    • 堆对象

    堆对象的识别重点在于识别堆空间的申请与使用

    举例:

    new以及new的大小:

     返回的地址:

    调用构造函数:

    先判断new是否从成功,如果失败就跳过构造函数:

    执行构造函数初始化对象:

    定位到对象的第一个成员变量,并赋值为0xb:

    new对象返回对象的首地址时,也检查了是否为NULL,进行了一次判断,如果失败就跳过构造函数,所以可以从这一点入手,找到堆对象的构造函数

     考虑下我们这里有两个成员变量的情况:

    new的大小变了:

    给第二个成员变量赋值时的定位变了:

    • 参数对象

    当对象作为函数参数时,调用一个特殊的构造函数——拷贝构造函数。拷贝构造函数只有一个参数,即为对象的引用

    例子如下:

     1、初始化一个MyString对象,先调用了一个autoclassinit函数:

    类对象的地址为:

    构造函数内部在初始化成员m_pString时调用了memset函数:

    这里的ecx就是类对象的首地址。

    2、调用构造函数:

    ecx保存的是类对象,给成员赋值:

    3、调用成员函数SetString:

    里边会先获取字符串长度:

    然后再去拷贝:

    参数:

    返回:

     4、Show(MyString)利用了拷贝构造函数(本小节重点)

    先调用一个拷贝构造函数,拷贝MyString。即一个新的CMyString对象调用拷贝构造函数,这个拷贝构造函数的参数是前面的MyString对象。

    拷贝构造函数的调用,且新的对象的地址就是esp的值

    拷贝构造函数中,会用this作为返回值

    其中ecx是新的CMyString对象的首地址:

    eax是前面定义的、这里被拷贝的MyString对象的地址:

    返回的是生成的新对象的地址,也是esp的值:

    调用完拷贝构造函数后紧接着调用Show函数,并没有push操作来压入参数:

     

    其参数就是新返回的对象的成员(这里我们发现,刚刚返回的新的CMyString的地址就是esp的值):

    验证:

    如果CMyString中有两个成员变量,那么在给Show传参的时候,就压入了两个数据:

    传给Show的参数其实并不是通过push操作压入的参数,而是通过拷贝构造函数帮忙压入的。因为拷贝构造函数新构造的那个对象的地址就是esp所指向的位置,所以拷贝构造函数执行完成之后生成的数据就直接放在了栈顶位置

    • 返回对象 

    返回时需要对返回对象进行拷贝,因此同样会使用到拷贝构造函数

    举例:

     除了那个autoclassInit函数之外,其实就调用了GetMyString这么一个函数:

     那么可以说明给MyString对象赋值的情况是在GetMyString中完成的。而我们可以知道,GetMyString中并没有刻意实现给MyString赋值的功能,那么究竟是如何做到的呢?

    GetMyString()并没有参数,但是还是push了一个eax,这个eax是由autoclassInit函数返回的仅仅是一个地址,也就是MyString的地址(但是没有调用MyString的构造函数进行初始化):

    随后以这个eax为参数调用了GetMyString,进入其中我们来到return MyString的部分,这里调用了一个拷贝构造函数:

    寄存器的值为:

    也就是说,是函数外面创建的那个对象,复制了函数内部的临时的对象。并返回12FF5C:

    这里我们可以看出,并不是GetMyString得到一个对象后,再去拷贝给某个定义好的等待接收的变量,而是在GetMyString过程中,就给main中的MyString变量构建好了(借助传递地址参数)

    • 全局对象与静态对象

     我们必须清楚的知道全局对象与静态对象构造的时机。所有的全局对象都会在同一地点调用构造函数进行初始化,即_cinit函数。_cinit的_initterm函数中逐一初始化了全局对象。

    定位方法一——直接定位法:mainCRTStartup->_cinit->_initterm->构造代理函数

    定位方法二——利用栈回溯:全局对象的地址固定,可以先对这些数据下读写断点。

    定位方法三——定位atexit:对atexit下断点。

    进入:

    先找到:

    在下面这个循环中,会调用构造代理函数:

     进入这个call eax函数中,跟踪找到了一个构造函数,但并不一定是我们定义的那全局对象的构造函数:

     继续找,找到第一个全局对象,一般先是调用构造函数再有一个注册析构函数

    构造函数内部:

    构建时先是初始化一块地址:

    返回值:

     调用了带一个参数的构造函数:

    其中赋值:

    继续找找到第二个全局对象:

    考虑,如果是一个全局变量数组,那么构造函数是如何被调用的呢?比如:

    那么不会是分别调用三次call eax函数,每次构造一个对象。而是调用一次call eax函数,里边调用一个构造代理函数来分别构建三个对象:

    进入构造代理函数内部,发现下面的循环逐一调用构造函数:

    其逻辑类似于书上P249所述。

    •  每个对象都有默认构造函数吗

    举例:

    这里不会为CMyString提供默认的构造函数:

    这里采用的做法是给ecx寄存器传入一个地址,给SetInt传入一个参数,然后把这个参数的值写入对应的地址:

    但是如果你在类中定义了构造函数,哪怕是什么也没做,也会调用构造函数的,因为你一旦自己定义了一个构造函数,就不会被提供任何默认构造函数了

    所以我们一直要讨论的,就是没有提供构造函数的情况下,编译器是否会给你提供默认构造函数。

    以下几种情况会提供默认的构造函数:

    1、本类中存在虚函数

    默认的构造函数里进行了虚表的初始化操作:

    虚表中的内容如下:

    2、本类中定义的成员对象有虚函数

    如果类的成员对象只是普通的成员函数,则该类没有构造函数:

    但是如果成员对象由虚函数:

    构造函数内部调用了成员对象的构造函数,其目的显然是为了初始化虚表

    3、父类中存在虚函数(道理同上)

     4、父类中定义的对象带有构造函数

    5、本类中定义的对象带有构造函数

  • 相关阅读:
    超链接导航栏案例
    css中定位功能的特性
    background的使用
    数据库单表查询
    各种算法
    面向对象能做什么和特性及嵌套
    c3算法详解
    面向对象继承
    生成器
    迭代器
  • 原文地址:https://www.cnblogs.com/predator-wang/p/8031071.html
Copyright © 2020-2023  润新知