深入底层剖析过程调用

概述

一个过程调用包括把数据和控制从代码的一部分转移到另一部分。同时,过程还需要为局部变量分配空间,在执行完该部分后退出时释放这些空间。其中,数据传递、局部变量的空间分配和释放通过操作程序栈来实现。

栈帧结构

既然需要操作栈,我们先来看看栈的结构。栈帧是为单个过程分配的部分。
栈帧的大小由栈顶指针%esp和栈底指针%ebp确定。
栈帧结构图

1、发生调用时:当使用call语句调用某个函数时,通常分为两步操作:1)将返回地址压入栈中(即在调用者中执行完调用函数后的下一句的地址)2)将下一条要执行的指令的地址赋为被调用函数的地址(%eip = 该地址)。
2、被调用函数结束时:通常汇编代码中使用ret语句返回到call指令后的指令。leave语句是为返回准备栈:1)先释放空间。2)恢复调用者的%ebp。

//每个函数最后都有这两句
leave  //等价于movl %edp,%esp;这句指令将栈顶指针和栈底指针重合,此时分配的空间为0,释放了空间。然后恢复调用者的ebp,pop %ebp,将当前地址的值(调用者的%ebp)弹出栈,并赋给%ebp,此时%ebp已恢复原来位置。
ret //将下一条执行的指令地址赋为call指令的后一条的地址

接下来以调用者和被调用者的角度理解:
调用者:
1)准备要传递的参数,入栈的顺序为从右到左。
2)保存返回地址,压入栈。
3)将被调用函数的地址---->%eip。
在这里插入图片描述
被调用者:
1)保存调用者的%ebp,入栈。
2)开辟该函数空间
3)执行完后释放空间
4)恢复%ebp、%esp。
5)返回地址---->%eip。
在这里插入图片描述

例子讲解

C语言代码:

#include<stdio.h>

int sum (int x ,int y){
	int z = 0;
	z= x+y;
	return z;

}

void main(){

	int a= 3,b=4;
	int c = sum(a,b); //这里参数入栈顺序是b--->a
	printf("the sum is %d\n",c);


}

Ubantu下查看汇编代码:

main函数:

在这里插入图片描述
前4行是做准备,开辟空间。由第4行可知main函数的空间大小为20个字节。
在这里插入图片描述
接下来准备本地变量a,b,入栈保存。执行赋值后的结果。a,b分别放在esp+14,esp+18处。
在这里插入图片描述

接下来调用sum函数

在这里插入图片描述
先看看ebp,esp的变化。
在这里插入图片描述
为啥ebp是这个值呢?可以看到main函数准备好参数之后,esp的值为0xbffff214。然后存入返回地址、旧的ebp,所以ebp的值就等于0xbffff214-12 = 0xbffff1f8(地址、int型整数、还有参数的大小都是4个字节),也可以得出调用者和被调用者的在栈内的空间是相连的。
在这里插入图片描述
打印edp,发现存的是旧的ebp的值0xbffff228。
在这里插入图片描述
z=x+y的执行情况,先取x、y的值,再进行相加,最后再赋给z。我们打印z的值是7,同时z是保存在ebp-4处,所以查看ebp-4处的值也是7.
在这里插入图片描述
最后返回到main函数。此时的ebp,esp已经恢复到原来的值。并且eip的值将是call指令的下一条指令的地址。下面进行验证:
在这里插入图片描述
在这里插入图片描述
可见call sum指令的下一条的地址为0x0804842e,与eip值相等。
之后程序将执行完main后面的语句,单个过程的调用到此结束。

  • 后面的递归调用、嵌套调用实现基本一致,如果有机会,后面再详细讲解。本篇博客为本人复习所用,刚刚接触,难免有错误,希望大家多多指教。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

J.K.Roling

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值