我常常在想一些莫名其妙的问题,走路也想,吃饭也想,睡觉也想,先看段代码:
00BE1380 push ebp
00BE1381 mov ebp,esp
00BE1383 sub esp,0D8h
00BE1389 push ebx
00BE138A push esi
00BE138B push edi
00BE138C lea edi,[ebp-0D8h]
00BE1392 mov ecx,36h
00BE1397 mov eax,0CCCCCCCCh
00BE139C rep stos dword ptr es:[edi]
00BE139E mov esi,esp
00BE13A0 push 0Ch
00BE13A2 call dword ptr [__imp__malloc (0BE82B0h)]
00BE13A8 add esp,4
00BE13AB cmp esi,esp
00BE13AD call @ILT+300(__RTC_CheckEsp) (0BE1131h)
00BE13B2 mov dword ptr [heap_array],eax
00BE13B5 mov dword ptr [idx],2
00BE13BC mov eax,dword ptr [heap_array]
00BE13BF mov dword ptr [eax],0Ah
00BE13C5 mov eax,dword ptr [heap_array]
00BE13C8 mov dword ptr [eax+4],14h
00BE13CF mov eax,dword ptr [heap_array]
00BE13D2 mov dword ptr [eax+8],1Eh
00BE13D9 xor eax,eax
代码第一行将ebp值入栈,接着将当前esp的值赋给ebp,好吧,从此以后这个值,也就是ebp成了函数栈帧的基址,接着栈指针esp突然渐少0D8h,也就是13 * 16 + 8 = 216个字节,也就是说,在栈里面分配216个字节单元,接着依次将ebx、esi、edi入栈,栈顶指针再次减少3 * 4 = 12个字节,而代码:
00BE138C lea edi,[ebp-0D8h]
本质上是edi = ebp-0D8h,因为不可以使用mov edi,ebp-0D8h这样的指令,所以使用lea(load effective address)。
接着将ecx设置为36h,也就是十进制3 * 16 + 6 = 54,接着:
00BE1397 mov eax,0CCCCCCCCh
00BE139C rep stos dword ptr es:[edi]
第一行将eax设置为 0CCCCCCCCh。接着用eax的值去初始化从ebp-0D8h开始往高地址的216个字节空间,这216个字节空间是上面代码分配的栈空间。如果这个时候用printf函数输出这段内存的内容,会显示:
烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫
也就是108个中文汉字“烫”,因为两个0xCC合起来是一个汉字“烫”,而216字节可显示108个“烫”字。
以上代码是在debug模式下,编译器自动生成的一些汇编代码,与程序实际内容毫无关系。
接着正文代码:
00BE139E mov esi,esp
00BE13A0 push 0Ch
00BE13A2 call dword ptr [__imp__malloc (0BE82B0h)]
00BE13A8 add esp,4
00BE13AB cmp esi,esp
00BE13AD call @ILT+300(__RTC_CheckEsp) (0BE1131h)
00BE13B2 mov dword ptr [heap_array],eax
接着将esp赋值给esi,也就是让esi等于当前栈顶指针, 00BE13A0 push 0Ch 中0Ch是一个立即数(中国人总喜欢把简单的东西搞得复杂化,我在看杨季文老师的汇编教材时,看了几遍都没明白他说的“立即数”是啥意思,其实就是一个程序中的常数嘛),0Ch 等于10进制的12,也就是将数字12入栈,接着调用C函数库函数malloc,malloc函数申明于stdlib.h文件,其原型是:
void* malloc(size_t size);
而这个12也就是作为实参传给size, 函数返回值放在eax中,接着代码是:
00BE13A8 add esp,4
这个地方我看了好久觉得晕,为什么要让栈指针向高地址方向移动4个字节?后来恍然大悟:因为malloc是C库函数,它的调用方式__cdecl,所以调用这个函数之后的栈由调用方清理,也就是主程序清理,那么 add esp,4用来将栈里面四个字节的内存清理掉,但是为什么是4个字节?而不是更多或者更少?因为malloc有一个参数size,这个参数占四个字节,当调用malloc函数结束以后,栈中仍然才在4个字节的空间是为size参数开辟的,我们必须释放掉。
接着代码:
00BE13AB cmp esi,esp
00BE13AD call @ILT+300(__RTC_CheckEsp) (0BE1131h)
这是一种保护措施,这个时候(调用malloc函数之后)理论上说,esi应该与esp相等,第二行我猜测:如果esi与esp不相等,那么ZF标志位应该为0,这说明有错误出现(这种错误概率是非常小的), 而函数CheckEsp会根据ZF是否为0来决定一些情况,比如ZF等于0,说明程序出错了,而这个错误是内存错误,这是一个很严重的错误。
接着代码:
00BE13B2 mov dword ptr [heap_array],eax
00BE13B5 mov dword ptr [idx],2
也就是:
上文中我们调用了malloc函数,这个函数的返回值放在eax中,现在执行 mov dword ptr [heap_array],eax ,也就是将heap_array = eax,通俗地说就是将malloc返回值赋给heap_array。
代码mov dword ptr [idx],2含义是:[idx] = 2。
后续代码:
00BE13BC mov eax,dword ptr [heap_array]
00BE13BF mov dword ptr [eax],0Ah
使用了eax寄存器作为中介容器,因为不可以用mov指令将立即数0Ah赋值给dword ptr [heap_array],所以最终结果是:[heap_array] = 0Ah = 10。同理:
00BE13C5 mov eax,dword ptr [heap_array]
00BE13C8 mov dword ptr [eax+4],14h
这段代码的含义: [heap_array + 4] = 14h = 1 * 16 + 4 = 20.
而代码:
00BE13CF mov eax,dword ptr [heap_array]
00BE13D2 mov dword ptr [eax+8],1Eh
含义是:[heap_array + 8] = 1Eh = 1 * 16 + 14 = 30
我们发现 [heap_array]、[heap_array + 4]、[heap_array + 8],这是什么?数组嘛!而heap_array又是函数malloc返回的,所以总结起来,这段汇编代码实际上对应:
int *heap_array = (int*)malloc(3 * sizeof(int));
int idx = 2;
heap_array[0] = 10;
heap_array[1] = 20;
heap_array[2] = 30;
别急,还有最后一行:
00BE13D9 xor eax,eax
这行使用xor指令将eax清零,如果这个时候外层函数调用完毕以后,那么eax的值就将作为返回值返回,那么
就是:
return 0;
好了,整段汇编代码分析完毕,但是这段程序存在一个问题,即没有使用delete去释放malloc函数分配的内存。
Okay,that's all.