x86-64 在寄存器中传递参数的顺序 [英] x86-64 order of passing parameters in registers
问题描述
我对 x86-64 环境中的参数传递过程很好奇,因此我写了一段代码.
I'm curious about the parameter passing procedure in the x86-64 environment and therefore I wrote a snippet of code.
//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的反汇编代码.
I compile two files using the following commands:
gcc -c -fno-stack-protector a.c b.c
Then I objdump -d a.o
to check a.o's disassembly code.
Disassembly of section .text:
0000000000000000 <main>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 83 ec 10 sub $0x10,%rsp
8: c7 45 fc 64 00 00 00 movl $0x64,-0x4(%rbp)
f: 48 8d 45 fc lea -0x4(%rbp),%rax
13: be 00 00 00 00 mov $0x0,%esi
18: 48 89 c7 mov %rax,%rdi
1b: b8 00 00 00 00 mov $0x0,%eax
20: e8 00 00 00 00 callq 25 <main+0x25>
25: b8 00 00 00 00 mov $0x0,%eax
2a: c9 leaveq
2b: c3 retq
由于我的工作环境是Ubuntu 16.04 x86-64,我发现很难理解传递参数的顺序.
Due to my working environment is Ubuntu 16.04 x86-64, I found it hard to understand the order of passing parameter.
在我看来,这里的默认调用约定是 fastcall
,因此参数从从右到左传递.
In my point of view, the default call convention is fastcall
here and therefore the parameters are passed from right to left.
我从 x86-64 System V ABI 手册中得知 rdi
和 rsi
用于传递前两个参数
I know from the x86-64 System V ABI manual that rdi
and rsi
are used for passing the first two parameters
但是,根据反汇编代码,rdi
负责var a
,也就是左边的param,意思应该是second 参数.
However, according to the disassembly code, rdi
is responsible for var a
, which is the param on the left, meaning it should be the second param.
有人可以帮我指出我的错误吗?
Could someone help me point out my mistake?
推荐答案
Args 从左到右编号(感谢 @R. 发现这是您的实际困惑;我以为您是说到asm指令的顺序,漏掉了问题的最后一段.)
Args are numbered from left to right (credit to @R. for spotting that this was your actual confusion; I thought you were talking about the order of asm instructions, and missed the last paragraph of the question.)
在我看来很正常.当call swap
指令运行时,
Looks normal to me. When the call swap
instruction runs,
rdi
持有一个指向a
(堆栈上的局部变量)的指针,由
设置lea -0x4(%rbp),%rax
和mov %rax,%rdi
.
rdi
holds a pointer toa
(a local on the stack), set up by
lea -0x4(%rbp),%rax
andmov %rax,%rdi
.
(而不是将 lea
放入 rdi
,因为您没有启用优化.)
(instead of just lea
into rdi
, because you didn't enable optimization.)
.o
的反汇编显示 $shared
为 0,因为它尚未链接,因此它是符号的占位符(和 0 偏移量).使用 objdump -drwC
查看重定位符号.(我也喜欢 -Mintel
,而不是 AT&T 语法.)
The disassembly of the .o
shows $shared
as 0 because it's not linked yet, so it's a placeholder (and 0 offset) from the symbol. Use objdump -drwC
to see relocation symbols. (I like -Mintel
as well, instead of AT&T syntax.)
同样更容易查看的是编译器的 asm 输出,您会在其中看到 $shared
而不是数字和符号引用.请参阅如何去除噪音"来自 GCC/clang 程序集输出?.
Also easier to look at would be the compiler's asm output, where you would see $shared
instead of a number and symbol reference. See How to remove "noise" from GCC/clang assembly output?.
寄存器的写入顺序无关紧要,重要的是它们在进入被调用函数时的值.
对于堆栈参数也是如此:如果编译器选择使用 mov
将参数写入堆栈,它可以按任何顺序进行.
Same for stack args: if a compiler chooses to use mov
to write args to the stack, it can do it in any order.
只有当你选择使用 push
时,你才必须从右到左将第一个(最左边的)arg 留在最低地址,这是所有主流 C 调用约定所要求的对于未在寄存器中传递的参数(如果有).
Only if you choose to use push
do you have to go from right to left to leave the first (left-most) arg at the lowest address, as required by all the mainstream C calling conventions for args that aren't passed in registers (if any).
这种从右到左的顺序可能是 gcc -O0
(没有优化,加上调试的反优化)选择按该顺序设置寄存器的原因,即使这无关紧要.
This right-to-left order might be why gcc -O0
(no optimization, plus anti-optimization for debugging) chooses to set registers in that order, even though it doesn't matter.
顺便说一句,即使在没有 UB 的情况下正确实施,xor-swap 也是无用且毫无意义的.(表达式中是否有序列点a^=b^=a^=b,还是未定义?).
And BTW, xor-swap is useless and pointless even if implemented correctly without 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;}
是一种更有效的交换,如果两个指针都指向同一个对象,它会保留归零的安全异或交换的行为.我猜你想要那个?你为什么要使用异或交换?
if(a==b) { *a = *b = 0; } else { int tmp = *a; *a=*b; *b=tmp; }
is a more efficient swap that preserves the behaviour of a safe xor-swap of zeroing if both pointers are to the same object. I assume you want that? Why else would you use an xor-swap?
如果你不启用优化,编译器生成的 asm 基本上会很糟糕,就像 main
的代码出于同样的原因很糟糕.如果你这样做了,swap
通常可以内联并且是零指令,或者在寄存器之间花费多达 3 个 mov
指令;有时更少.(编译器可以改变它的寄存器分配并决定 a
和 b
现在在相反的寄存器中.)
The compiler-generated asm for either will basically suck if you don't enable optimization, just like the code for main
sucks for the same reason. And if you do, swap
can often inline and be zero instructions, or just cost up to 3 mov
instructions between registers; sometimes less. (The compiler can just change its register allocation and decide that a
and b
are in opposite registers now.)
这篇关于x86-64 在寄存器中传递参数的顺序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!