x86-64 在寄存器中传递参数的顺序 [英] x86-64 order of passing parameters in registers

查看:151
本文介绍了x86-64 在寄存器中传递参数的顺序的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我对 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 手册中得知 rdirsi 用于传递前两个参数

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),%raxmov %rax,%rdi.

  • rdi holds a pointer to a (a local on the stack), set up by
    lea -0x4(%rbp),%rax and mov %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 指令;有时更少.(编译器可以改变它的寄存器分配并决定 ab 现在在相反的寄存器中.)

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屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