函数栈帧的创建和销毁

Posted 北川_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了函数栈帧的创建和销毁相关的知识,希望对你有一定的参考价值。

目录

各种寄存器的作用

eax是“累加器”(accumulator),它是很多加法乘法指令的缺省寄存器
ebx是“基地址”(base)寄存器,在内存寻址时存放基地址
ecx是计数器(counter), 是重复(REP)前缀指令和LOOP指令的内定计数器。
edx:总是被用来放整数除法产生的余数。
esp:寄存器存放当前线程的栈顶指针
ebp:寄存器存放当前线程的栈底指针

main()函数的调用

VS2013中mainCRTStartup()函数内部调用__tmainCRTStartup()函数,
__tmainCRTStartup()函数内部调用main()函数

通过汇编观察函数调用过程

int Add(int x, int y)

	int z = 0;
	z = x + y;
	return z;


int main(void)

	int a = 10;
	int b = 20;
	int c = 0;
	c = Add(a, b);
	printf("%d\\n", c);
	return 0;

汇编代码

int main(void)

00301900  push        ebp  
00301901  mov         ebp,esp  
00301903  sub         esp,0E4h  
00301909  push        ebx  
0030190A  push        esi  
0030190B  push        edi  
0030190C  lea         edi,[ebp+FFFFFF1Ch]  
00301912  mov         ecx,39h  
00301917  mov         eax,0CCCCCCCCh  
0030191C  rep stos    dword ptr es:[edi]  
0030191E  mov         ecx,30C003h  
00301923  call        0030132A  
	int a = 10;
00301928  mov         dword ptr [ebp-8],0Ah  
	int b = 20;
0030192F  mov         dword ptr [ebp-14h],14h  
	int c = 0;
00301936  mov         dword ptr [ebp-20h],0  
	c = Add(a, b);
0030193D  mov         eax,dword ptr [ebp-14h]  
00301940  push        eax  
00301941  mov         ecx,dword ptr [ebp-8]  
00301944  push        ecx  
00301945  call        003010B9  
0030194A  add         esp,8  
0030194D  mov         dword ptr [ebp-20h],eax  
	printf("%d\\n", c);
00301950  mov         eax,dword ptr [ebp-20h]  
00301953  push        eax  
00301954  push        307B30h  
00301959  call        003010D7  
0030195E  add         esp,8  
	return 0;
00301961  xor         eax,eax  

上面提到main函数也是被调用的,所以当代码走到main函数中时,调用main函数的函数的栈帧已经被创建好了。

main()函数栈帧开辟过程

第3行把ebp压栈,第4行把esp的值赋给ebp,第5行esp减去0E4h,此时esp和ebp之间就是main函数的栈帧

6-8行把ebx、esi、edi压栈

lea表示load effective address加载有效地址
第9行到第12行将main函数栈帧的内容全部初始化位cccccccc

此时main函数栈帧开辟好了。
第16行将ebp-8的位置初始化为10,这是变量a的空间,18行将ebp-14h的位置赋值给20,为变量b,20行ebp-20h的位置赋值为0,为变量c

接下来第22行把ebp-14h的值(也就是b的20)赋给eax,并把eax压栈,把ebp-8的值(也就是a的10)赋给ecx,把ecx压栈。然后第26行call指令调用Add()函数,在调用call指令时会把call指令的下一条指令的地址压栈

Add()函数栈帧开辟过程

接下来调用Add()函数
Add()函数的汇编

int Add(int x, int y)

00301790  push        ebp  
00301791  mov         ebp,esp  
00301793  sub         esp,0CCh  
00301799  push        ebx  
0030179A  push        esi  
0030179B  push        edi  
0030179C  lea         edi,[ebp+FFFFFF34h]  
003017A2  mov         ecx,33h  
003017A7  mov         eax,0CCCCCCCCh  
003017AC  rep stos    dword ptr es:[edi]  
003017AE  mov         ecx,30C003h  
003017B3  call        0030132A  
	int z = 0;
003017B8  mov         dword ptr [ebp-8],0  
	z = x + y;
003017BF  mov         eax,dword ptr [ebp+8]  
003017C2  add         eax,dword ptr [ebp+0Ch]  
003017C5  mov         dword ptr [ebp-8],eax  
	return z;
003017C8  mov         eax,dword ptr [ebp-8]  

003017CB  pop         edi  
003017CC  pop         esi  
003017CD  pop         ebx  
003017CE  add         esp,0CCh  
003017D4  cmp         ebp,esp  
003017D6  call        0030124E  
003017DB  mov         esp,ebp  
003017DD  pop         ebp  
003017DE  ret

从第3行开始的一系列操作和上面的main函数中一样,依然是为函数开辟栈帧,并且把Add()函数栈帧空间初始化为cccccccc。

第16行把ebp-8的位置赋值为0,作为变量z的空间,18行把ebp+8位置的值放到eax中,19行把ebp+12的位置的值放到eax中,完成了两个变量的相加
第20行把eax中的30放到ebp-8也就是变量z的空间

return z,局部变量z在函数执行结束后销毁了,所以第22行的意思是把放在z变量中的两个数求和的结果保存在eax中。

Add()函数栈帧销毁过程

第24-26行把edi、esi、ebx出栈,然后把ebp赋给esp,pop掉ebp让ebp重新指向main函数栈底的位置,ret返回到之前保存的call指令的下一条指令的地址,自此Add函数调用结束,继续执行main函数
返回main函数后形参x和形参y没用了,把他们也出栈
过程如下:

回到main()函数后把eax中保存的30赋给ebp-20也就是c,此时完成了Add()函数栈帧的创建,传递形参,求值并保存在eax中,Add函数销毁,将函数返回值赋给变量c的一系列操作。

以上是关于函数栈帧的创建和销毁的主要内容,如果未能解决你的问题,请参考以下文章

函数栈帧的创建与销毁

函数栈帧的创建与销毁

图解函数栈帧 - 函数的创建与销毁

C语言进阶 顶级神功! 函数栈帧的创建和销毁

函数栈帧的创建与销毁

C语言深入逐汇编详解函数栈帧的创建和销毁过程