C++

C++变量在内存中的位置和访问方式

"C++反汇编学习笔记"

Posted by 1r0nz on October 3, 2016

前言

       变量在内存中除了有局部变量的类型表示外还可以有全局变量和静态变量等。了解变量的作用域和变量的生存周期对理解原生程序的内存布局和结构有较大的帮助。


变量的作用域

       全局变量属于进程作用域,在整个进程中都可以访问到这个全局变量。静态变量属于文件作用域,在当前源码文件中都可以访问。局部变量属于函数作用域,在函数体内都可以访问到。另外,在{}内定义的变量只能在其中访问。


变量的生命周期

       指的是变量所在的内存从申请到释放过程的这段时间。变量所在内存被分配,代表变量生命开始。变量内存被释放,代表变量生命结束。


变量和常量的区别

       常量在程序执行前就存在了,pe文件中的只读数据节中,常量的节属性被修饰为不可写;而全局变量和静态变量则在属性为可读写的数据节中。


变量在反汇编文件中的识别

       具有初始值的全局变量,其值在链接时被写入所创建的pe文件中,当用户执行该文件时,操作系统先分析这个pe中的数据,将各个节中的数据填入对应的虚拟内存地址中,这时全局变量就已经存在了,等pe的分析和加载工作完成以后,才开始执行入口点的代码。因全局变量可以不受作用域的影响,在程序中的任何位置访问和操作。
       对全局变量初步分析得出,它和常量类似,被写入文件中,因此生命周期与所在模块相同。全局变量和局部变量最大不同为生命周期。全局变量在进入文件时加载,局部变量创建只在各自作用域,通过栈平衡释放局部变量空间。
global
global1
如上图所示访问全局变量使用立即数来访问,在编译时期就已经确定地址。局部变量需要进入作用域内申请栈空间,利用esp和ebp访问。这里需要注意的是全局变量在内存中的地址顺序是先定义的变量在低地址,后定义的变量在高地址。局部变量相反


局部静态变量

       全局变量和全局静态变量在内存结构和访问原理上都是一样的,相当于全局静态变量等价于全局变量。
局部静态变量不会随作用域的消失而消失,并且在未进入作用域之前就已经存在,生命周期也和全局变量相同
局部静态变量会预先被作为全局变量处理,而它的初始化部分是赋值操作。然而根据C++的定义,静态变量只能初始化一次,如何多次使用静态变量?首先看一下代码
static
地址0x004252cc中保存了局部静态变量的一个标志,这个标志占位一个字节。通过位运算,将标志中的一位数据置1,以此判断局部静态变量是否已经被初始化过(无非是0和1而已)。由于一个静态变量只是使用了1位 ,而1个字节数据占8位,因此这个标志可以同时表示8个局部变量的初始状态。
当局部静态变量被初始化为一个常量时,这个局部静态变量在初始化过程中不会产生任何代码。如图
static1
静态局部变量多次初始化都是常量,经后都不会发生变化,所以编译器直接将其用全局变量的方式处理,优化代码,提升了效率。但还是不可以超出其作用域访问。同时,局部静态变量在编译期间采用名称粉碎法,让作用域内的静态局部变量无法让其他作用域内的静态局部变量可见,从而避免冲突。如图:
static2
名称粉碎后,在原有名称中加入了其所在的作用域和类型等信息。这一点和重载函数有点像,也是先粉碎函数名称再组合出新名称。
总结:如图
static3
在分析过程中,如果遇到以上代码块,可判定局部静态变量的初始化过程。在分析过程中应注意对测试标志位的操作,其立即数为1,2,8等2次幂。


堆变量

       堆变量是所有变量表现形式中比较容易识别的。所有使用malloc或者new实现堆变量的申请,返回的数据是对变量的空间地址。相应使用free和delete完成堆空间释放。保存堆空间首地址为变量大小为4字节的指针类型。
       C++中的new和delete属于运算符,没有定义重载的情况下,它们的执行过程与malloc和free类似。如下
nd
nd1
使用new申请堆空间最终也会使用到”_nh_malloc_dbg”和malloc。当它们被执行后,将会返回所申请堆空间的首地址。
在分析过程中,关于堆空间的释放不能只看delete与free,还需要结合new和malloc确认所操作的是同一个堆空间。

  • 堆空间的存储信息
           申请堆空间的过程中调用函数_heap_alloc_dbg,其中使用_CrtMemBlockHeader结构描述了堆空间中的各个成员。内存中堆空间的表示形式是双向链表结构,双向链表存储堆空间的每个节点。结构体定义如下:
    sstruct
    sstruct1
    通过此结构管理申请的堆空间。释放堆空间其实就是根据堆数据的首地址将所释放的堆从链表中脱链,完成free或者delete操作
    现在来看下堆结构在内存中的表现形式,如下图
    sstruct2
           new和malloc函数返回的地址为堆数据地址0x00431BF0,堆数据地址减4后,其数据为0xFDFDFDFD,这是往上越界检查标志。堆结构从高地址到低地址按定义的顺序排列。堆数据地址减8后数据为0x2A,表示词堆空间为第0x2A次申请堆操作,说明此前多次申请过堆。堆数据空间容量存储在地址0x00431BE0处,该堆空间占10字节大小。地址0x00431BD0处为上一个堆空间首地址。地址0x00431BD4处的数据为0,表示没有下一个堆空间。堆空间被释放后,下一次申请会检查上一次释放的堆空间是否满足要求。如果满足则申请的堆空间地址为刚释放过的堆空间地址。