本文为看雪论坛优秀文章
看雪论坛作者ID:flag0
全局静态变量
数据存储:
已初始化的存储在数据区中的已初始化变量区。
未初始化的存储在数据区中的未初始化变量区。
作用域:文件作用域。
本质:是受编译器按语法约束的全局变量。
作用:私有化某些变量和方法,以文件为单位对源码进行控制和管理。
生命周期:从所处模块装载到所处模块卸载。
#include #include static int g_nTest = 0x996; int main(){ printf("%p\r\n",&g_nTest); system("pause"); return 0;}
在mainCRTStartup()函数起始位置下断点,然后在内存窗口监测静态全局变量地址。
单步步过,寻找影响全局静态变量内存地址的语句。
可以看到其在断下时,全局静态变量地址的值就已经有了,因为已初始化的全局变量的值会被写入到exe文件中,所以其在模块加载时,就已经有了值,是在mainCRTStartup()函数之前的。
我们继续测试,在C++编译器环境下,将函数的返回值赋值给全局静态变量的情况。
#include #include int GetInt(){ printf("Hello world!"); return 0x996;} int nTest1 = GetInt(); int main(){ system("pause"); return 0;}
该函数在_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:static char* msg = "Hello";char* GetMsg(){ return msg;}
Test.c:
printf("%s\r\n",GetMsg());
控制跨文件访问
编译器编译阶段将全局静态变量进行处理,在链接阶段时候,其他文件便不能够访问本文件中的全局静态变量了,会产生报错。
但是仅仅是编译器层面做的处理,全局静态变量的值依旧存在内存中,可以用如下的方法进行访问。
main.cpp:
#include #include static int g_nTest = 0x996;int g_nTest2 = 0x123;void printFun(); int main(){ printFun(); //printf("%p\r\n",&g_nTest2); system("pause"); return 0;}
Test.cpp:
#include extern int g_nTest2; void printFun(){ printf("%x\r\n",(&g_nTest2)[-1]);}
局部静态变量
数据存储:
已初始化的存储在数据区中的已初始化变量区。
未初始化的存储在数据区中的未初始化变量区。
作用域:与所在函数作用域相同。
生命周期:与全局静态变量相同。
作用:局部静态变量可以在过程或函数重复运行的时候保留上次运行的值。
#include #include void TestLocal(){ static int nTest1 = 0x996; printf("%d\r\n",nTest1);} int main(){ TestLocal(); system("pause"); return 0;}
将以上代码编译称为obj文件。
打开obj文件,搜索局部静态变量名nTest1:
其在vc6.0的c编译器下的名称粉碎为:
_?nTest1@?1??TestLocal@@9@9
将其局部静态变量放入函数内的代码块中,编译后观察名称粉碎的变化:
void TestLocal(){ { static int nTest1 = 0x996; printf("%d\r\n",nTest1); }}
其名称粉碎后的结果为
_?nTest1@?2??TestLocal@@9@9
可以看到由?1变成了?2这里大致可以推测,?x表示层级。
名称粉碎识别关键参数
变量名
作用域名
作用域的层级编号
局部静态变量只能被赋一次初值的原因
static int snTest = 999;
上述代码是给编译器看的,告诉编译器全局变量的snTest的初值为999。
静态局部变量定义处没有产生赋值的汇编代码,所以在函数执行时不会被赋值。
局部静态变量初始化为常量的值
静态局部变量如果赋初值,则会和已初始化的全局变量一样被写入到文件中,存储在数据区中的已初始化的全局变量区。 查看exe文件26a30处: 如果未赋初值,则会存储在未初始化的全局变量区,都不会产生赋值的汇编指令。局部静态变量初始化为变量的值
void fooD(int n){ static int nTest = n;}
在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中,一个位存储一个静态全局变量是否被赋初值的状态。 其他编译器存储状态的位置和大小可能不一样,但是思路一样。#include #include void TestLocal(int n){ static int nTest2 = n; printf("%p:",&nTest2); printf("%d\r\n",nTest2); (&nTest2)[1] = 0; nTest2++;} int main(){ TestLocal(10); TestLocal(20); TestLocal(30); system("pause"); return 0;}
(&nTest2)[1] = 0;将这个标志位的值给修改掉了,所以导致了静态变量重复赋初值。
在VC++6.0编译器中,当赋初值为函数参数的局部静态变量超过8个时,会新增加一个字节来记录状态:
致谢
科锐逆向 钱林松老师 by : 科锐37期学员 - End -看雪ID:flag0
https://bbs.pediy.com/user-873556.htm
*这里由看雪论坛 flag0 原创,转载请注明来自看雪社区。推荐文章++++
* 短视频某手sig3算法调用
* Android逆向之一款有缘的apk 第一篇
* ptmalloc代码研究
* 深入窥探动态链接
* PspCidTable引起的思考和探索
好书推荐
﹀ ﹀ ﹀ 公众号ID:ikanxue 官方微博:看雪安全 商务合作:wsc@kanxue.com 戳 “阅读 原文 ” 一起来充电吧!