在VC 的Debug 模式下编译程序,编译器在内存处理中加入许多额外的代码以方便发现程序中可能出现的越界现象。先做个小测试,下面这个程序可以看到有个小越界。
void FMemoryAllocTest()
{
int x[4];
int y1, y2, y3;
x[0] = 0;
x[1] = 1;
x[2] = 2;
x[3] = 3;
x[4] = 4;
y1 = 5;
y2 = 6;
y3 = 7;
}
先看看进入函数时编译器作了什么工作。其中绿色部分是正常的初始化部分,而红色部分就是在Debug 中的额外处理,它把函数栈从栈顶开始的一段内存全部填上0XCC 。
void FMemoryAllocTest()
{
00411B10 push ebp
00411B11 mov ebp,esp
00411B13 sub esp,108h
00411B19 push ebx
00411B1A push esi
00411B1B push edi
00411B1C lea edi,[ebp-108h]
00411B22 mov ecx,42h
00411B27 mov eax,0CCCCCCCCh
00411B2C rep stos dword ptr es:[edi]
而当函数退出时,它会调用一个检查函数测试是否有内存越界。
}
00411B66 push edx
00411B67 mov ecx,ebp
00411B69 push eax
00411B6A lea edx,[ (411B80h)]
00411B70 call @ILT+145(@_RTC_CheckStackVars@8) (411096h)
00411B75 pop eax
00411B76 pop edx
00411B77 pop edi
00411B78 pop esi
00411B79 pop ebx
00411B7A mov esp,ebp
00411B7C pop ebp
00411B7D ret
现在再看看具体的变量在内存中的布局。下面是在我系统中的一个例子。其中红色部分是越界内存,绿色为变量位置。
0x0012FE10 cc cc cc cc cc cc cc cc 07 00 00 00 cc cc cc cc
0x0012FE20 cc cc cc cc 06 00 00 00 cc cc cc cc cc cc cc cc
0x0012FE30 05 00 00 00 cc cc cc cc cc cc cc cc 00 00 00 00
0x0012FE40 01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00
0x0012FE50 68 ff 12 00 03 36 41 00 00 00 00 00 9c f9 05 06
由上面内存可见,变量x,y1,y2,y3 等的内存分布如下表。可见从变量的声明顺序,按照地址递减的顺序依次分配内存,而且每个变量都不是连续存在,而是被0xcc 包围。这样如果我们在调试状态下察看内存内容,有经验的话可以幸运的发现是否有内存越界。或者,可以通过这一功能,开发特殊的工具来进行检测。
0x0012FE10 | cc cc cc cc | cc cc cc cc | Y3 | cc cc cc cc |
0x0012FE20 | cc cc cc cc | Y2 | cc cc cc cc | cc cc cc cc |
0x0012FE30 | Y1 | cc cc cc cc | cc cc cc cc | X[0] |
0x0012FE40 | X[1] | X[2] | X[3] | cc cc cc cc |
0x0012FE50 | EBP | Return address | … | … |
函数退出时,编译器捕捉到了这一越界,弹出一个对话框: