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

时间:2018-09-07 02:39:41

标签: c assembly parameters x86-64 calling-convention

我对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

但是,根据反汇编代码,rdivar a的责任,它是左侧的参数,意味着它应该是 second 参数。

有人可以帮我指出我的错误吗?

2 个答案:

答案 0 :(得分:3)

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

此外,您的代码至少有一些问题:

  1. 您正在调用没有声明/原型的swap,因此虽然不是必须的,但GCC选择生成的代码在可变参数函数中有效(在{{中存储0 1}})。

  2. %rax的定义是未定义行为的堆。

答案 1 :(得分:2)

Args的编号从左到右(贷记为@R。因为发现这是您的实际困惑;我认为您是在谈论asm指令的顺序,因此错过了最后一段问题。)

对我来说很正常。当m2 = random.randint(0,9) m1 = random.randint(0,9) b = random.randint(0,9) funk = lambda i : m2*i**2 + m1*i + b 指令运行时,

  • call swap持有指向rdi(堆栈上的本地变量)的指针,该指针由
    设置 alea -0x4(%rbp),%rax

    (而不是mov %rax,%rdi进入lea,因为您没有启用优化。)

  • rdi持有指向rsi设置的shared的指针
  • mov $shared,%esi之所以拥有al,是因为您在调用函数之前没有定义函数或对其进行原型设计。 (即使没有0,gcc也会警告您)

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

更容易看的是编译器的asm输出,您将在其中看到-Mintel而不是数字和符号引用。参见How to remove "noise" from GCC/clang assembly output?


写入什么顺序寄存器都没有关系,仅在进入被调用函数时才赋值。

与堆栈args相同:如果编译器选择使用$shared将args写入堆栈,则它可以按任何顺序进行操作。

仅当您选择使用mov时,才需要从右到左将第一个(最左边的)arg保留在最低地址,这是所有主流args的C调用约定所要求的。没有传入寄存器(如果有)。

从右到左的顺序可能是push(无优化,加上调试的反优化)选择以该顺序设置寄存器的原因,即使这无关紧要。


而且顺便说一句,即使没有UB正确实施,xor-swap也是毫无意义的。 (Are there sequence points in the expression a^=b^=a^=b, or is it undefined?)。

gcc -O0是一种更有效的交换,如果两个指针都指向同一个对象,则该交换将保留安全的零异或交换行为。我想你想要那个吗?为什么还要使用异或交换?

如果您不启用优化功能,则任一编译器生成的asm基本上都会烂掉,就像if(a==b) { *a = *b = 0; } else { int tmp = *a; *a=*b; *b=tmp; }的代码出于同样的原因烂掉一样。而且,如果这样做,main通常可以内联并且为零指令,或者在寄存器之间最多花费3条swap指令;有时更少。 (编译器只需更改其寄存器分配,然后确定mova现在位于相反的寄存器中。)