汇编函数的实现

本文来讨论一下汇编中函数的实现。我的环境:ubuntu 18.04,gcc 7.5.0

我们首先看下我们的 c/c++ 函数代码,编译器会给我们生成什么样的汇编函数代码。

1
2
3
4
5
6
7
8
9
void func() {
int a = 10;
return;
}

int main() {
func();
return 0;
}

我们通过编译,并且反汇编来查看汇编代码

1
2
gcc main.c -g -m32 -o main
objdump -d -M intel main

如下是汇编代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
0000054b <__x86.get_pc_thunk.ax>:
54b: 8b 04 24 mov eax,DWORD PTR [esp]
54e: c3 ret
54f: 90 nop

000004ed <func>:
4ed: 55 push ebp
4ee: 89 e5 mov ebp,esp
4f0: 83 ec 10 sub esp,0x10
4f3: e8 53 00 00 00 call 54b <__x86.get_pc_thunk.ax>
4f8: 05 e4 1a 00 00 add eax,0x1ae4
4fd: c7 45 fc 0a 00 00 00 mov DWORD PTR [ebp-0x4],0xa
504: 90 nop
505: c9 leave
506: c3 ret

00000532 <main>:
532: 55 push ebp
533: 89 e5 mov ebp,esp
535: e8 11 00 00 00 call 54b <__x86.get_pc_thunk.ax>
53a: 05 a2 1a 00 00 add eax,0x1aa2
53f: e8 a9 ff ff ff call 4ed <func>
544: b8 00 00 00 00 mov eax,0x0
549: 5d pop ebp
54a: c3 ret

我们来解释一下,我们知道生成的二进制执行过程前,会做很多初始化工作,之后才会跳到 main 函数执行。

  • ebp 是栈底指针,push ebp 先将栈底指针存储到栈中,此时 esp 会减 4,我们目前是 32 位机器架构,所以栈的每一个 slot 是 4 字节。
  • mov ebp,esp 将栈顶指针 esp 寄存器的值存入 ebp 中。
  • 接下来是 call 54b <__x86.get_pc_thunk.ax>add eax,0x1aa2 这两句汇编,我们先不看
  • call 4ed <func> 就是在调用 func 函数,call 指令会将 IP 寄存器保存到栈中,并跳到 func 地址出执行 func 函数
  • func 函数中也是先将 ebp 寄存器压栈,然后将 esp 寄存器的内容写入到 ebp 中。
  • sub esp, 0x10 是给 func 函数开辟一段栈内存用于函数内部使用
  • mov dword ptr [ebp-0x04], 0xa ,这句代码就是 int a = 10,编译器给 a 在栈内存 [ebp-0x04] 处分配了内存,并且标明了是 dword 4 字节大小
  • leave 的意思为 mov esp, ebppop ebp 这两句代码
  • 最后 ret 的意思是 pop IP,从栈中取出数据给 IP 寄存器赋值,相当于跳到刚开始 call 为止的下一句代码
  • 在 main 函数中,继续 pop ebpret 即可退出 main 函数的栈