材料准备
首先手写一个简单的C程序:main.c
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <stdio.h>
void empty();
int add(int i, int j);
void myPrint(int num);
void testParams(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k);
int main()
{
// 测试空函数
empty();
// 测试传2个参数的函数
int i = 3;
int j = 4;
int k = add(i, j);
// 测试传多个参数的函数
testParams(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);
// 测试print函数(不定参数)
myPrint(k);
return 0;
}
void empty()
{
return;
}
int add(int i, int j)
{
int k = i + j;
return k;
}
void myPrint(int num)
{
printf("%s %s %s: %d\n", "hello", "world", "print", num);
}
void testParams(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k)
{
return;
}
然后执行 clang main.c,默认生成 a.out 文件。查看 a.out,可以看到 a.out 是一个 x86_64 架构下的 Mach-O 格式的文件。
1
2
3
4
5
6
➜ 函数调用过程 git:(master) ✗ clang main.c
➜ 函数调用过程 git:(master) ✗ ls
a.out main.c
➜ 函数调用过程 git:(master) ✗ file a.out
a.out: Mach-O 64-bit executable x86_64
➜ 函数调用过程 git:(master) ✗
查看Mach-O
我们使用工具来分析Mach-O文件:MachOView
展开段:__TEXT_text,我们可以看到 main.c 中我们自定义的函数的以汇编指令的形式顺序的排列在这里。
RBP 和 RSP
分析函数调用过程,其实就是分析程序执行过程中指令的跳转和栈内存区域的变化
在函数调用过程中有2个非常重要的指针寄存器:bp 和 sp
- bp始终指向当前最顶部的栈帧的栈底
- sp指向当前最顶部的栈帧的栈顶(原则上是需要指向栈顶,但由于代码优化的原因,并不是总是)

