天天看点

『C』栈帧什么是栈帧函数调用过程总结

什么是栈帧

栈帧也叫过程活动记录,是编译器用来实现过程/函数调用的一种数据结构。C语言中,每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量。从逻辑上讲,栈帧就是一个函数执行的环境:函数参数、函数的局部变量、函数执行完后返回到哪里等等。

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

注意:ebp指向当前位于系统栈最上边一个栈帧的底部,而不是系统栈的底部。严格说来,“栈帧底部”和“栈底”是不同的概念;esp所指的栈帧顶部和系统栈的顶部是同一个位置。

函数调用过程

示例代码

#include <stdio.h>
#include <stdlib.h>

int Add(int left, int right){

	int sum = 0;

	sum = left + right;

	return sum;

}

int main(){

	int num1 = 1;
	int num2 = 2;
	int result = 0;

	result = Add(num1, num2);

	printf("result: %d\n", result);
	
	system("pause");
	return 0;
}
           

_tmainCRTStartup函数的调用

我们对该代码进行调试,查看调用堆栈如下:

『C』栈帧什么是栈帧函数调用过程总结

我们可以发现,main函数是在_tmainCRTStartup函数中调用的。而_tmainCRTStartup是在mainCRTStartup中调用的。

下面看一下_tmainCRTStartup函数的栈帧。

『C』栈帧什么是栈帧函数调用过程总结

main函数的调用

既然main函数被调用的,那么这个程序第一步应该会为main函数分配栈空间。调试中查看反汇编。

int main(){
/* 将ebp压栈处理(方便函数返回之后的现场恢复) */
00C51420  push        ebp  

/* 这里使esp的值赋给ebp,产生新的ebp */
00C51421  mov         ebp,esp  

/* 给esp减去一个16进制数字0E4H,产生新的esp */
00C51423  sub         esp,0E4h  

00C51429  push        ebx  
00C5142A  push        esi  
00C5142B  push        edi  

/* 将栈帧与开辟的空间全部初始化为0xCCCCCCCC */
00C5142C  lea         edi,[ebp-0E4h]  
00C51432  mov         ecx,39h  
00C51437  mov         eax,0CCCCCCCCh  

00C5143C  rep stos    dword ptr es:[edi] 

/* 创建局部变量num1、num2、result */
	int num1 = 1;
00C5143E  mov         dword ptr [num1],1  
	int num2 = 2;
00C51445  mov         dword ptr [num2],2  
	int result = 0;
00C5144C  mov         dword ptr [result],0  
           

图解如下

『C』栈帧什么是栈帧函数调用过程总结

过程分析

  • 首先_tmainCRTStartup函数调用main函数。
  • 将ebp压栈处理,保存指向栈底的ebp的地址(方便函数返回之后的现场恢复)。
  • 将esp的值赋给ebp,产生新的ebp。
  • 给esp减去一个16进制数0E4H(为main函数预开辟空间)。
  • push ebx、esi、edi。
  • lea指令,加载有效地址。
  • 初始化预开辟的空间为0xCCCCCCCC。
  • 创建变量num1、num2和result。

Add函数的调用

先查看main函数中调用Add之前的反汇编:

result = Add(num1, num2);
	
/* 参数压栈,先压num2 */
00C51453  mov         eax,dword ptr [num2]  
00C51456  push        eax  

/* 参数压栈,压num1 */
00C51457  mov         ecx,dword ptr [num1]  
00C5145A  push        ecx  

/* call指令调用Add函数 */
00C5145B  call        _Add (0C510E6h)  
00C51460  add         esp,8  
00C51463  mov         dword ptr [result],eax  
           

逐语句执行到call指令,如下,跳转到Add函数。

『C』栈帧什么是栈帧函数调用过程总结

再来看Add函数的反汇编:

int Add(int left, int right){
/* 将ebp压栈处理(方便函数返回之后的现场恢复) */
00C513D0  push        ebp  

/* 这里使esp的值赋给ebp,产生新的ebp */
00C513D1  mov         ebp,esp 

/* 给esp减去一个16进制数字0CCH,产生新的esp */ 
00C513D3  sub         esp,0CCh  

00C513D9  push        ebx  
00C513DA  push        esi  
00C513DB  push        edi  

/* 将栈帧与开辟的空间全部初始化为0xCCCCCCCC */
00C513DC  lea         edi,[ebp-0CCh]  
00C513E2  mov         ecx,33h  
00C513E7  mov         eax,0CCCCCCCCh 
 
00C513EC  rep stos    dword ptr es:[edi]  

/* 创建局部变量sum */
	int sum = 0;
00C513EE  mov         dword ptr [sum],0  

/* 获取形参left和right的值,相加之后存到sum中 */
	sum = left + right;
00C513F5  mov         eax,dword ptr [left]  
00C513F8  add         eax,dword ptr [right]  
00C513FB  mov         dword ptr [sum],eax  

/* 将结果存到寄存器,通过寄存器带回函数的返回值 */
	return sum;
00C513FE  mov         eax,dword ptr [sum]  

}

/* 寄存器edi、esi、ebx出栈 */
00C51401  pop         edi  
00C51402  pop         esi  
00C51403  pop         ebx 

/* 将ebp赋给esp,使esp指向ebp指向的地方,释放Add函数的栈帧 */ 
00C51404  mov         esp,ebp  

/* ebp出栈,将出栈的内容给ebp(即main函数的ebp),回到main函数的栈帧 */
00C51406  pop         ebp  

/* 出栈一次,将出栈的内容当做地址,并跳转到该地址处(call指令的下一条) */
00C51407  ret  
           

图解如下

『C』栈帧什么是栈帧函数调用过程总结

注意:C语言函数调用参数的压栈顺序为从右向左,上述例子中就是num2先压栈,num1后压栈。

过程分析

  • 将num2存入eax寄存器,将eax压栈。
  • 将num1存入ecx寄存器,将ecx压栈。
  • call指令的调用,先将call指令的下一条指令的地址压栈。然后跳转到Add函数。
  • 将main函数的ebp压栈,方便函数返回之后的现场恢复。
  • 将esp的值赋给ebp,产生新的ebp,即Add函数的ebp。
  • 给esp减去一个16进制的数0CCH,为Add函数预开辟空间。
  • push ebx、esi、edi。
  • lea指令,加载有效地址。
  • 初始化预开辟的空间为0xCCCCCCCC。
  • 创建变量sum。
  • 获取形参left和right的值再相加,将结果存到sum中。
  • 将结果存储到eax寄存器,通过寄存器带回函数的返回值。
  • pop edi、esi、ebx。
  • 将ebp的值赋给esp,是esp指向ebp指向的地方。释放Add函数的栈帧。
  • ebp出栈,将出栈的内容给ebp(即main函数的ebp),回到main函数的栈帧。
  • ret指令,出栈一次,将出栈的内容当做地址,并跳转到该地址处(call指令的下一条指令的地址)。

总结

  • ebp寄存器:扩展基址指针寄存器(extended base pointer)其内存放一个指针,该指针指向系统栈最上面一个栈帧的底部。
  • esp寄存器:扩展栈指针寄存器(extended stack pointer),是指针寄存器的一种,用于存放函数栈顶指针。esp为栈指针,用于指向栈的栈顶(下一个压入栈的活动记录的顶部)。