x86-64寄存器中传递参数的顺序

问题描述 投票:3回答:2

我对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    $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,我发现很难理解传递参数的顺序。

在我看来,默认的调用约定是fastcall,因此参数从右向左传递。

我从x86-64 System V ABI手册中了解到,rdirsi用于传递前两个参数enter image description here

但是,根据反汇编代码,rdi负责var a,这是左边的参数,这意味着它应该是第二个参数。

有人能帮助我指出我的错误吗?

c assembly parameters x86-64 calling-convention
2个回答
2
投票

Args从左到右编号(归功于@R。因为发现这是你的实际混淆;我以为你在谈论asm指令的顺序,并且错过了问题的最后一段。)

看起来很正常。当call swap指令运行时,

  • rdi拥有一个指向a(堆栈中的本地)的指针,由 lea -0x4(%rbp),%raxmov %rax,%rdi。 (而不仅仅是lea进入rdi,因为你没有启用优化。)
  • rsi指向由shared建立的mov $shared,%esi
  • al持有0,因为在调用函数之前没有定义或原型化函数。 (即使没有-Wall,gcc也应该警告过你)

.o的反汇编显示$shared为0,因为它尚未链接,因此它是符号的占位符(和0偏移量)。使用objdump -drwC查看重定位符号。 (我也喜欢-Mintel,而不是AT&T语法。)

也更容易看到编译器的asm输出,你会看到$shared而不是数字和符号引用。见How to remove "noise" from GCC/clang assembly output?


写入什么顺序寄存器并不重要,只有它们在进入被调用函数时的值。

堆栈参数也是如此:如果编译器选择使用mov将args写入堆栈,它可以按任何顺序执行。

只有当你选择使用push时,你必须从右到左将第一个(最左边)的arg留在最低地址,这是所有主要的C调用约定所要求的args没有在寄存器中传递(如果有的话)。

这种从右到左的顺序可能就是为什么gcc -O0(没有优化,再加上用于调试的反优化)选择按顺序设置寄存器,即使它无关紧要。


顺便说一下,即使没有UB也能正确实现,xor-swap也毫无用处且毫无意义。 (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; }是一个更高效的交换,如果两个指针都指向同一个对象,则保留了安全xor-swap of zeroing的行为。我想你想要那个?为什么你会使用xor-swap?

如果不启用优化,编译器生成的asm将基本上很糟糕,就像main的代码因为同样的原因而糟透了。如果你这样做,swap通常可以内联并且是零指令,或者只需要在寄存器之间花费3个mov指令;有时少。 (编译器只需更改其寄存器分配并确定ab现在位于相反的寄存器中。)


3
投票

你对第一/第二的想法是错误的。计数从左侧开始。

另外你的代码有一些问题,至少:

  1. 你在没有声明/原型的情况下调用swap,所以虽然没有,但GCC选择生成代码,如果它是一个可变函数(在%rax中存储0),它将起作用。
  2. swap的定义是一堆未定义的行为。
© www.soinside.com 2019 - 2024. All rights reserved.