函数调用时,在硬件层面我们需要关注的通常是cpu 的通用寄存器。每个寄存器通常都是有建议的使用方法的,而编译器也通常依照CPU架构的建议来使用这些寄存器,那么x86_64架构下函数调用时参数存放在哪些寄存器上呢?
1, x86_64 有16个通用寄存器
按照通用寄存器的定义这六个寄存器rdi,rsi,rdx,rcx,r8,r9分别放函数传入的第1到第6个参数。
2,如果函数的参数超过6个参数会怎么存放呢?
写个简单程序看看:
#include<stdio.h>
int stack_64(int a,int b,int c,int d,int e,int f,int g,int h,int k)
{
return a+b+c+d+e+f+g+h+k;
}
int main()
{
stack_64(1,2,3,4,5,6,7,8,9);
return 0;
}
编译后dump出汇编代码分析:gcc -o out test.c; objdump -d out
0000000000400563 <stack_64>:
400563: 55 push %rbp //把main函数的栈地址压栈
400564: 48 89 e5 mov %rsp,%rbp
400567: 89 7d fc mov %edi,-0x4(%rbp)
40056a: 89 75 f8 mov %esi,-0x8(%rbp)
40056d: 89 55 f4 mov %edx,-0xc(%rbp)
400570: 89 4d f0 mov %ecx,-0x10(%rbp)
400573: 44 89 45 ec mov %r8d,-0x14(%rbp)
400577: 44 89 4d e8 mov %r9d,-0x18(%rbp)
40057b: 8b 45 f8 mov -0x8(%rbp),%eax
40057e: 8b 55 fc mov -0x4(%rbp),%edx
400581: 01 c2 add %eax,%edx
400583: 8b 45 f4 mov -0xc(%rbp),%eax
400586: 01 c2 add %eax,%edx
400588: 8b 45 f0 mov -0x10(%rbp),%eax
40058b: 01 c2 add %eax,%edx
40058d: 8b 45 ec mov -0x14(%rbp),%eax
400590: 01 c2 add %eax,%edx
400592: 8b 45 e8 mov -0x18(%rbp),%eax
400595: 01 c2 add %eax,%edx
400597: 8b 45 10 mov 0x10(%rbp),%eax //这里从栈向上偏移16个字节取第7个参数值, 因为之前栈里压了下一条指令和main函数的栈地址向下增增长了16个字节所以这里要向上偏移16个字节取参数
40059a: 01 c2 add %eax,%edx
40059c: 8b 45 18 mov 0x18(%rbp),%eax
40059f: 01 c2 add %eax,%edx
4005a1: 8b 45 20 mov 0x20(%rbp),%eax
4005a4: 01 d0 add %edx,%eax
4005a6: 5d pop %rbp
4005a7: c3 retq
0000000000400532 <main>:
400532: 55 push %rbp
400533: 48 89 e5 mov %rsp,%rbp
400536: 48 83 ec 18 sub $0x18,%rsp //栈上预留3x8个字节用于存第7,8,9个参数
40053a: c7 44 24 10 09 00 00 movl $0x9,0x10(%rsp)
400541: 00
400542: c7 44 24 08 08 00 00 movl $0x8,0x8(%rsp)
400549: 00
40054a: c7 04 24 07 00 00 00 movl $0x7,(%rsp)
400551: 41 b9 06 00 00 00 mov $0x6,%r9d
400557: 41 b8 05 00 00 00 mov $0x5,%r8d
40055d: b9 04 00 00 00 mov $0x4,%ecx
400562: ba 03 00 00 00 mov $0x3,%edx
400567: be 02 00 00 00 mov $0x2,%esi
40056c: bf 01 00 00 00 mov $0x1,%edi
400571: e8 77 ff ff ff callq 4004ed <stack_64> //默认会把下一条指令压栈
- 可以看到前6个参数分别放到edi,esi,edx, ecx,r8d, r9d上, 从第7个参数开始放到rsp指向的栈内存上
- 栈内存是向下生长的, 开始sub $0x18 %rsp, 先在栈上预留24个字节的空间用来存第7~9的参数, 分别存到(%rsp),0x8(%rsp),0x10(%rsp)
- 调用call stack_64函数默认会把stack_64的下一条指令压栈, 同时stack_64函数开始会把man函数的栈地址rbp压栈push $rbp, 所以rsp栈地址会自动向下生长16(0x10)个字节长度
- 所以在stack_64函数里加的是0x10(%rbp),0x18(%rbp),0x20(%rbp)
2, 结论
x86-64架构下函数传入的参数如果小于等于6, 一次存在rdi,rsi, rdx, rcx, r8, r9; 如果超过6个参数, 从第7个开始存在栈内, 栈内会预留相应的空间存超出6个的参数。