x86-64 在寄存器中传递参数的顺序
x86-64 order of passing parameters in registers
对x86-64环境下的参数传递过程很好奇,写了一段代码。
//a.c
extern int shared;
int main(){
int a=100;
swap(&a, &shared);
}
//b.c
int shared=1;
void swap(int* a, int* b){
*a ^= *b ^= *a ^= *b;
}
我使用以下命令编译了两个文件:
gcc -c -fno-stack-protector a.c b.c
然后我objdump -d a.o
查看a.o的反汇编代码
Disassembly of section .text:
0000000000000000 <main>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 83 ec 10 sub [=11=]x10,%rsp
8: c7 45 fc 64 00 00 00 movl [=11=]x64,-0x4(%rbp)
f: 48 8d 45 fc lea -0x4(%rbp),%rax
13: be 00 00 00 00 mov [=11=]x0,%esi
18: 48 89 c7 mov %rax,%rdi
1b: b8 00 00 00 00 mov [=11=]x0,%eax
20: e8 00 00 00 00 callq 25 <main+0x25>
25: b8 00 00 00 00 mov [=11=]x0,%eax
2a: c9 leaveq
2b: c3 retq
由于我的工作环境是Ubuntu 16.04 x86-64,我觉得传递参数的顺序比较难理解
在我看来,这里的默认调用约定是fastcall
,因此参数从右向左传递。
我从 x86-64 System V ABI 手册中知道 rdi
和 rsi
用于传递前两个参数
但是根据反汇编代码,rdi
负责var a
,也就是左边的param,意思应该是second参数
谁能帮我指出我的错误?
参数从左到右编号(归功于@R。发现这是您真正的困惑;我以为您在谈论 asm 指令的顺序,并且错过了问题的最后一段。)
我看起来很正常。 call swap
指令运行时,
rdi
持有指向 a
的指针(堆栈上的一个本地指针),由
设置
lea -0x4(%rbp),%rax
和 mov %rax,%rdi
.
(而不只是 lea
到 rdi
,因为你没有启用优化。)
rsi
持有指向 shared
的指针,由 mov $shared,%esi
设置
al
保持 0
因为您在调用函数之前没有定义函数或原型。 (即使没有 -Wall
,gcc 也应该警告你)
.o
的反汇编显示 $shared
为 0,因为它尚未链接,所以它是符号的占位符(和 0 偏移量)。使用 objdump -drwC
查看重定位符号。 (我也喜欢 -Mintel
,而不是 AT&T 语法。)
编译器的 asm 输出也更容易查看,您会在其中看到 $shared
而不是数字和符号引用。参见 。
写入寄存器的顺序无关紧要,重要的是它们在进入被调用函数时的值。
堆栈参数相同:如果编译器选择使用 mov
将参数写入堆栈,它可以按任何顺序执行。
只有当您选择使用 push
时,您才需要从右到左将第一个(最左边的)arg 留在最低地址,这是所有主流 C 调用约定所要求的未在寄存器中传递的参数(如果有)。
这种从右到左的顺序可能是 gcc -O0
(没有优化,加上用于调试的反优化)选择按该顺序设置寄存器的原因,尽管这无关紧要。
顺便说一句,异或交换即使在没有 UB 的情况下正确实施也是无用且毫无意义的。 (Are there sequence points in the expression a^=b^=a^=b, or is it undefined?).
if(a==b) { *a = *b = 0; } else { int tmp = *a; *a=*b; *b=tmp; }
是一种更有效的交换,如果两个指针都指向同一个对象,它会保留安全的归零异或交换的行为。我猜你想要那个?为什么要使用异或交换?
如果不启用优化,编译器生成的 asm 基本上都会很烂,就像 main
的代码出于同样的原因很烂一样。如果这样做,swap
通常可以内联并成为零指令,或者只在寄存器之间花费最多 3 个 mov
指令;有时更少。 (编译器可以改变它的寄存器分配并决定 a
和 b
现在在相反的寄存器中。)
你的想法 first/second 是错误的。从左边开始计数。
此外你的代码还有一些问题,至少:
您在调用 swap
时没有使用 declaration/prototype,因此虽然不必这样做,但 GCC 已选择生成如果它是可变参数函数就可以工作的代码(在 %rax
中存储 0)。
swap
的定义是一堆未定义的行为。
对x86-64环境下的参数传递过程很好奇,写了一段代码。
//a.c
extern int shared;
int main(){
int a=100;
swap(&a, &shared);
}
//b.c
int shared=1;
void swap(int* a, int* b){
*a ^= *b ^= *a ^= *b;
}
我使用以下命令编译了两个文件:
gcc -c -fno-stack-protector a.c b.c
然后我objdump -d a.o
查看a.o的反汇编代码
Disassembly of section .text:
0000000000000000 <main>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 83 ec 10 sub [=11=]x10,%rsp
8: c7 45 fc 64 00 00 00 movl [=11=]x64,-0x4(%rbp)
f: 48 8d 45 fc lea -0x4(%rbp),%rax
13: be 00 00 00 00 mov [=11=]x0,%esi
18: 48 89 c7 mov %rax,%rdi
1b: b8 00 00 00 00 mov [=11=]x0,%eax
20: e8 00 00 00 00 callq 25 <main+0x25>
25: b8 00 00 00 00 mov [=11=]x0,%eax
2a: c9 leaveq
2b: c3 retq
由于我的工作环境是Ubuntu 16.04 x86-64,我觉得传递参数的顺序比较难理解
在我看来,这里的默认调用约定是fastcall
,因此参数从右向左传递。
我从 x86-64 System V ABI 手册中知道 rdi
和 rsi
用于传递前两个参数
但是根据反汇编代码,rdi
负责var a
,也就是左边的param,意思应该是second参数
谁能帮我指出我的错误?
参数从左到右编号(归功于@R。发现这是您真正的困惑;我以为您在谈论 asm 指令的顺序,并且错过了问题的最后一段。)
我看起来很正常。 call swap
指令运行时,
rdi
持有指向a
的指针(堆栈上的一个本地指针),由
设置lea -0x4(%rbp),%rax
和mov %rax,%rdi
.(而不只是
lea
到rdi
,因为你没有启用优化。)rsi
持有指向shared
的指针,由mov $shared,%esi
设置
al
保持0
因为您在调用函数之前没有定义函数或原型。 (即使没有-Wall
,gcc 也应该警告你)
.o
的反汇编显示 $shared
为 0,因为它尚未链接,所以它是符号的占位符(和 0 偏移量)。使用 objdump -drwC
查看重定位符号。 (我也喜欢 -Mintel
,而不是 AT&T 语法。)
编译器的 asm 输出也更容易查看,您会在其中看到 $shared
而不是数字和符号引用。参见
写入寄存器的顺序无关紧要,重要的是它们在进入被调用函数时的值。
堆栈参数相同:如果编译器选择使用 mov
将参数写入堆栈,它可以按任何顺序执行。
只有当您选择使用 push
时,您才需要从右到左将第一个(最左边的)arg 留在最低地址,这是所有主流 C 调用约定所要求的未在寄存器中传递的参数(如果有)。
这种从右到左的顺序可能是 gcc -O0
(没有优化,加上用于调试的反优化)选择按该顺序设置寄存器的原因,尽管这无关紧要。
顺便说一句,异或交换即使在没有 UB 的情况下正确实施也是无用且毫无意义的。 (Are there sequence points in the expression a^=b^=a^=b, or is it undefined?).
if(a==b) { *a = *b = 0; } else { int tmp = *a; *a=*b; *b=tmp; }
是一种更有效的交换,如果两个指针都指向同一个对象,它会保留安全的归零异或交换的行为。我猜你想要那个?为什么要使用异或交换?
如果不启用优化,编译器生成的 asm 基本上都会很烂,就像 main
的代码出于同样的原因很烂一样。如果这样做,swap
通常可以内联并成为零指令,或者只在寄存器之间花费最多 3 个 mov
指令;有时更少。 (编译器可以改变它的寄存器分配并决定 a
和 b
现在在相反的寄存器中。)
你的想法 first/second 是错误的。从左边开始计数。
此外你的代码还有一些问题,至少:
您在调用
swap
时没有使用 declaration/prototype,因此虽然不必这样做,但 GCC 已选择生成如果它是可变参数函数就可以工作的代码(在%rax
中存储 0)。swap
的定义是一堆未定义的行为。