1.调用函数的过程
C语言中调用一个没有参数的空函数的过程为:保存%rbp的值;nop;恢复%rbp的值。
#include
void nop();
int main()
{
nop();
}
void nop()
{
}
以上空代码,汇编的结果为:
0000000000000000 :
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: b8 00 00 00 00 mov $0x0,%eax
9: e8 00 00 00 00 callq e
e: b8 00 00 00 00 mov $0x0,%eax
13: 5d pop %rbp
14: c3 retq
0000000000000015 :
15: 55 push %rbp
16: 48 89 e5 mov %rsp,%rbp
19: 90 nop
1a: 5d pop %rbp
1b: c3 retq
可以看到,调用nop函数时先保存了%rbp的值,将栈指针的值赋值给%rbp,nop,取出保存的值,退出;
当nop函数中有参数时:
#include
void nop(int a);
int main()
{
nop(1);
}
void nop(int a)
{
a++;
}
汇编的结果为:
0000000000000000 :
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: bf 01 00 00 00 mov $0x1,%edi
9: b8 00 00 00 00 mov $0x0,%eax
e: e8 00 00 00 00 callq 13
13: b8 00 00 00 00 mov $0x0,%eax
18: 5d pop %rbp
19: c3 retq
000000000000001a :
1a: 55 push %rbp
1b: 48 89 e5 mov %rsp,%rbp
1e: 89 7d fc mov %edi,-0x4(%rbp)
21: 83 45 fc 01 addl $0x1,-0x4(%rbp)
25: 90 nop
26: 5d pop %rbp
27: c3 retq
可以看到,参数通过参数寄存器%edi传递到nop函数中,并将参数保存到了栈指针 -4的空间,调用时也从这里调用,从这里我们可以看到增加参数的时候,参数保存的地址是减小的。
x86共有6个参数寄存器,也就是说,前6个参数通过寄存器传递,之后的参数直接push到栈里:
#include
void nop(int a, int b, int c, int d, int e, int f, int g, int h);
int main()
{
nop(1, 2, 3, 4, 5, 6, 7, 8);
}
void nop(int a, int b, int c, int d, int e, int f, int g, int h)
{
a++;
g++;
h++;
}
汇编结果:
0000000000000000 :
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 6a 08 pushq $0x8
6: 6a 07 pushq $0x7
8: 41 b9 06 00 00 00 mov $0x6,%r9d
e: 41 b8 05 00 00 00 mov $0x5,%r8d
14: b9 04 00 00 00 mov $0x4,%ecx
19: ba 03 00 00 00 mov $0x3,%edx
1e: be 02 00 00 00 mov $0x2,%esi
23: bf 01 00 00 00 mov $0x1,%edi
28: e8 00 00 00 00 callq 2d
2d: 48 83 c4 10 add $0x10,%rsp
31: b8 00 00 00 00 mov $0x0,%eax
36: c9 leaveq
37: c3 retq
0000000000000038 :
38: 55 push %rbp
39: 48 89 e5 mov %rsp,%rbp
3c: 89 7d fc mov %edi,-0x4(%rbp)
3f: 89 75 f8 mov %esi,-0x8(%rbp)
42: 89 55 f4 mov %edx,-0xc(%rbp)
45: 89 4d f0 mov %ecx,-0x10(%rbp)
48: 44 89 45 ec mov %r8d,-0x14(%rbp)
4c: 44 89 4d e8 mov %r9d,-0x18(%rbp)
50: 83 45 fc 01 addl $0x1,-0x4(%rbp)
54: 83 45 10 01 addl $0x1,0x10(%rbp)
58: 83 45 18 01 addl $0x1,0x18(%rbp)
5c: 90 nop
5d: 5d pop %rbp
5e: c3 retq
可以看到前6个参数通过寄存器传递,7,8俩个参数直接push到栈里,通过调用俩个参数可以发现,先压入栈中的参数7、8在高地址,且参数8的地址比参数7更高。参数的栈排布为:
栈地址 | 参数 |
18 | 8(h) |
10 | 7 (g) |
-4 | 1 (a) |
-8 | 2 (b) |
-c | 3 (c) |
-10 | 4 (d) |
-14 | 5 (e) |
-18 | 6 (f) |
局部变量也保存在栈中:
#include
void nop(int a, int b, int c, int d, int e, int f, int g, int h);
int main()
{
nop(1, 2, 3, 4, 5, 6, 7, 8);
}
void nop(int a, int b, int c, int d, int e, int f, int g, int h)
{
int var0 = 0;
int var1 = 1;
int var2 = 2;
int var3 = 3;
int var4 = 4;
}
汇编结果:
0000000000000000 :
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 6a 08 pushq $0x8
6: 6a 07 pushq $0x7
8: 41 b9 06 00 00 00 mov $0x6,%r9d
e: 41 b8 05 00 00 00 mov $0x5,%r8d
14: b9 04 00 00 00 mov $0x4,%ecx
19: ba 03 00 00 00 mov $0x3,%edx
1e: be 02 00 00 00 mov $0x2,%esi
23: bf 01 00 00 00 mov $0x1,%edi
28: e8 00 00 00 00 callq 2d
2d: 48 83 c4 10 add $0x10,%rsp
31: b8 00 00 00 00 mov $0x0,%eax
36: c9 leaveq
37: c3 retq
0000000000000038 :
38: 55 push %rbp
39: 48 89 e5 mov %rsp,%rbp
3c: 89 7d dc mov %edi,-0x24(%rbp)
3f: 89 75 d8 mov %esi,-0x28(%rbp)
42: 89 55 d4 mov %edx,-0x2c(%rbp)
45: 89 4d d0 mov %ecx,-0x30(%rbp)
48: 44 89 45 cc mov %r8d,-0x34(%rbp)
4c: 44 89 4d c8 mov %r9d,-0x38(%rbp)
50: c7 45 ec 00 00 00 00 movl $0x0,-0x14(%rbp)
57: c7 45 f0 01 00 00 00 movl $0x1,-0x10(%rbp)
5e: c7 45 f4 02 00 00 00 movl $0x2,-0xc(%rbp)
65: c7 45 f8 03 00 00 00 movl $0x3,-0x8(%rbp)
6c: c7 45 fc 04 00 00 00 movl $0x4,-0x4(%rbp)
73: 90 nop
74: 5d pop %rbp
75: c3 retq
可以看出,局部变量越多,参数越多,最终会导致栈的地址越来越小,即栈是向低地值增长的。进入函数后调用栈的情况为:
高地址 | push到栈中的参数 |
…… | 局部变量 |
低地值 | 通过寄存器传递的参数 |
2. 运行时栈
经过以上的内容已经了解到了函数调用时发生了什么,结合进程整个程序的栈帧就可以得出运行时的栈帧以及堆在程序中的变化:
#include
#include
void nop();
int main()
{
int arg = 0;
char *pointer = (char *)malloc(1);
printf("arg addr in main %p\n", &arg);
printf("pointer in main %p\n", pointer);
nop();
free(pointer);
}
void nop()
{
int arg = 0;
char *pointer = (char *)malloc(1);
printf("arg addr in nop %p\n", &arg);
printf("pointer in nop %p\n", pointer);
free(pointer);
}
运行结果:
arg addr in main 0x7ffe491e420c
pointer in main 0x55b3d8bfa010
arg addr in nop 0x7ffe491e41dc
pointer in nop 0x55b3d8bfa440
可以看到:
低地值 | main pointer |
…… | nop pointer |
…… | 未使用的空间 |
…… | nop arg |
高地址 | main arg |
由此,栈的由高地址向低地值减少,堆由低地址向高地址增加,且如果先在nop函数中malloc并且不释放然后在main中malloc,nop会获得一个更小的地址,但是栈的地址不会根据执行顺序改变,因为栈的空间在编译的时候已经分配好了。