背景
突然好奇x86架构下函数参数怎么传递的,之前只是听别人说过通过寄存器,但是怎么传,什么顺序都没有仔细研究过,也没有实际测试过,因此就想着用实践来检验一下咯。
传参顺序
在32位和64位机器上,寄存器名称不同,64位机器为rxx,32位机器为exx。传参顺序如下,
64位系统有16个寄存器,32位系统只有8个。e开头的寄存器命名依然可以直接运用于相应寄存器的低32位。而剩下的寄存器名则是从r8 - r15,其低位分别用d,w,b指定长度。
测试程序
我们通过简单的程序编译后,使用gdb反汇编看下参数传入是否和上面描述的相符合。
先看看32位程序是怎么传递参数的,
#include <stdio.h>
static int test32(int arg1, int arg2, int arg3, int arg4,int arg5,
int arg6, int arg7, int arg8)
{
return 0;
}
int main(void)
{
test32(1, 2, 3, 4, 5, 6, 7, 8);
return 0;
}
将函数入参设置成8个,使用gdb反汇编观察参数具体传递情况。
[root@CentOS-7-2 /home/register]# gcc -g -m32 test.c -o test
[root@CentOS-7-2 /home/register]# gdb --quiet
(gdb) file test
Reading symbols from /home/register/test...done.
(gdb) disassemble /m main
Dump of assembler code for function main:
10 {
0x080483fa <+0>: push %ebp
0x080483fb <+1>: mov %esp,%ebp
0x080483fd <+3>: sub $0x20,%esp
11 test32(1, 2, 3, 4, 5, 6, 7, 8);
0x08048400 <+6>: movl $0x8,0x1c(%esp)
0x08048408 <+14>: movl $0x7,0x18(%esp)
0x08048410 <+22>: movl $0x6,0x14(%esp)
0x08048418 <+30>: movl $0x5,0x10(%esp)
0x08048420 <+38>: movl $0x4,0xc(%esp)
0x08048428 <+46>: movl $0x3,0x8(%esp)
0x08048430 <+54>: movl $0x2,0x4(%esp)
0x08048438 <+62>: movl $0x1,(%esp)
0x0804843f <+69>: call 0x80483f0 <test32>
12
13 return 0;
0x08048444 <+74>: mov $0x0,%eax
14 }
0x08048449 <+79>: leave
0x0804844a <+80>: ret
End of assembler dump.
由上可见,32位系统中并没有通过寄存器传参,而是直接将参数入栈,而且是按照参数顺序从右向左依次入栈。
现在再来看下64为系统,
#include <stdio.h>
static int test64(long int arg1, long int arg2, long int arg3, long int arg4,
long int arg5, long int arg6, long int arg7, long int arg8)
{
return 0;
}
int main(void)
{
test64(11111111111L, 22222222222L, 33333333333L, 44444444444L,
55555555555L, 66666666666L, 77777777777L, 88888888888L);
return 0;
}
这里入参使用long int类型,是因为如果使用int型,编译器会默认使用32位寄存器,即exx寄存器,不直观。
[root@CentOS-7-2 /home/register]# gcc -g -m64 test.c -o test
[root@CentOS-7-2 /home/register]# gdb --quiet
(gdb) file test
Reading symbols from /home/register/test...done.
(gdb) disassemble /m main
Dump of assembler code for function main:
10 {
0x0000000000400513 <+0>: push %rbp
0x0000000000400514 <+1>: mov %rsp,%rbp
0x0000000000400517 <+4>: sub $0x10,%rsp
11 test64(11111111111L, 22222222222L, 33333333333L, 44444444444L,
0x000000000040051b <+8>: movabs $0x14b230ce38,%rax
0x0000000000400525 <+18>: mov %rax,0x8(%rsp)
0x000000000040052a <+23>: movabs $0x121beab471,%rax
0x0000000000400534 <+33>: mov %rax,(%rsp)
0x0000000000400538 <+37>: movabs $0xf85a49aaa,%r9
0x0000000000400542 <+47>: movabs $0xcef5e80e3,%r8
0x000000000040054c <+57>: movabs $0xa5918671c,%rcx
0x0000000000400556 <+67>: movabs $0x7c2d24d55,%rdx
0x0000000000400560 <+77>: movabs $0x52c8c338e,%rsi
0x000000000040056a <+87>: movabs $0x2964619c7,%rdi
0x0000000000400574 <+97>: callq 0x4004f0 <test64>
12 55555555555L, 66666666666L, 77777777777L, 88888888888L);
13
14 return 0;
0x0000000000400579 <+102>: mov $0x0,%eax
15 }
0x000000000040057e <+107>: leaveq
0x000000000040057f <+108>: retq
End of assembler dump.
为了更直观,我们把64位的入参转换为16进制,
十进制 16进制
11111111111 0x2964619c7
22222222222 0x52c8c338e
33333333333 0x7c2d24d55
从这我们就能看出,参数传递确实是按照rdi、rsi、rdx、rcx、r8、r9的顺序存放第一个参数到第六个参数。对于超出6个参数的入参还是和32位机器一样放入栈中。
相比于参数入栈,通过寄存器传递参数肯定更为高效。