过程调用(Procedures)


过程调用包含:

  • 传递数据(过程参数和返回值)
  • 控制从程序的一部分转移到另一个部分

除此之外,过程必须在进入时为局部变量分配空间,并在退出时解分配。

1. 栈帧结构

为单个过程调用所分配的栈的那一部分称为一个栈帧 (stack frame),即一个过程调用对应一个栈帧。栈帧的结构一般如下图所示:
在这里插入图片描述
按栈增长的方向依次是:

  • 帧指针(frame pointer):存储寄存器 %esp 的值
  • 寄存器 (saved registers):其他的寄存器的值拷贝
  • 局部变量 (local variables):不能存储于寄存器中的局部变量
  • 参数 (Arguments):
  • 返回地址(return address):当前过程完成后 (出栈),上次过程调用恢复执行的地址 (栈顶)
  • 栈指针(stack pointer):存储寄存器 %ebp 的值

栈帧的边界分别是两个指针,依据栈增长的方向,下边界为帧指针,上边界为栈指针。栈指针随着过程的执行而移动,因此在访问大部分的信息时都相对帧指针做偏移。

假设过程调用 P (调用方) 调用过程 Q (被调用方)。传入 Q 的参数被存储在 P 的栈帧中,除此之外,当 P 调用 Q 时,P 的返回地址 (return address) 被推入 P 的栈中,形成 P 栈帧的尾部。

过程调用 Q 也使用栈存储任何不能存储于寄存器中的局部变量。这种情况的发生可能因为:

  • 没有足够的寄存器保存所有的局部变量。
  • 一些局部变量是数组或结构体,因此必须通过数组和结构体引用才能访问。
  • 取址操作符 ‘&’ 被应用于局部变量,因此我们必须能为它生成一个地址。

此外,Q 使用栈帧存储任何它所调用的过程所传入的参数。如上图所示,在被调用过程中,第一个参数位于相对 %ebp 8 的偏移处,剩余的参数 (假设它们的数据类型不需要超过 4 字节大小的空间) 存储于连续的 4 字节块,即参数 i 位于相对 %ebp 4 + 4i 的偏移处。更大的参数 (例如结构体或者更大的数字格式) 在栈上需要更大的空间。

如前所述,栈向低地址增长,栈指针 %esp 指向栈顶元素。使用 pushl 和 popl 指令能存储和检索数据到栈中。 对于没有指定初始化值的数据,其空间可以通过简单地将栈指针向下移动合适的距离进行分配。类似的,空间可以通过增加栈指针的值解分配。

2. 控制转移

支持过程调用和返回的指令如下表所示:
在这里插入图片描述
call 指令指定目标 (指令) 地址,被调用的过程从该地址开始。与 Jumps 类型,call 要么是直接的,要么是间接的。在汇编代码中,直接的 call 的目标是一个 label,间接 call 的目标是一个 * 号后跟一个操作数说明符。

call 指令的作用是将返回地址推入栈中,并跳到被调用过程的起始点。返回地址是紧随 call 的指令的地址。当被调用的过程返回时,执行将在此处恢复。ret 指令将返回地址退出栈并跳到返回地址指示的位置。
在这里插入图片描述
图 3.22 表明了 callret 指令执行 mainsum 函数的过程。其 C 代码和部分反汇编代码如下:

1 int accum = 0;
2
3 int sum(int x, int y)
4 {
5 int t = x + y;
6 accum += t;
7 return t;
8 }
sum:
	pushl %ebp
	movl %esp, %ebp
	movl 12(%ebp), %eax
	addl 8(%ebp), %eax
	addl %eax, accum
	popl %ebp
	ret
1 int main()
2 {
3 return sum(1, 3);
4 }

其中调用过程的反汇编代码摘录如下:

Beginning of function sum
1 08048394 <sum>:
2 8048394: 55 					push 	%ebp
...
Return from function sum
3 80483a4: c3 					ret
...
Call to sum from main
4 80483dc: e8 b3 ff ff ff 		call 	8048394 <sum>
5 80483e1: 83 c4 14 			add 	 $0x14,%esp

从代码中,我们可以看到 maincall 指令使用地址 0x080483dc 调用函数 sum。该状态如图 3.22 (a) 所示,使用栈指针 %esp 和 program counter %eip 所指示的值。call 将返回地址 0x080483e1 推入栈中,并跳到函数 sum 的第一条指令,在地址 0x08048394 (图 3.22(b))。函数 sum 一直执行直到它在地址 0x080483a4 遇到 ret 指令。ret 指令从栈中推出值 0x080483e1,并跳到该地址,恢复 main 函数的执行 (图 3.22©)。

leave 指令被用于准备栈的返回。它等价于下述代码序列:

1 movl %ebp, %esp 	//Set stack pointer to beginning of frame
2 popl %ebp 	    //Restore saved %ebp and set stack ptr to end of caller’s frame

或者,这一准备可以通过显式地执行 move 和 pop 操作地序列。当函数返回一个整数或指针时,寄存器 %eax 被用于返回来自函数的值。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值