汇编代码中的函数调用
以下分析全部基于AT&T汇编语法,目标操作数在源操作数的右边,且正确性不做保证
汇编函数调用时堆栈的变化
问题概述
在主程序调用子程序时,子程序的开头两行代码很多都是一样的,例如:
7d15: 55 push %ebp
7d16: 89 e5 mov %esp,%ebp
这是为什么呢
详解
接下来通过一个例子来直观的感受一下。在讲这个例子之前要先讲一下与堆栈相关的寄存器的作用,比较重要。
与堆栈相关的 3 个寄存器是:SS, ESP, EBP。
ESP 寄存器中的内容作为堆栈的当前指针。PUSH, POP, CALL, RET 等指令都与堆栈有关,使用 SS:ESP 指向堆栈单元(栈顶)
EBP 寄存器中的内容作为堆栈的“基准”指针。SS:EBP 指向的地址作为基准地址。在函数(子程序)内部。能够使用 [EBP+马上数] 的形式来取得主程序传递的參数,使用 [EBP-马上数] 的形式来訪问局部变量。
下图就是我们要讲的例子了:

主程序在call之前会将实参压入堆栈, call的时候会将当前IP,也就是返回地址压入堆栈,然后跳转。
跳转到子程序后会先把ebp压栈,然后mov esp, ebp,重点来了,在执行这个命令之前,ebp是指向大地址X的(极端假设主程序的堆栈只有这两个数据),esp指向X-0CH,代表主程序使用的堆栈,执行这个命令之后,ebp指向了X-0CH,esp也指向了X-0CH,代表了子程序使用的新的堆栈,当子程序定义局部变量1和局部变量2的时候,esp便会再次发送生化。同时这个时候通过EBP+08H、EBP+0CH可以访问到主程序传入的参数。
子程序在执行完毕后会有一条leave指令,其实就是
mov ebp, esp
pop esp
相当于将esp重新指向X-0CH,ebp重新指向大地址X,恢复了原来主程序使用的堆栈。
leave执行完后,就ret,ret指令是用栈中的数据(也就是X-08H的地址)弹出到IP中,从而实现跳转。
参考了暴雪对门的文章:https://www.cnblogs.com/bxdm/p/6725873.html
有问题请联系我删除
汇编寄存器使用惯例
问题概述
函数调用中,进入子程序后为什么会执行
7d18: 56 push %esi
7d19: 53 push %ebx
详解
知识来源:深入了解计算机操作系统 第三章
程序寄存器组是唯一能够被所有过程共享的资源。虽然在给定时刻只能有一个过程是活动的,但是我们必须保证当一个过程(调用者)调用另一个过程(被调用者)时,被调用者不会覆盖某个调用者稍后会使用的寄存器的值。为此必须采用一组统一的寄存器使用惯例,所有的过程都必须遵守,包括程序库的过程。
假如没有这些规矩,比如在调用一个过程时,无论是调用者还是被调用者,都可能更新寄存器的值。假设调用者在%edx中存了一个整数值100,而被调用者也使用这个寄存器,并更新成了1000,于是悲剧就发生了。当过程调用完毕返回后,调用者再使用%edx的时候,值已经从100变成了1000,这几乎必将导致程序会错误的执行下去。所以便有如下规矩:
在 IA32 中,寄存器%eax,%edx和%ecx被划分为调用者保存寄存器。当过程 P 调用 Q 时,Q可以覆盖这些寄存器,而不会破坏 P 所需的数据。
寄存器%ebx,%esi和%edi被划分为被调用者保存寄存器。这里 Q 必须在覆盖这些寄存器的值之前,先把他们保存到栈中,并在返回前恢复它们,因为 P(或某个更高层次的过程)可能会在今后的计算中需要这些值。
考虑如下代码:
int P(int x)
{
int y = x*x;
int z = Q(y);
return y+z;
}
过程 P 在调用 Q 之前会先计算 y 的值,而且它必须保证 y 的值在 Q 返回后是可用的。这里有两种方法实现:
①、可以在调用 Q 之前,将 y 的值保存在自己的帧栈中;当 Q 返回时,过程 P 就可以从栈中取出y 的值。换句话说就是调用者 P 自己保存这个值。
②、可以将 y 保存在被调用者保存寄存器中。如果 Q ,或者其它 Q 调用的程序想使用这个寄存器,它必须将这个寄存器的值保存在帧栈中,并在返回前恢复该值。换句话说就是被调用者保存这个值。当 Q 返回到 P 时,y 的值会在被调用者保存寄存器中,或者是因为寄存器根本就没有改变,或者是因为它被保存并恢复了。
这两种方法在 IA32 中是都采用的。

浙公网安备 33010602011771号