简单的函数调用,通过简单的函数调用反汇编可以清楚了解如下
1.栈到底是什么,如何操纵栈的?
2.参数和临时变量是以什么形式在哪存放?
3.如何传递返回值?
举例:
#include <stdio.h>
int add(int a,int b)
{
int c=0;
c=a+b;
return c;
}
int main(void)
{
int x=0;
int y=3;
int z=4;
x=add(y,z);
return 0;
}
这是一个简单的通过调用函数计算两数之和的程序
VC6.0生成的汇编代码如下:
add函数
{
0040D750 push ebp
//把main函数的ebp压栈,ebp=1000,esp=896
0040D751 mov ebp,esp
//得到“新”栈基址,这里的新的意思是每个函数访问属于自己的一块栈区域,其实是相邻的内存区域,或者说栈只有一个。ebp=896,esp=896
0040D753 sub esp,44h
//ebp=896,esp=828
0040D756 push ebx
0040D757 push esi
0040D758 push edi
//ebp=896,esp=816
0040D759 lea edi,[ebp-44h]
0040D75C mov ecx,11h
0040D761 mov eax,0CCCCCCCCh
0040D766 rep stos dword ptr [edi]
//初始化内部变量区
5: int c=0;
0040D768 mov dword ptr [ebp-4],0
//c放入“新”栈基址
6: c=a+b;
0040D76F mov eax,dword ptr [ebp+8]
0040D772 add eax,dword ptr [ebp+0Ch]
//因为“新”栈基地址就是“旧”栈顶地址,所以通过ebp访问传过来的参数,ebp+8到ebp+c是因为ebp上方的8字节用于存储调用函数的调用地址和“旧”堆栈基地址了。
0040D775 mov dword ptr [ebp-4],eax
//运算结果放入c中
7: return c;
0040D778 mov eax,dword ptr [ebp-4]
//用寄存器eax返回结果
8: }
0040D77B pop edi
0040D77C pop esi
0040D77D pop ebx
//恢复寄存器的值,ebp=896,esp=828
0040D77E mov esp,ebp
//恢复“旧”栈顶地址,ebp=896,esp=896,此函数堆栈被释放!
0040D780 pop ebp
//恢复“旧”栈基地址,ebp=1000,esp=900,此时恢复到调用前的栈基地址和顶地址
0040D781 ret
//返回调用点,ebp=1000,esp=904,调用点地址被弹出,返回到调用点
main函数
{
0040D790 push ebp
0040D791 mov ebp,esp
//用栈顶地址作为栈基地址,目的是不和调用前栈空间冲突,为了叙述方便假设此时的栈基址ebp=1000,esp=1000。
0040D793 sub esp,4Ch
//esp下移,开辟出0x4C字节的空间,这个空间是留给内部参数用的,这个空间的大小随内部变量多少由编译器决定。ebp=1000,esp=1000-0x4C=924
0040D796 push ebx
0040D797 push esi
0040D798 push edi
//保护 ebx,esi,edi的值,ebp=1000,esp=924-12=912
0040D799 lea edi,[ebp-4Ch]
0040D79C mov ecx,13h
0040D7A1 mov eax,0CCCCCCCCh
0040D7A6 rep stos dword ptr [edi]
//把内部参数占用的空间每个字节都初始化为0xCC,这个是为了在DUBUG程序的方便,编译器加入的,如果不在DEBUG状态下,这个区域是没有被初始化的,也就是说是随机值。
12: int x=0;
0040D7A8 mov dword ptr [ebp-4],0
13: int y=3;
0040D7AF mov dword ptr [ebp-8],3
14: int z=4;
0040D7B6 mov dword ptr [ebp-0Ch],4
//内部变量放入刚才被初始化为0xCC的内部变量区,x占用四字节在地址9996-9999,y,z一次类推
15: x=add(y,z);
0040D7BD mov eax,dword ptr [ebp-0Ch]
0040D7C0 push eax
0040D7C1 mov ecx,dword ptr [ebp-8]
0040D7C4 push ecx
//把参数按照stdcall方式从右到左压栈,ebp=1000,esp=912-8=904,z首先入栈在908-911,y在904-907
0040D7C5 call @ILT+15(_add) (00401014)
//把返回下一行代码即 add esp,8 的地址压栈,转向add函数,ebp=1000,esp=900,看add函数
0040D7CA add esp,8
//ebp=1000,esp=912,恢复到压入参数前栈基地址和顶地址,这个步骤叫做堆栈修正
0040D7CD mov dword ptr [ebp-4],eax
//返回的变量放到x中
16: return 0;
17: }
现在来总结开始提出的三个问题
1.栈到底是什么,如何操纵栈的?
栈是操作系统分配给程序运行的一块内存区域,有以下特点
1.1、改变堆栈用push, pop,用的esp栈顶指针,而读指针则用ebp栈基指针灵活访问
1.2、每当一个函数跳转到另一个函数时,会在上一个函数用到的栈空间下方开辟空间
2.参数和临时变量是以什么形式在哪存放?
2.1、参数放在旧栈的返回地址和旧栈基地址的上方,而临时变量则在新栈的最上方处,变量名会被编译器连接一个地址,程序在被编译成汇编以后,变量名就是虚无了。
3.如何传递返回值?
3.1、传递一个值的情况下,通过eax传递
可以看出,栈溢出是由于编译器没有检查栈是否还有空间。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/codesnail/archive/2009/10/10/4652385.aspx