空函数的调用过程
接着让我们用debug的方式来看下 x86_64 下函数的调用过程。首先程序会先进入 main 函数,然后调用 empty 函数,empty 函数非常简单,什么都不做就退出了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1 // 调用 lldb 开始调试 a.out
➜ 函数调用过程 git:(master) ✗ lldb a.out
(lldb) target create "a.out"
Current executable set to 'a.out' (x86_64).
2 // 设置断点到 main
(lldb) breakpoint set --name main
Breakpoint 1: where = a.out`main, address = 0x0000000100000e20
3 // run 程序
(lldb) run
Process 42767 launched: '/Users/guosai/practice_C/函数调用过程/a.out' (x86_64)
Process 42767 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x0000000100000e20 a.out`main
a.out`main:
-> 0x100000e20 <+0>: pushq %rbp
0x100000e21 <+1>: movq %rsp, %rbp
0x100000e24 <+4>: subq $0x40, %rsp
0x100000e28 <+8>: movl $0x0, -0x4(%rbp)
Target 0: (a.out) stopped.
(lldb)
可以看到,程序停留在了 main 函数的第一条指令 pushq %rbp。然后我们再来分析看下 main 函数和 empty 函数的汇编代码(用 disassemble –name 命令)
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
26
27
28
29
30
31
a.out`main:
-> 0x100000e20 <+0>: pushq %rbp
0x100000e21 <+1>: movq %rsp, %rbp
0x100000e24 <+4>: subq $0x40, %rsp
0x100000e28 <+8>: movl $0x0, -0x4(%rbp)
0x100000e2f <+15>: callq 0x100000f60 ; empty
0x100000e34 <+20>: movl $0x3, -0x8(%rbp)
0x100000e3b <+27>: movl $0x4, -0xc(%rbp)
0x100000e42 <+34>: movl -0x8(%rbp), %edi
0x100000e45 <+37>: movl -0xc(%rbp), %esi
0x100000e48 <+40>: callq 0x100000eb0 ; add
0x100000e4d <+45>: movl %eax, -0x10(%rbp)
0x100000e50 <+48>: movl $0x1, %edi
0x100000e55 <+53>: movl $0x2, %esi
0x100000e5a <+58>: movl $0x3, %edx
0x100000e5f <+63>: movl $0x4, %ecx
0x100000e64 <+68>: movl $0x5, %r8d
0x100000e6a <+74>: movl $0x6, %r9d
0x100000e70 <+80>: movl $0x7, (%rsp)
0x100000e77 <+87>: movl $0x8, 0x8(%rsp)
0x100000e7f <+95>: movl $0x9, 0x10(%rsp)
0x100000e87 <+103>: movl $0xa, 0x18(%rsp)
0x100000e8f <+111>: movl $0xb, 0x20(%rsp)
0x100000e97 <+119>: callq 0x100000ed0 ; testParams
0x100000e9c <+124>: movl -0x10(%rbp), %edi
0x100000e9f <+127>: callq 0x100000f20 ; myPrint
0x100000ea4 <+132>: xorl %eax, %eax
0x100000ea6 <+134>: addq $0x40, %rsp
0x100000eaa <+138>: popq %rbp
0x100000eab <+139>: retq
0x100000eac <+140>: nopl (%rax)
1
2
3
4
5
a.out`empty:
0x100000f60 <+0>: pushq %rbp
0x100000f61 <+1>: movq %rsp, %rbp
0x100000f64 <+4>: popq %rbp
0x100000f65 <+5>: retq
要分析一个空函数的调用过程,主要是分析一下几个指令(或指令组合)
指令(指令组合) | 作用 |
---|---|
callq 数值 | 跳转到某函数 |
pushq %rbp 和 movq %rsp, %rbp | 暂存bp指针,并开辟新的栈帧 |
popq %rbp 和 retq | 函数结束,返回上一级函数 |
callq
callq 就是 call 指令(q代表要处理的字节长度,q是8字节,l是4字节,w是2字节)。call 指令指示 CPU 应跳转到某个地址去执行新的函数。
现在我们执行4次step指令,让 CPU 停留在第五条指令上:callq 0x100000f60(去跳转 empty 函数),在分析这条指令之前,我们先来看下 CPU 中各寄存器的状态(register read 命令)
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
26
27
28
29
30
31
32
a.out`main:
0x100000e20 <+0>: pushq %rbp
0x100000e21 <+1>: movq %rsp, %rbp
0x100000e24 <+4>: subq $0x40, %rsp
0x100000e28 <+8>: movl $0x0, -0x4(%rbp)
-> 0x100000e2f <+15>: callq 0x100000f60 ; empty
0x100000e34 <+20>: movl $0x3, -0x8(%rbp)
0x100000e3b <+27>: movl $0x4, -0xc(%rbp)
............
General Purpose Registers:
rax = 0x0000000100000e20 a.out`main
rbx = 0x0000000000000000
rcx = 0x00007ffeefbff5a8
rdx = 0x00007ffeefbff3a8
rdi = 0x0000000000000001
rsi = 0x00007ffeefbff398
rbp = 0x00007ffeefbff370
rsp = 0x00007ffeefbff330
r8 = 0x0000000000000000
r9 = 0x0000000000000000
r10 = 0x0000000000000000
r11 = 0x0000000000000000
r12 = 0x0000000000000000
r13 = 0x0000000000000000
r14 = 0x0000000000000000
r15 = 0x0000000000000000
rip = 0x0000000100000e2f a.out`main + 15
rflags = 0x0000000000000206
cs = 0x000000000000002b
fs = 0x0000000000000000
gs = 0x0000000000000000

