汇编语言中的函数调用
C语言从原则上来说,只能在函数内执行代码。
所以任何 text 段都对应有自己的帧栈。
本文主要谈一下 call leave ret 三条与函数调用紧密相关的指令。
call 指令
call 的不同形式
call Label 所谓直接跳转
call *operand 所谓间接跳转
080483f7 <caller>:
804840c: e8 dc ff ff ff call 80483ed <callee>
上边代码段中 caller 中 call 80483ed <callee>
就是直接跳转
call 之前的准备
080483f7 <caller>:
80483fa: 83 ec 08 sub $0x8,%esp
80483fd: c7 44 24 04 1c a0 04 movl $0x804a01c,0x4(%esp)
8048404: 08
8048405: c7 04 24 01 00 00 00 movl $0x1,(%esp)
gcc ABI约定被调函数的参数保存在调用者的栈帧(frame)上,所以 caller 需要将 callee 的参数放在自己的栈帧上。这个过程分两步完成。
- 开栈。
将栈指针向下(栈由高位向下扩展)移动 8 bytes。这是因为两个参数一个是指针类型,一个是整数类型,均需要 4 bytes 来存储。事实上由于对齐的要求,即使参数类型小于 4 bytes 编译器还是会为其分配 4 bytes 的栈空间, - 反向保存参数。
gcc ABI规定,反向保存参数,故栈顶保存最后一个参数。如果参数类型大于 4 bytes,IA32 需要用两条movl
指令来传递参数。
值得注意的是,ABI只规定了参数在栈上存储的空间顺序,并没有规定参数压入栈中的时间顺序
call 干了什么
存储返回地址。
call
指令将 (%eip)
对应指令之后的那条指令的起始地址放在栈上,也就是把 %eip + n
放在 (%esp)
,其中 n 为 (%eip)
中指令的长度。然后跳转到 call
的操作数所指的地址。
call之后发生了什么
080483ed <callee>:
80483ed: 55 push %ebp // sub $0x4,%esp
// mov %ebp,(%esp)
80483ee: 89 e5 mov %esp,%ebp
80483f0: 83 ec 2c sub $0x2c,%esp
8048405: c7 45 e8 01 00 00 00 movl $0x1,-0x18(%ebp)
804840c: c7 45 ec 02 00 00 00 movl $0x2,-0x14(%ebp)
8048413: c7 45 f0 03 00 00 00 movl $0x3,-0x10(%ebp)
804841a: c7 45 f4 04 00 00 00 movl $0x4,-0xc(%ebp)
8048421: c7 45 f8 05 00 00 00 movl $0x5,-0x8(%ebp)
8048428: c7 45 fc 06 00 00 00 movl $0x6,-0x4(%ebp)
- 切换栈帧。
被调函数首先将旧的栈底指针%ebp
压到自己的栈帧上,然后以其地址(而非内容)作为自己的栈底指针的内容,此时新的栈帧已经形成了,由于%esp == %ebp
,故新的栈帧暂时没有使用栈内存。 - 开栈。
当局部变量数量太大时,编译器会选择将局部变量放在栈帧上。gcc的ABI约定,函数栈帧的大小必须 16 bytes 对齐,所以sub
指令所减去的16进制数以c结尾(栈帧上已经有上一帧%ebp
) 。 - 初始化局部变量。
这里对局部变量的初始化是以栈底指针为基准的,此处值得注意的是(%ebp)
中存储的是上一帧的%ebp
leave 指令
8048411: c9 leave
leave
所做的工作是还原上一帧的栈底指针与栈顶指针,等效于
mov %ebp,%esp // 把栈顶指针置为本帧的栈底(同时也是存储上一帧栈底指针内容的地址),
popl %ebp // 还原上一帧的栈底指针,此时 %esp 指向返回地址
ret 指令
8048412: c3 ret
ret
所做的工作是弹出栈顶的返回地址,并跳转到此地址。此时 %esp
指向调用函数所存储的被调函数的最后一个参数。
杂记
一个完整的栈帧上会有什么?
从底到顶依次是:
1. 上一帧的 `%ebp`
1. ABI 约定被调用者保存(如果有)的调用者的三个寄存器的内容 `%ebx` `%esi` `%edi`
2. 局部变量
2. 对齐空白
3. ABI 约定调用者保存(如果有)的自己的三个寄存器的内容 `%eax` `%edx` `%ecx`
3. 所调用的函数的参数
3. 返回地址(本帧的 %esp所指,下一帧的 0x4(%ebp))
人就像是被蒙着眼推磨的驴子,生活就像一条鞭子;当鞭子抽到你背上时,你就只能一直往前走,虽然连你也不知道要走到什么时候为止,便一直这么坚持着。