VC编译选项“基本运行时检查”的作用

从VS新建一个C++工程,Debug的配置中,“基本运行时检查”这个选项默认值为“两者”,也就是同时包含“堆栈帧”和“未初始化的变量”检查。

VC编译选项“基本运行时检查”的作用_第1张图片

一、堆栈帧检查

先将选项设置为“默认值”,默认值意味着不检查,写一个这样的函数

void test()
{
	int val[3];
	val[0] = 0x11111111;
	val[1] = 0x22222222;
	val[2] = 0x33333333;
	val[3] = 0;  // 越界
}

其汇编代码大概如下

push ebp
mov ebp,esp
sub esp,4C
push ebx
push esi
push edi
mov eax,4
imul ecx,eax,0
mov dword ptr ss:[ebp+ecx-C],11111111
mov eax,4
shl eax,0
mov dword ptr ss:[ebp+eax-C],22222222
mov eax,4
shl eax,1
mov dword ptr ss:[ebp+eax-C],33333333
mov eax,4
imul ecx,eax,3
mov dword ptr ss:[ebp+ecx-C],0   // 越界写入
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret 

越界写入前,栈空间内容是这样的

00EFF760  11111111  
00EFF764  22222222  
00EFF768  33333333  
00EFF76C  00EFF7C0  // 在这段汇编中,这里是保存的ebp值

越界写入后

00EFF760  11111111  
00EFF764  22222222  
00EFF768  33333333  
00EFF76C  00000000  

因为违规写入了不属于自己的栈空间,会导致内存数据异常,程序其他地方访问了该地址就会引发内存错误

VC编译选项“基本运行时检查”的作用_第2张图片

而出现这种内存错误的时候可能已经离BUG事发地很远很远了,排查BUG会耗费你大量的时间。

因此微软在编译器里加入了栈检查的功能,这样就能当场抓住BUG。让我们把选项改为“堆栈帧”后汇编如下

push ebp
mov ebp,esp
sub esp,D4
push ebx
push esi
push edi
lea edi,dword ptr ss:[ebp-D4]
mov ecx,35
mov eax,CCCCCCCC
rep stosd // 将栈空间初始化为0xCCCCCCCC
mov eax,4
imul ecx,eax,0
mov dword ptr ss:[ebp+ecx-10],11111111
mov eax,4
shl eax,0
mov dword ptr ss:[ebp+eax-10],22222222
mov eax,4
shl eax,1
mov dword ptr ss:[ebp+eax-10],33333333
mov eax,4
imul ecx,eax,3
mov dword ptr ss:[ebp+ecx-10],0
push edx
mov ecx,ebp
push eax
lea edx,dword ptr ds:[<>]
call  // 检查栈末尾的值是否为0xCCCCCCCC
pop eax
pop edx
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret 

再看这次开启了检查后的越界写入前的栈空间

010FFCB0  11111111  
010FFCB4  22222222  
010FFCB8  33333333  
010FFCBC  CCCCCCCC  
010FFCC0  010FFD94  

越界写入后

010FFCB0  11111111  
010FFCB4  22222222  
010FFCB8  33333333  
010FFCBC  00000000  
010FFCC0  010FFD94  

原理很简单,加入了栈检查后,初始化栈空间时会多4字节,并把所有内容填充为0xCC,然后通过嵌入的RTC_CheckStackVars函数来检查栈底的4字节是否为0xCCCCCCCC,如果不是则会当场引发异常,省去了你排查出事点的时间。

VC编译选项“基本运行时检查”的作用_第3张图片

二、未初始化变量检查

写测试代码

void test()
{
	int val1;
	int val2 = val1;
}

汇编代码

push ebp
mov ebp,esp
sub esp,48
push ebx
push esi
push edi
mov eax,dword ptr ss:[ebp-4]
mov dword ptr ss:[ebp-8],eax
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret 

而打开检查后的汇编如下

push ebp
mov ebp,esp
sub esp,4C
push ebx
push esi
push edi
mov byte ptr ss:[ebp-49],0
cmp byte ptr ss:[ebp-49],0
jne dddd.F1040
push 
call 
add esp,4
mov eax,dword ptr ss:[ebp-4]
mov dword ptr ss:[ebp-8],eax
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret 

看上去也很简单,对于未初始化的变量设为0,并且判断其等于0就通过嵌入的一个函数__RTC_UninitUse来引发异常

VC编译选项“基本运行时检查”的作用_第4张图片

对于Release配置来说,这个选项默认就是”默认值“的,不用任何检查。

所以“基本运行时检查”功能应该只在开发阶段开启,来辅助我们避免写出糟糕的代码。

你可能感兴趣的:(Windows编程)