可以看到此时:
- rbp = 0x00007ffeefbff370,main栈帧的栈底的地址就是 0x00007ffeefbff370
- rsp = 0x00007ffeefbff330,main栈帧的栈顶的地址就是 0x00007ffeefbff330(因为栈是向下生长的,所以sp小于dp)
由此可以算出 main 的栈帧占的内存空间大小是 0x40 ,正好符合第三条指令:subq $0x40, %rsp
- rip 指向当前等待执行的指令的地址,此时 rip = 0x0000000100000e2f,正好是(0x100000e2f <+15>: callq 0x100000f60)
那么 callq 0x100000f60 指令做了什么呢?我们首先抛出结论:
- 首先 call 指令把当前指令的下一条指令 push 入栈(先把这条指令的地址暂存下来,等后面被调用函数返回的时候才能找到它,并继续执行)
- 找到下一条需要执行的指令的地址(即 empty 的地址),并跳转执行。
首先我们来验证第2点,当前的跳转指令是:callq 0x100000f60,empty 的第一条指令的地址也是 0x100000f60(0x100000f60 <+0>: pushq %rbp)。这里 0x100000f60 是 lldb帮我们计算出来的值,算法是:目的值 = 偏移值+下一条指令的地址,这个逻辑就是地址无关代码(position independent code)
然后我们验证第一点,先看一下当前内存中栈顶附近(rsp = 0x00007ffeefbff330)存储的数值(memory read 指令)
1
2
3
4
5
6
7
memory read --size 4 --format x --count 16 0x00007ffeefbff310
0x7ffeefbff310: 0x00000000 0x00000000 0xefbff398 0x00007ffe
0x7ffeefbff320: 0xefbff380 0x00007ffe (0x00000000 0x00000001)
0x7ffeefbff330: 0x00000001 0x0000000e 0x00000000 0x00000000
0x7ffeefbff340: 0x00000000 0x00000000 0x00000000 0x00000000
(lldb)
上面的(0x00000000 0x00000001)表示0x7ffeefbff328地址存的现在存的值是 0000000100000000(小端模式),后面在会被覆盖成函数返回地址 0x100000e34。
然后执行 step。再次观察寄存器和内存数值的变化:
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
26
27
28
29
memory read --size 4 --format x --count 16 0x00007ffeefbff310
0x7ffeefbff310: 0x00000000 0x00000000 0xefbff398 0x00007ffe
0x7ffeefbff320: 0xefbff380 0x00007ffe (0x00000e34 0x00000001)
0x7ffeefbff330: 0x00000001 0x0000000e 0x00000000 0x00000000
0x7ffeefbff340: 0x00000000 0x00000000 0x00000000 0x00000000
(lldb) register read
General Purpose Registers:
rax = 0x0000000100000e20 a.out`main
rbx = 0x0000000000000000
rcx = 0x00007ffeefbff5a8
rdx = 0x00007ffeefbff3a8
rdi = 0x0000000000000001
rsi = 0x00007ffeefbff398
rbp = 0x00007ffeefbff370
rsp = 0x00007ffeefbff328
r8 = 0x0000000000000000
r9 = 0x0000000000000000
r10 = 0x0000000000000000
r11 = 0x0000000000000000
r12 = 0x0000000000000000
r13 = 0x0000000000000000
r14 = 0x0000000000000000
r15 = 0x0000000000000000
rip = 0x0000000100000f60 a.out`empty
rflags = 0x0000000000000206
cs = 0x000000000000002b
fs = 0x0000000000000000
gs = 0x0000000000000000
可以看到有三个地方变化了
- 0x7ffeefbff328 地址内存的值有(0x00000000 0x00000001)变成了(0x00000e34 0x00000001,即小端模式下的 0000000100000e34),正好是 callq 0x100000f60 的下一条指令的地址 0x100000e34。
- 因为有新的值入栈了,所以rsp的值也需要更新到新的栈顶,即 0x00007ffeefbff330 - 8 = 0x00007ffeefbff328(push入栈的指针占8个字节)
- rip指向了新的指令地址:empty函数的第一条指令:rip = 0x0000000100000f60 a.out`empty
执行完call指令后,执行逻辑就从main跳出来,去执行empty了。

pushq %rbp & movq %rsp, %rbp
所有函数的开头都需要执行这两行函数,它的目的是暂存上一个函数的rbp指针,并把rbp指针更新指向到新的栈帧
- pushq %rbp:暂存一个函数的rbp指针
- movq %rsp, %rbp:rbp指针更新指向到新的栈帧
第一步:验证一下 pushq %rbp,首先看下当前寄存器和内存的状态:
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
26
27
28
(lldb) memory read --size 4 --format x --count 16 0x00007ffeefbff310
0x7ffeefbff310: 0x00000000 0x00000000 0xefbff398 0x00007ffe
0x7ffeefbff320: (0xefbff380 0x00007ffe) 0x00000e34 0x00000001
0x7ffeefbff330: 0x00000001 0x0000000e 0x00000000 0x00000000
0x7ffeefbff340: 0x00000000 0x00000000 0x00000000 0x00000000
(lldb) register read
General Purpose Registers:
rax = 0x0000000100000e20 a.out`main
rbx = 0x0000000000000000
rcx = 0x00007ffeefbff5a8
rdx = 0x00007ffeefbff3a8
rdi = 0x0000000000000001
rsi = 0x00007ffeefbff398
rbp = 0x00007ffeefbff370
rsp = 0x00007ffeefbff328
r8 = 0x0000000000000000
r9 = 0x0000000000000000
r10 = 0x0000000000000000
r11 = 0x0000000000000000
r12 = 0x0000000000000000
r13 = 0x0000000000000000
r14 = 0x0000000000000000
r15 = 0x0000000000000000
rip = 0x0000000100000f60 a.out`empty
rflags = 0x0000000000000206
cs = 0x000000000000002b
fs = 0x0000000000000000
gs = 0x0000000000000000
其中 0x7ffeefbff320 地址中的值(0x00000001 0x0000000e)一会儿是要被当前 rbp 的值(rbp = 0x00007ffeefbff370)覆盖的,然后 rsp 会减“1”,变成 0x00007ffeefbff320。ok,我们执行以下 step,看下结果:
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
26
27
28
(lldb) memory read --size 4 --format x --count 16 0x00007ffeefbff310
0x7ffeefbff310: 0x00000000 0x00000000 0xefbff398 0x00007ffe
0x7ffeefbff320: (0xefbff370 0x00007ffe) 0x00000e34 0x00000001
0x7ffeefbff330: 0x00000001 0x0000000e 0x00000000 0x00000000
0x7ffeefbff340: 0x00000000 0x00000000 0x00000000 0x00000000
(lldb) register read
General Purpose Registers:
rax = 0x0000000100000e20 a.out`main
rbx = 0x0000000000000000
rcx = 0x00007ffeefbff5a8
rdx = 0x00007ffeefbff3a8
rdi = 0x0000000000000001
rsi = 0x00007ffeefbff398
rbp = 0x00007ffeefbff370
rsp = 0x00007ffeefbff320
r8 = 0x0000000000000000
r9 = 0x0000000000000000
r10 = 0x0000000000000000
r11 = 0x0000000000000000
r12 = 0x0000000000000000
r13 = 0x0000000000000000
r14 = 0x0000000000000000
r15 = 0x0000000000000000
rip = 0x0000000100000f61 a.out`empty + 1
rflags = 0x0000000000000206
cs = 0x000000000000002b
fs = 0x0000000000000000
gs = 0x0000000000000000
结果和预料的一样,现在:rsp = 0x00007ffeefbff320,地址 0x7ffeefbff320 存储的值是 0x00007ffeefbff370(小端模式)。

第二步:movq %rsp, %rbp,是把 rsp 的值赋值给 rbp,这样 rbp 就指向了新的栈帧的栈底,代表开辟了了一个新的栈帧,即 empty 函数的栈帧。执行 step ,看下结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
General Purpose Registers:
rax = 0x0000000100000e20 a.out`main
rbx = 0x0000000000000000
rcx = 0x00007ffeefbff5a8
rdx = 0x00007ffeefbff3a8
rdi = 0x0000000000000001
rsi = 0x00007ffeefbff398
rbp = 0x00007ffeefbff320 // rbp的值更新了,目前和 rsp 相同
rsp = 0x00007ffeefbff320
r8 = 0x0000000000000000
r9 = 0x0000000000000000
r10 = 0x0000000000000000
r11 = 0x0000000000000000
r12 = 0x0000000000000000
r13 = 0x0000000000000000
r14 = 0x0000000000000000
r15 = 0x0000000000000000
rip = 0x0000000100000f64 a.out`empty + 4
rflags = 0x0000000000000206
cs = 0x000000000000002b
fs = 0x0000000000000000
gs = 0x0000000000000000

以上就是一个空函数调用时,CPU跳转新函数和新栈帧开辟的逻辑。因为 empty 函数中没有任何实现,所以接着会马上返回。
popq %rbp & retq
所有函数的汇编指令也都是有这两条指令作为结尾,这两条指令的含义是:取回暂存的 rbp 指针并恢复 rbp 寄存器的值,然后取出暂存的返回指令的地址,并跳转执行。
- popq %rbp: 取回暂存的 rbp 指针并恢复 rbp 寄存器的值。
- 取出暂存的返回指令的地址,并跳转执行。
第一步验证:popq %rbp。看下执行这一步之前,寄存器和内存的状态:
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
26
27
28
29
(lldb) register read
General Purpose Registers:
rax = 0x0000000100000e20 a.out`main
rbx = 0x0000000000000000
rcx = 0x00007ffeefbff5a8
rdx = 0x00007ffeefbff3a8
rdi = 0x0000000000000001
rsi = 0x00007ffeefbff398
rbp = 0x00007ffeefbff320
rsp = 0x00007ffeefbff320
r8 = 0x0000000000000000
r9 = 0x0000000000000000
r10 = 0x0000000000000000
r11 = 0x0000000000000000
r12 = 0x0000000000000000
r13 = 0x0000000000000000
r14 = 0x0000000000000000
r15 = 0x0000000000000000
rip = 0x0000000100000f64 a.out`empty + 4
rflags = 0x0000000000000206
cs = 0x000000000000002b
fs = 0x0000000000000000
gs = 0x0000000000000000
(lldb) memory read --size 4 --format x --count 16 0x00007ffeefbff310
0x7ffeefbff310: 0x00000000 0x00000000 0xefbff398 0x00007ffe
0x7ffeefbff320: 0xefbff370 0x00007ffe 0x00000e34 0x00000001
0x7ffeefbff330: 0x00000001 0x0000000e 0x00000000 0x00000000
0x7ffeefbff340: 0x00000000 0x00000000 0x00000000 0x00000000
当前栈顶地址是:rsp = 0x00007ffeefbff320,栈顶地址存储的值是:0xefbff370 0x00007ffe,也就是我们之前暂存的上一个函数的栈帧的基地址。然后执行 step,看下结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(lldb) register read
General Purpose Registers:
rax = 0x0000000100000e20 a.out`main
rbx = 0x0000000000000000
rcx = 0x00007ffeefbff5a8
rdx = 0x00007ffeefbff3a8
rdi = 0x0000000000000001
rsi = 0x00007ffeefbff398
rbp = 0x00007ffeefbff370
rsp = 0x00007ffeefbff328
r8 = 0x0000000000000000
r9 = 0x0000000000000000
r10 = 0x0000000000000000
r11 = 0x0000000000000000
r12 = 0x0000000000000000
r13 = 0x0000000000000000
r14 = 0x0000000000000000
r15 = 0x0000000000000000
rip = 0x0000000100000f65 a.out`empty + 5
rflags = 0x0000000000000206
cs = 0x000000000000002b
fs = 0x0000000000000000
gs = 0x0000000000000000
现在 rbp = 0x00007ffeefbff370,rbp 寄存器恢复了,同时 rsp 增加了 “1”(因为执行了 pop 指令,栈的长度减少了),此时:

第二步执行:retq,这一步会取出当前栈顶的值,即暂存的需要返回到的指令的地址(此时是 0x100000e34)。先看一下当前状态:
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
26
27
28
(lldb) memory read --size 4 --format x --count 16 0x00007ffeefbff310
0x7ffeefbff310: 0x00000000 0x00000000 0xefbff398 0x00007ffe
0x7ffeefbff320: 0xefbff370 0x00007ffe (0x00000e34 0x00000001)
0x7ffeefbff330: 0x00000001 0x0000000e 0x00000000 0x00000000
0x7ffeefbff340: 0x00000000 0x00000000 0x00000000 0x00000000
(lldb) register read
General Purpose Registers:
rax = 0x0000000100000e20 a.out`main
rbx = 0x0000000000000000
rcx = 0x00007ffeefbff5a8
rdx = 0x00007ffeefbff3a8
rdi = 0x0000000000000001
rsi = 0x00007ffeefbff398
rbp = 0x00007ffeefbff370
rsp = 0x00007ffeefbff328
r8 = 0x0000000000000000
r9 = 0x0000000000000000
r10 = 0x0000000000000000
r11 = 0x0000000000000000
r12 = 0x0000000000000000
r13 = 0x0000000000000000
r14 = 0x0000000000000000
r15 = 0x0000000000000000
rip = 0x0000000100000f65 a.out`empty + 5
rflags = 0x0000000000000206
cs = 0x000000000000002b
fs = 0x0000000000000000
gs = 0x0000000000000000
rsp = 0x00007ffeefbff328,rsp 执行当前栈顶,当前栈顶存储的值是 (0x00000e34 0x00000001)。然后执行 step, 看下结果:
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
(lldb) disassemble
a.out`empty:
0x100000f60 <+0>: pushq %rbp
0x100000f61 <+1>: movq %rsp, %rbp
0x100000f64 <+4>: popq %rbp
-> 0x100000f65 <+5>: retq
(lldb) step
Process 45685 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = instruction step into
frame #0: 0x0000000100000e34 a.out`main + 20
a.out`main:
-> 0x100000e34 <+20>: movl $0x3, -0x8(%rbp)
0x100000e3b <+27>: movl $0x4, -0xc(%rbp)
0x100000e42 <+34>: movl -0x8(%rbp), %edi
0x100000e45 <+37>: movl -0xc(%rbp), %esi
Target 0: (a.out) stopped.
(lldb) register read
General Purpose Registers:
rax = 0x0000000100000e20 a.out`main
rbx = 0x0000000000000000
rcx = 0x00007ffeefbff5a8
rdx = 0x00007ffeefbff3a8
rdi = 0x0000000000000001
rsi = 0x00007ffeefbff398
rbp = 0x00007ffeefbff370
rsp = 0x00007ffeefbff330
r8 = 0x0000000000000000
r9 = 0x0000000000000000
r10 = 0x0000000000000000
r11 = 0x0000000000000000
r12 = 0x0000000000000000
r13 = 0x0000000000000000
r14 = 0x0000000000000000
r15 = 0x0000000000000000
rip = 0x0000000100000e34 a.out`main + 20
rflags = 0x0000000000000206
cs = 0x000000000000002b
fs = 0x0000000000000000
gs = 0x0000000000000000
可以看到,执行完 retq 后,CPU指向了需要返回的指令地址:0x100000e34(rip = 0x0000000100000e34);rsp 继续加“1”。此时 函数的栈帧恢复了之前在main函数的状态:

到此为止,一个空函数的调用和返回过程就结束了,我们再来总结一下整个过程:
函数调用过程

传参 & 返回值
函数调用时,有两种传参方式:
- 寄存器够用时,是寄存器传参
- 寄存器不够用时,把参数 push 到栈上,用栈内存传参。
返回值:
- 利用寄存器 eax 存储返回值,返回给上一级函数。
寄存器传参
我们以 add 函数为例分析:
1
2
3
4
5
6
7
8
9
10
// 测试传2个参数的函数
int i = 3;
int j = 4;
int k = add(i, j);
int add(int i, int j)
{
int k = i + j;
return k;
}
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
a.out`main:
-> 0x100000e20 <+0>: pushq %rbp
0x100000e21 <+1>: movq %rsp, %rbp
0x100000e24 <+4>: subq $0x40, %rsp
0x100000e28 <+8>: movl $0x0, -0x4(%rbp)
0x100000e2f <+15>: callq 0x100000f60
0x100000e34 <+20>: movl $0x3, -0x8(%rbp)
0x100000e3b <+27>: movl $0x4, -0xc(%rbp)
0x100000e42 <+34>: movl -0x8(%rbp), %edi // 参数:数字 3 放入 edi 中
0x100000e45 <+37>: movl -0xc(%rbp), %esi // 参数:数字 4 放入 esi 中
0x100000e48 <+40>: callq 0x100000eb0 ; add
..........
a.out`add:
0x100000eb0 <+0>: pushq %rbp
0x100000eb1 <+1>: movq %rsp, %rbp
0x100000eb4 <+4>: movl %edi, -0x4(%rbp) // 从 edi 中取出参数 3
0x100000eb7 <+7>: movl %esi, -0x8(%rbp) // 从 esi 中取出参数 4
0x100000eba <+10>: movl -0x4(%rbp), %esi
0x100000ebd <+13>: addl -0x8(%rbp), %esi // 计算 3 + 4
0x100000ec0 <+16>: movl %esi, -0xc(%rbp)
0x100000ec3 <+19>: movl -0xc(%rbp), %eax // 3 + 4 的结果放入 eax 中,作为返回参数返回
0x100000ec6 <+22>: popq %rbp
0x100000ec7 <+23>: retq
0x100000ec8 <+24>: nopl (%rax,%rax)
栈内存传参
我们以 testParams 函数为例分析:
1
2
3
4
5
6
7
// 测试传多个参数的函数
testParams(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);
void testParams(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k)
{
return;
}
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
a.out`main:
........
0x100000e50 <+48>: movl $0x1, %edi // 数字1,2,3,4,5,6 分别放入了 6 个寄存器
0x100000e55 <+53>: movl $0x2, %esi
0x100000e5a <+58>: movl $0x3, %edx
0x100000e5f <+63>: movl $0x4, %ecx
0x100000e64 <+68>: movl $0x5, %r8d
0x100000e6a <+74>: movl $0x6, %r9d
0x100000e70 <+80>: movl $0x7, (%rsp) // 数字7,8,9,10,11 分别放入栈内存(main的栈帧空间)
0x100000e77 <+87>: movl $0x8, 0x8(%rsp)
0x100000e7f <+95>: movl $0x9, 0x10(%rsp)
0x100000e87 <+103>: movl $0xa, 0x18(%rsp)
0x100000e8f <+111>: movl $0xb, 0x20(%rsp)
0x100000e97 <+119>: callq 0x100000ed0 ; testParams
........
a.out`testParams:
0x100000ed0 <+0>: pushq %rbp
0x100000ed1 <+1>: movq %rsp, %rbp
0x100000ed4 <+4>: pushq %r14
0x100000ed6 <+6>: pushq %rbx
0x100000ed7 <+7>: movl 0x30(%rbp), %eax // 从栈内存中把参数读取出来,并放入testParams的栈帧空间
0x100000eda <+10>: movl 0x28(%rbp), %r10d
0x100000ede <+14>: movl 0x20(%rbp), %r11d
0x100000ee2 <+18>: movl 0x18(%rbp), %ebx
0x100000ee5 <+21>: movl 0x10(%rbp), %r14d
0x100000ee9 <+25>: movl %edi, -0x14(%rbp)
0x100000eec <+28>: movl %esi, -0x18(%rbp)
0x100000eef <+31>: movl %edx, -0x1c(%rbp)
0x100000ef2 <+34>: movl %ecx, -0x20(%rbp)
0x100000ef5 <+37>: movl %r8d, -0x24(%rbp)
0x100000ef9 <+41>: movl %r9d, -0x28(%rbp)
0x100000efd <+45>: movl %eax, -0x2c(%rbp)
0x100000f00 <+48>: movl %r10d, -0x30(%rbp)
0x100000f04 <+52>: movl %r11d, -0x34(%rbp)
0x100000f08 <+56>: movl %ebx, -0x38(%rbp)
0x100000f0b <+59>: movl %r14d, -0x3c(%rbp)
0x100000f0f <+63>: popq %rbx
0x100000f10 <+64>: popq %r14
0x100000f12 <+66>: popq %rbp
0x100000f13 <+67>: retq
0x100000f14 <+68>: nopw %cs:(%rax,%rax)
0x100000f1e <+78>: nop