08 变量和作用域,call
1. 全局变量
全局变量访问方式是立即数间接访问
普通全局变量和const全局常量分布在不同的内存分页中,satic全局变量和普通全局变量没有区别:
const int g_nTest1 = 123;
int g_nTest2 = 456;
static int g_nTest3 = 789;
int main(int argc, char *argv[]) {
printf("%p %d\n", &g_nTest1, g_nTest1);
printf("%p %d\n", &g_nTest2, g_nTest2);
printf("%p %d\n", &g_nTest3, g_nTest3);
return 0;
}
对于局部静态变量,存在一个是否初已始化的标志,防止重入,VC6代码逻辑如下:
VC6中,如果存在多个static局部变量,则用每个bit表示每个是否已初始化。
VS2019中为了防止多线程冲突,用了TLS,如果已初始化就把标志设为0x800000001, 0x800000002, 0x800000003等,下图00411A8C的一句里的0x104偏移是TLS数据所在位置,每个线程都有一个。
2. 局部变量
以返回地址为分界线,往0方向走的是局部变量,往地址增量方向走的是参数。
3. 堆变量
new和delete,malloc和free可以靠函数签名识别,如果签名未识别出,跟到函数内部可以看到HeapAlloc和HeapFree等类似函数的调用。
4. 函数调用约定
extern "C"
int __fastcall Test1(int nParam1, int nParam2, int nParam3) {
return nParam1 + nParam2 + nParam3;
}
extern "C"
int __cdecl Test2(int nParam1, int nParam2, int nParam3) {
return nParam1 - nParam2 - nParam3;
}
extern "C"
int __stdcall Test3(int nParam1, int nParam2, int nParam3) {
return nParam1 * nParam2 * nParam3;
}
以上代码按C的名称粉碎规则生成函数名:
__cdecl: _Test2
__stdcall: _Test3@12
__fastcall: @Test1@12
如果函数内部没有平栈操作,考虑__cdecl。
如果函数内部直接使用ecx和edx的值,考虑__fastcall,函数内平栈。
5. 函数返回值
返回char -> al -> movsx
返回unsigned char -> al -> movzx
返回short -> ax -> movsx
返回unsigned short -> ax -> movzx
返回int -> eax
返回unsigned int -> eax
返回long long -> edx.eax
返回unsigned long long -> edx.eax
返回float -> ST(0)或xmm0
返回double -> ST(0)或xmm0