使用64位arch中的寄存器访问参数

时间:2014-01-24 13:14:18

标签: c assembly operating-system

我参加了华盛顿大学的硬件/软件接口课程。 在那个课程中,讲师解释了x86-32 vs x86-64。 他展示了一个简单的反汇编函数,它进行了交换。

void swap(int *xp, int *yp)
{
    int t0 = *xp;
    int t1 = *yp;
    *xp = t1;
    *yp = t0;
}

在x86-32中,我可以看出参数是通过堆栈传递的,但是在x86-64中,参数通过寄存器传递。

我可以告诉你,在x86-32中,参数是在cdecl调用约定中传递的,而不是x86-64,这是一个快速调用约定。

为什么会这样?它总是这样吗?如果我选择传递超过寄存器参数的数量会发生什么?

2 个答案:

答案 0 :(得分:3)

x86处理器比x64旧很多。 x86的调用约定环境随着不同的操作系统和不同的编译器选择不同的方法而有机地增长。目前,只有这些调用约定仍被广泛使用:

  • CDECL
  • STDCALL
  • Borland fastcall
  • MS fastcall

但是多年来已经使用了更多的呼叫惯例。可以想象,尝试任何类型的二进制互操作都会因许多不同的调用约定而变得复杂。

当主要操作系统采用x64时,已经吸取了教训。操作系统设计人员非常热衷于将单个调用约定作为明确指定的ABI的一部分。这有许多优点。它不仅使开发人员的生活更轻松,而且使开发编译器工具更容易,并且还使得更容易保护代码。

有趣的是,x64上仍然存在多个调用约定,但在单个平台上,只有一个x64调用约定。有类似操作系统的Unix使用的MS x64调用约定和System V x64调用约定。这两个x64调用约定都是fastcall,参数通过寄存器传递。

  

如果我选择传递超过寄存器参数的数量会怎样?

在这种情况下,对于我遇到的所有调用约定,参数都在堆栈上传递。

答案 1 :(得分:0)

使用的调用约定取决于您的编译器。编译器之间的默认设置也会有所不同。 cdecl和fastcall是两种最广泛采用的调用约定。 fastcall将寄存器中的前两个参数传递给堆栈。您通常可以将调用约定设置为用作编译器标志。您还可以将调用约定指定为函数属性

void __attribute__((cdecl)) swap(int *xp, int *yp);
void __attribute__((fastcall)) swap(int *xp, int *yp);

或者甚至指定您希望特定寄存器中的参数:

void swap(int *xp asm ("rax"), int *yp asm ("rbx"));

在任何情况下,如果不可能,编译器可以忽略任何这些指令和/或发出错误/警告。