首先介绍几个名词:
栈帧:也叫过程活动记录,是编译器用来实现过程/函数调用的一种数据结构。栈帧中保存了该函数的返回地址和局部变量。
寄存器:CPU内部用来存放数据的一些小型存储区域,用来暂时存放参与运算的数据和运算结果。常用的寄存器有:
ESP:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶。
EBP:基址指针寄存器(extended base pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部。
EIP:指令寄存器(extended instruction pointer),其内存放着一个指针,该指针永远指向下一条等待执行的指令地址。
EAX:累加器(accumulator),一般用来保存函数的返回值。
函数调用过程中系统栈的变化可见另一篇博文数据结构之—栈和递归&函数调用
下面具体讲函数调用的步骤及汇编语言中的相关指令。
函数调用大概包括以下几个步骤:
- 参数入栈:将参数从从右向左依次压入系统栈中。
- 返回地址入栈:将当前代码区调用指令的下一条指令地址压入栈中,供函数返回时继续执行。
- 代码区跳转:处理器从当前代码区跳转到被调用函数的入口处。
-
栈帧调整:具体包括
(1)保存当前栈帧状态值,以备后面恢复本栈帧时使用(EBP入栈);
(2)将当前栈帧切换到新栈帧(将ESP值装入EBP,更新栈帧底部);
(3)给新栈帧分配空间(把ESP减去所需空间的大小,抬高栈顶);以下附上《0day安全:软件漏洞分析技术》书中相关配图说明此过程
函数调用时用到的指令序列大致如下:
//....调用前
//调用开始
push 参数3 //假设函数有3个参数,将从右向左依次入栈
push 参数2
push 参数1
call 函数地址 //向栈中压入当前指令在内存中的位置,即保存返回地址;
//跳转到所调用函数的入口地址函数入口处
push ebp //保存旧栈帧的底部
mov ebp,esp //设置新栈帧的底部(栈帧切换)
sub esp,xxx //设置新栈帧的顶部(抬高栈顶,为新栈帧开辟空间)
具体过程可参考下图:
函数返回时的相关指令序列如下:
addesp,xxx //降低栈顶,回收当前的栈帧
pop ebp //将上一个栈帧底部位置恢复到ebp
retn //弹出当前栈顶元素,即弹出栈帧中的返回地址,完成栈帧恢复工作
//让处理器跳转到弹出的返回地址,恢复调用前的代码区
总的过程可参考下图: