函数栈帧(函数调用过程详解)

每次函数调用,都为函数开辟一块空间,称为栈帧

我们应该知道,栈是从高地址向低地址延伸的。每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息。寄存器ebp指向当前的栈帧的底部(高地址),我们称为栈底指针,寄存器esp指向当前的栈帧的顶部(低地址),我们称为栈顶指针。

下面我们以一个简单的程序为例,分析一下函数的调用过程:

#include

int Add(int x, int y)
{
	int ret = 0;
	ret = x + y;
	return ret;
}
int main()
{
	int a = 10;
	int b = 20;
	int c = 0;
	c = Add(a, b);
	printf("%d\n", c);
	system("pause");
	return 0;
}

以下是这段代码的反汇编代码:

    11: int main()
    12: {
00FE1420  push        ebp  
00FE1421  mov         ebp,esp  
00FE1423  sub         esp,0E4h  
00FE1429  push        ebx  
00FE142A  push        esi  
00FE142B  push        edi  
00FE142C  lea         edi,[ebp-0E4h]  
00FE1432  mov         ecx,39h  
00FE1437  mov         eax,0CCCCCCCCh  
00FE143C  rep stos    dword ptr es:[edi]  
    13: 	int a = 10;
00FE143E  mov         dword ptr [a],0Ah  
    14: 	int b = 20;
00FE1445  mov         dword ptr [b],14h  
    15: 	int c = 0;
00FE144C  mov         dword ptr [c],0  
    16: 	c = Add(a, b);
00FE1453  mov         eax,dword ptr [b]  
00FE1456  push        eax  
00FE1457  mov         ecx,dword ptr [a]  
00FE145A  push        ecx  
00FE145B  call        _Add (0FE10E6h)  
00FE1460  add         esp,8  
00FE1463  mov         dword ptr [c],eax  
    17: 	printf("%d\n", c);
00FE1466  mov         esi,esp  
00FE1468  mov         eax,dword ptr [c]  
00FE146B  push        eax  
00FE146C  push        0FE5858h  
00FE1471  call        dword ptr ds:[0FE9110h]  
00FE1477  add         esp,8  
00FE147A  cmp         esi,esp  
00FE147C  call        __RTC_CheckEsp (0FE1140h)  
    18: 	system("pause");
00FE1481  mov         esi,esp  
00FE1483  push        0FE585Ch  
00FE1488  call        dword ptr ds:[0FE9118h]  
00FE148E  add         esp,4  
00FE1491  cmp         esi,esp  
00FE1493  call        __RTC_CheckEsp (0FE1140h)  
    19: 	return 0;
    20: }

接下来我们要分析上面这一段反汇编代码:
首先要知道,连接器对控制台程序设置的入口函数是mainCRTStartup,mainCRTStartup 再调用main 函数;
所以当我们操作时,首先会给mainCRTStartup()函数开辟一段空间,然后让esp和ebp指向在这段空间的顶部和底部,然后_mainCRTStartup()函数又会调用mainCRTStartup()函数:
函数栈帧(函数调用过程详解)_第1张图片

push ebp;//把ebp压入栈帧

函数栈帧(函数调用过程详解)_第2张图片

mov         ebp,esp  ;//把esp给ebp

函数栈帧(函数调用过程详解)_第3张图片
加载有效地址

sub         esp,0E4h  //把esp向上生长0E4h

函数栈帧(函数调用过程详解)_第4张图片

push        ebx  
push        esi  
push        edi  
lea         edi,[ebp-0E4h]  
mov         ecx,39h  
mov         eax,0CCCCCCCCh  
rep stos    dword ptr es:[edi]  


/*3个push 分别把ebx,esi,edi 3个寄存器压入栈中。 
lea 就是把 [ebp-4Ch]的地址放在edi中,ebp-0E4h是3个push之前esp的位置做2个move操作,ecx寄存器的值为39h,eax为初始化值00CCCCCCCCh 
然后rep stos:实际上就是把初始化开辟的空间,初始值为eax寄存器内的值0CCCCCCCCh, 
 从edi开始(edi保存的esp的位置),向高地址的部分进行字节拷贝,每一次拷贝4个字节。 
 拷贝的内容就是eax的内容,拷贝次数为39h次。*/

函数栈帧(函数调用过程详解)_第5张图片

实参入栈:

    13: 	int a = 10;
00FE143E  mov         dword ptr [a],0Ah  
    14: 	int b = 20;
00FE1445  mov         dword ptr [b],14h  
    15: 	int c = 0;
00FE144C  mov         dword ptr [c],0 

函数栈帧(函数调用过程详解)_第6张图片

形参入栈:

16: 	c = Add(a, b);
00FE1453  mov         eax,dword ptr [b]  
00FE1456  push        eax  
00FE1457  mov         ecx,dword ptr [a]  
00FE145A  push        ecx  

函数调用指令:call

00FE145B  call        _Add (0FE10E6h)  
00FE1460  add         esp,8  

Add函数调用过程:

     5: int Add(int x, int y)
     6: {
00FE13D1  mov         ebp,esp  
00FE13D3  sub         esp,0CCh  
00FE13D9  push        ebx  
00FE13DA  push        esi  
00FE13DB  push        edi  
00FE13DC  lea         edi,[ebp-0CCh]  
00FE13E2  mov         ecx,33h  
00FE13E7  mov         eax,0CCCCCCCCh  
00FE13EC  rep stos    dword ptr es:[edi]  
     7: 	int ret = 0;
00FE13EE  mov         dword ptr [ret],0  
     8: 	ret = x + y;
00FE13F5  mov         eax,dword ptr [x]  
00FE13F8  add         eax,dword ptr [y]  
00FE13FB  mov         dword ptr [ret],eax  
     9: 	return ret;
00FE13FE  mov         eax,dword ptr [ret]  
    10: }
00FE1401  pop         edi  
00FE1402  pop         esi  
00FE1403  pop         ebx  
00FE1404  mov         esp,ebp  
00FE1406  pop         ebp  
00FE1407  ret  

函数调用结束后,释放栈帧。

你可能感兴趣的:(学习笔记,C/C++语言)