首先打印出全局变量的地址。
在mainCRTStartup()函数起始位置下断点,然后在内存窗口监测静态全局变量地址。
单步步过,寻找影响全局静态变量内存地址的语句。
可以看到其在断下时,全局静态变量地址的值就已经有了,因为已初始化的全局变量的值会被写入到exe文件中,所以其在模块加载时,就已经有了值,是在mainCRTStartup()函数之前的。
我们继续测试,在C++编译器环境下,将函数的返回值赋值给全局静态变量的情况。
该函数在_cinit()
中的第二个_initterm
调用里被执行,_cinit()
的作用为初始化浮点协处理器和初始化全局变量。
F11跟进_cinit
此时到了第二个_intitterm
按F10(不要按F11跟进去)自动跳转到在GetInt函数头部下的断点的位置。
第一个为_initterm
官方的全局变量初始化,第二个_initterm
才为用户的全局变量初始化。
全局变量结束
我们继续探测全局变量的值被释放的结束的地方。
在main函数return处下断点,单步步过到进程结束的位置,查看全局静态变量值的变化。
一路F10跟到MainCRTStartup中的exit(mainret);
处,全局静态变量内存的值仍未发生变动,此时单步执行exit时,程序结束。
所以,我们可以判定,全局变量的生命周期是从所处模块装载到所处模块卸载。
全局静态变量主要用途就是限制导出,实现其函数和变量的私有化,编译器通过限制导出机制来控制其跨文件访问的。
导入:使用其他模块中的符号。
导出:提供某个符号给其他的模块用。
例如:静态函数
static void foo()
,只能在本文件中使用,不可以跨文件调用,这样则有利于开发过程中的私有化,从而摘轻各自开发者的责任。
早期编译器的私有概念是通过static
来实现的,后来才完善这个概念,并逐步发展为其他的面向对象语言,比如C++。
在没有面向对象概念的时候,使用static来实现私有化。
使用限制导出思想的demo
main.c
Test.c
控制跨文件访问
编译器编译阶段将全局静态变量进行处理,在链接阶段时候,其他文件便不能够访问本文件中的全局静态变量了,会产生报错。
但是仅仅是编译器层面做的处理,全局静态变量的值依旧存在内存中,可以用如下的方法进行访问。
main.cpp
Test.cpp
名称粉碎(Name-mangling)又名命名粉碎或命名重组,是指在目标文件符号表和连接过程中使用的名字通常与编译目标文件的源程序中的名字不一样,编译器将目标源文件中的名字进行了调整。
编译器对局部静态变量使用了名称粉碎机制。
首先将其声明成全局变量,然后将其作用域插入到全局变量名称中去,类似于snTest_fooD
通过这种方式将全局变量限制为在某函数里面才可以访问。
不同编译器厂商对局部静态变量的名称粉碎机制存在差异,有些会将参数和返回值也加入到重组后的名称中,名称粉碎和编译器厂商的习惯相关,不属于标准,所以,不同的厂商不同的版本,甚至不同的版本规则都不一样。
编译器的名称粉碎机制测试方法
修改各项函数属性,编译后,打开对应的obj文件,搜索局部静态变量名,查看不同属性参数的修改对于名称粉碎后的局部静态变量名的影响。
将以上代码编译称为obj文件
打开obj文件,搜索局部静态变量名nTest1
其在vc6.0的c编译器下的名称粉碎为
_?nTest1@?1??TestLocal@@9@9
将其局部静态变量放入函数内的代码块中,编译后观察名称粉碎的变化
其名称粉碎后的结果为
_?nTest1@?2??TestLocal@@9@9
可以看到由?1变成了?2这里大致可以推测,?x
表示层级。
名称粉碎识别关键参数
全局静态变量不进行名称粉碎不影响从标识符到内存地址的识别,局部静态变量不名称粉碎会影响。
编译器通过名称粉碎的方式做语法检查,关键是集成了变量名、作用域名、作用域的层级编号。
上述代码是给编译器看的,告诉编译器全局变量的snTest的初值为999;
静态局部变量定义处没有产生赋值的汇编代码,所以在函数执行时不会被赋值。
静态局部变量如果赋初值,则会和已初始化的全局变量一样被写入到文件中,存储在数据区中的已初始化的全局变量区。
查看exe文件26a30处
如果未赋初值,则会存储在未初始化的全局变量区,都不会产生赋值的汇编指令。
在C编译器下报错error C2099: initializer is not a constant
在C++编译器环境下
c++的语法允许局部静态变量初始化为变量的值,c语言不允许。
当采用C++编译器时,名称粉碎规则会发生改变。
调用方式、返回值、函数参数、及函数参数的数量均会影响到其名称粉碎规则的改变。
_?nTest1@?1??TestLocal@@YAXH@Z@4HA
VC++6.0 Debug中watch窗口解析名称粉碎bug
watch窗口用的C编译器的名称粉碎规则,所以其无法正常显示cpp文件中的局部静态变量信息。
当静态局部变量赋初值为变量时,储存在未初始化区,会产生代码。
会产生汇编代码
存储在未初始化全局变量区
当静态全局变量赋值为变量之后,VC++6.0编译器会在其存储位置附近增加一个字节来存储是否赋初值的状态。
VC++6.0中,一个位存储一个静态全局变量是否被赋初值的状态。
其他编译器存储状态的位置和大小可能不一样,但是思路一样。
(&nTest2)[1] = 0;
将这个标志位的值给修改掉了,所以导致了静态变量重复赋初值。
在VC++6.0编译器中,当赋初值为函数参数的局部静态变量超过8个时,会新增加一个字节来记录状态
科锐逆向 钱林松老师
by:科锐37期学员
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2020-4-20 09:32
被flag0编辑
,原因: