从汇编看函数调用

时间:2023-01-19 08:08:19

首先介绍几个名词:
栈帧:也叫过程活动记录,是编译器用来实现过程/函数调用的一种数据结构。栈帧中保存了该函数的返回地址和局部变量。
寄存器:CPU内部用来存放数据的一些小型存储区域,用来暂时存放参与运算的数据和运算结果。常用的寄存器有:
ESP:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶。
EBP:基址指针寄存器(extended base pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部。
EIP:指令寄存器(extended instruction pointer),其内存放着一个指针,该指针永远指向下一条等待执行的指令地址。
EAX:累加器(accumulator),一般用来保存函数的返回值。

函数调用过程中系统栈的变化可见另一篇博文数据结构之—栈和递归&函数调用

下面具体讲函数调用的步骤及汇编语言中的相关指令。

函数调用大概包括以下几个步骤:

  1. 参数入栈:将参数从从右向左依次压入系统栈中。
  2. 返回地址入栈:将当前代码区调用指令的下一条指令地址压入栈中,供函数返回时继续执行。
  3. 代码区跳转:处理器从当前代码区跳转到被调用函数的入口处。
  4. 栈帧调整:具体包括
    (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        //弹出当前栈顶元素,即弹出栈帧中的返回地址,完成栈帧恢复工作
            //让处理器跳转到弹出的返回地址,恢复调用前的代码区

总的过程可参考下图:
从汇编看函数调用