函数调用原理

以下方程序为例。

#include <stdio.h>

int main(void)
{
    int apple = 10;
    int pear = 20;
    int total = 0;
    printf("apple = %d, pear = %d.\n", apple, pear);
    total = apple + pear;
    return 0;
}

第一步,函数参数入栈printf 函数调用之前,参数从右向左入栈,所以入栈顺序为——变量 pear 的值,变量 apple 的值,字符串 "apple = %d, pear = %d.\n" 的地址。

pop <printf 函数的参数>
; ...

第二步,返回地址入栈,准备跳转。调用 call 指令,此时存储在指令寄存器 ip 中的值是 printf 函数下一条语句 total = apple + pear; 对应的机器指令的地址即返回地址,该地址入栈,同时指令寄存器 ip 的值修改为 printf 函数在代码段中的第一条指令的地址。

call printf
; 等价于以下汇编程序
; push ip
; mov ip, <printf 函数入口地址>

第三步,保存原栈帧的栈底地址,通过特定的两个寄存器之间赋值来设置新栈帧的栈底地址,通过修改特定一个寄存器值来设置新栈帧的栈顶地址,然后正式执行函数。开始执行 printf 函数时,会进行三步操作——在 printf 函数栈帧中保存原栈帧即 main 函数栈帧的栈底地址;将 main 函数栈帧的栈顶地址作为 printf 函数栈帧的栈底地址;为 printf 函数的局部变量开辟足够的空间。三步操作执行完之后便开始执行 printf 函数的主体机器指令段。

push ebp
mov ebp, esp
; 栈空间往低地址方向扩展,故用减法指令
sub esp, <printf 函数局部变量所占空间字节数>

第四步,销毁新栈帧的局部变量空间,恢复原栈帧的栈底地址,往特定寄存器装载返回地址,销毁函数参数空间,前四部都完成后原栈帧的栈底地址也能恢复printf 函数的主体机器指令段执行完毕后,便开始收尾工作——将 esp 恢复为为 printf 函数局部变量开辟空间之前的值;将 ebp 恢复为 main 函数栈帧的栈底地址;将 eip 恢复为语句 total = apple + pear; 对应的机器指令地址;将 esp 值恢复为为 printf 函数的参数开辟空间之前的值,恢复后,esp 的值恰好是 total 的地址。

mov esp, ebp
mov ebp, [esp]
mov ip, [esp]
pop <printf 函数的参数>

猜你喜欢

转载自blog.csdn.net/weixin_36725931/article/details/85208168