一个简单 C 程序的汇编代码分析

概述

一个 C 程序它会编译时候产生什么样的汇编代码?这些汇编代码如何执行?函数的栈是如 何工作的?这里通过一个简单的 C 程序生成的汇编程序来分析上面的这些问题。

一段简单的 c 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int g(int x)
{
return x + 3;
}

int f(int x)
{
return g(x);
}

int main(void)
{
return f(8) + 1;
}

得到的简化的汇编代码

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