概述
一个 C 程序它会编译时候产生什么样的汇编代码?这些汇编代码如何执行?函数的栈是如 何工作的?这里通过一个简单的 C 程序生成的汇编程序来分析上面的这些问题。
一段简单的 c 代码
1 | int g(int x) |
得到的简化的汇编代码
g:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
addl $3, %eax
popl %ebp
ret
f:
pushl %ebp
movl %esp, %ebp
pushl 8(%ebp)
call g
addl $4, %esp
leave
ret
main:
pushl %ebp
movl %esp, %ebp
pushl $8
call f
addl $4, %esp
addl $1, %eax
leave
ret
分析
从 16 行的 main 函数开始执行:假设 ebp
目前指向 128, esp
目前指向 128
17 - pushl %ebp
把调用 mian
函数之前的 ebp
压入栈中,
+-----+-----+
| 128 | 128 | 操作之前的寄存器
+-----+-----+
ebp esp
+-----------+ 128
| ebp 128 |
+-----------+ 124
| |
+-----------+ 操作之后的栈
+-----+-----+
| 128 | 124 | 操作之后的寄存器
+-----+-----+
ebp esp
18 movl %esp, %ebp
把 esp 的值传递到 ebp 中,这样就形成了 main 函数的栈帧。
+-----+-----+
| 128 | 124 | 操作之前的寄存器
+-----+-----+
ebp esp
+-----+-----+
| 124 | 124 | 操作之后的寄存器
+-----+-----+
ebp esp
19 pushl $8 —— 把函数 f 的参数 8 压入栈中
+-----+-----+
| 124 | 120 | 操作之后的寄存器
+-----+-----+
ebp esp
+-----------+ 128
| ebp 128 |
+-----------+ 124
| 8 |
+-----------+ 120
| |
+-----------+ 操作之后的栈
20 call f —— 调用函数 f
+-----+-----+----+
| 128 | 124 | 21 | 操作之前的寄存器
+-----+-----+----+
ebp esp eip
+-----------+ 128
| ebp 128 |
+-----------+ 124
| $8 |
+-----------+ 120
| eip 21 |
+-----------+ 116
| |
+-----------+ 操作之后的栈
+-----+-----+---+
| 124 | 116 | 9 | 操作之后的寄存器
+-----+-----+---+
ebp esp eip
9 pushl %ebp —— 保存 main 函数的栈基地址,和执行位置 eip 以便恢复
+-----+-----+
| 124 | 116 | 操作之前的寄存器
+-----+-----+
ebp esp
+-----------+ 128
| ebp 128 |
+-----------+ 124
| $8 |
+-----------+ 120
| eip 21 |
+-----------+ 116
| ebp 124 |
+-----------+ 112
| |
+-----------+ 操作之后的栈
+-----+-----+
| 124 | 112 | 操作之后的寄存器
+-----+-----+
ebp esp
10 movl %esp, %ebp —— 把 esp 的值传递到 ebp 中,形成函数 f 的新栈帧
+-----+-----+
| 124 | 112 | 操作之前的寄存器
+-----+-----+
ebp esp
+-----+-----+
| 112 | 112 | 操作之后的寄存器
+-----+-----+
ebp esp
11 pushl 8(%ebp) —— 把 f 函数的参数 8 压入到 f 函数的栈中。因为 ebp 是 112,所以 8(%ebp) 120 中的值,也就是 8。
+-----------+ 128
| ebp 128 |
+-----------+ 124
| $8 |
+-----------+ 120
| eip 21 |
+-----------+ 116
| ebp 124 |
+-----------+ 112
| $8 |
+-----------+ 108
| |
+-----------+ 操作之后的栈
+-----+-----+
| 112 | 108 | 操作之后的寄存器
+-----+-----+
ebp esp
12 call g —— 调用 g 函数
+-----+-----+----+
| 112 | 108 | 13 | 操作之前的寄存器
+-----+-----+----+
ebp esp eip
+-----------+ 128
| ebp 128 |
+-----------+ 124
| $8 |
+-----------+ 120
| eip 21 |
+-----------+ 116
| ebp 124 |
+-----------+ 112
| $8 |
+-----------+ 108
| eip 13 |
+-----------+ 104
| |
+-----------+ 操作之后的栈
+-----+-----+---+
| 112 | 104 | 2 | 操作之后的寄存器
+-----+-----+---+
ebp esp eip
2 pushl %ebp —— 把 f 函数的基地址压入栈中以便返回
+-----------+ 128
| ebp 128 |
+-----------+ 124
| $8 |
+-----------+ 120
| eip 21 |
+-----------+ 116
| ebp 124 |
+-----------+ 112
| $8 |
+-----------+ 108
| eip 13 |
+-----------+ 104
| ebp 112 |
+-----------+ 100
| |
+-----------+ 操作之后的栈
+-----+-----+
| 112 | 100 | 操作之后的寄存器
+-----+-----+
ebp esp
3 movl %esp, %ebp —— 把 ebp 重置到 esp 的位置,形成 g 函数的栈帧。
+-----+-----+
| 112 | 100 | 操作之前的寄存器
+-----+-----+
ebp esp
+-----+-----+
| 100 | 100 | 操作之后的寄存器
+-----+-----+
ebp esp
4 movl 8(%ebp), %eax —— 把 g 函数的参数 x 放到 eax 寄存器中。
+-----+-----+---+
| 100 | 100 | 8 | 操作之后的寄存器
+-----+-----+---+
ebp esp eax
5 addl $3, %eax
+-----+-----+----+
| 100 | 100 | 11 | 操作之后的寄存器
+-----+-----+----+
ebp esp eax
6 popl %ebp —— 把栈顶部的元素弹出放入到 %ebp 中,恢复 f 函数的栈。
+-----------+ 128
| ebp 128 |
+-----------+ 124
| $8 |
+-----------+ 120
| eip 21 |
+-----------+ 116
| ebp 124 |
+-----------+ 112
| $8 |
+-----------+ 108
| eip 13 |
+-----------+ 104
| |
+-----------+ 操作之后的栈
+-----+-----+----+
| 112 | 104 | 11 | 操作之后的寄存器
+-----+-----+----+
ebp esp eax
7 ret —— 恢复 f 函数中的 eip
+-----+-----+----+---+
| 112 | 104 | 11 | 8 | 操作之前的寄存器
+-----+-----+----+---+
ebp esp eax eip
+-----------+ 128
| ebp 128 |
+-----------+ 124
| $8 |
+-----------+ 120
| eip 21 |
+-----------+ 116
| ebp 124 |
+-----------+ 112
| $8 |
+-----------+ 108
| |
+-----------+ 操作之后的栈
+-----+-----+----+----+
| 112 | 108 | 11 | 13 | 操作之后的寄存器
+-----+-----+----+----+
ebp esp eax eip
13 addl $4, %esp —— 栈顶回退 4,相当与 pop 但是不需要栈顶值。
+-----------+ 128
| ebp 128 |
+-----------+ 124
| $8 |
+-----------+ 120
| eip 21 |
+-----------+ 116
| ebp 124 |
+-----------+ 112
| |
+-----------+ 操作之后的栈
+-----+-----+----+
| 112 | 112 | 11 | 操作之后的寄存器
+-----+-----+----+
ebp esp eax
14 leave —— 相当于 move %ebp %esp, pop %ebp,恢复 main 函数的栈帧。
+-----------+ 128
| ebp 128 |
+-----------+ 124
| $8 |
+-----------+ 120
| eip 21 |
+-----------+ 116
| |
+-----------+ 操作之后的栈
+-----+-----+----+
| 124 | 116 | 11 | 操作之后的寄存器
+-----+-----+----+
ebp esp eax
15 ret —— 恢复 main 函数的 eip。
+-----+-----+----+----+
| 124 | 116 | 11 | 16 | 操作之前的寄存器
+-----+-----+----+----+
ebp esp eax eip
+-----------+ 128
| ebp 128 |
+-----------+ 124
| $8 |
+-----------+ 120
| |
+-----------+ 操作之后的栈
+-----+-----+----+----+
| 124 | 120 | 11 | 21 | 操作之后的寄存器
+-----+-----+----+----+
ebp esp eax eip
21 addl $4, %esp —— 去掉栈顶元素
+-----------+ 128
| ebp 128 |
+-----------+ 124
| |
+-----------+ 操作之后的栈
+-----+-----+----+
| 124 | 124 | 11 | 操作之后的寄存器
+-----+-----+----+
ebp esp eax
22 addl $1, %eax
+-----+-----+----+
| 124 | 124 | 12 | 操作之后的寄存器
+-----+-----+----+
ebp esp eax
23 leave
+-----+-----+----+
| 128 | 128 | 12 | 操作之后的寄存器
+-----+-----+----+
ebp esp eax
24 ret
结束 main 函数的调用,返回之前的栈帧。此时 eax 中包含返回值 12