1. 栈帧
计算机使用栈这样的结构来支持函数调用,栈用来传递过程参数、存储返回信息、保存寄存器信息用于恢复、存储局部变量等。每一次函数调用,系统都会在栈中开辟一块空间用来保存执行状态,为每次函数调用分配的栈空间成为栈帧。
上图描绘了栈帧的结构,帧指针和栈顶指针标示了栈帧的范围,帧指针指示栈帧的起始位置,通常使用寄存器%ebp来保存帧指针,使用寄存器%esp来保存栈顶指针。
假设函数P调用函数Q,则P传递给Q的参数(即实参)保存在P帧的尾部,P帧结尾保存着调用Q完成之后的返回地址(返回地址即调用Q完成之后执行的下一条指令的地址)。Q帧首先保存寄存器%ebp的值(即P帧起始位置),然后保存一些寄存器的值以及局部变量,如果Q帧又调用了R帧,则Q帧尾部保存传递给R的实参,Q帧最后保存调用R完成之后的返回地址。
在被调用函数栈帧中,对于传进来参数可以使用相对于寄存器%ebp的正偏移量访问。
2. call指令和ret指令
call指令用来调用另一个函数,执行call指令的效果即将返回地址入栈并跳转到被调用函数的起始指令,返回地址就是调用函数中call指令后面的那一条指令;
ret指令用来从栈中弹出返回地址,并跳转到这个地址;
3. 函数调用的寄存器使用规则
一个函数调用另一个函数时,被调用函数可能会覆盖调用函数保存在寄存器中的值,因此我们需要有一个统一的规则规定调用时如何使用和保存寄存器的旧值。
根据规则,寄存器%eax(通常用来保存返回值)、%edx和%ecx是调用者负责保存旧值的寄存器,被调用函数可以直接覆盖这些寄存器的旧值;寄存器%ebp、%ebx、%esi和%edi是被调用者负责保存旧值的寄存器,被调用函数在覆盖这些寄存器之前要在自己的栈帧中先保存寄存器的旧值,并负责在返回前恢复这些寄存器的旧值。