从 Assembly 调用 C 函数——切换调用约定

Calling a C function from Assembly -- switching calling convention

我有一个 Linux x64 的程序集应用程序,我通过寄存器将参数传递给函数,因此我使用了某种特定的调用约定,在本例中为 fastcall。现在我想从程序集应用程序 中调用一个 C 函数 ,它需要 10 个参数。我是否必须为此切换到 cdecl 并通过堆栈传递参数,而不管我的应用程序中的其他任何地方我都是通过寄存器传递参数的?是否允许在一个应用程序中混合调用约定?

当然可以。调用约定在 per-function 基础上应用。这是一个完全有效的应用程序:

int __stdcall func1()
{
   return(1);
}

int __fastcall func2()
{
  return(2);
}

int __cdecl main(void)
{
  func1();
  func2();

  return(0);
}

可以,但没必要。

__attribute__((fastcall)) 只要求在寄存器中传递前两个参数 - 其他一切都会自动传递到堆栈上,就像 cdecl 一样。这样做是为了不限制通过选择特定调用约定可以赋予函数的参数数量。

在您的示例中,使用 fastcall 调用约定调用的函数有 10 个参数,前两个参数将在寄存器中传递,其余 8 个自动在堆栈中传递,就像标准调用一样惯例。

由于您已选择将 fastcall 用于所有其他功能,我看不出您为什么要针对某个特定功能更改此设置。

我假设通过 fastcall,你的意思是 SysV ABI 使用的 amd64 调用约定(即 Linux 使用的),其中传递了前几个参数rdirsirdx

ABI稍微复杂一些,下面做个简化。您可能需要阅读 the specification 了解详情。

一般来说,前几个(最左边的)整数或指针参数被放入寄存器rdirsirdxrcxr8,以及 r9。浮点参数在 xmm0xmm7 中传递。如果寄存器 space 已用完,则额外的参数从右到左通过堆栈传递。例如,要调用具有 10 个整数参数的函数:

foo(a, b, c, d, e, f, g, h, i, k);

您需要这样的代码:

mov $a,%edi
mov $b,%esi
mov $c,%edx
mov $d,%ecx
mov $e,%r8d
mov $f,%r9d
push $k
push $i
push $h
push $g
call foo
add ,%rsp

对于您的具体示例,getnameinfo:

int getnameinfo(
    const struct sockaddr *sa,
    socklen_t salen,
    char *host,
    size_t hostlen,
    char *serv,
    size_t servlen,
    int flags);

您将在 rdi 中传递 sa,在 rsi 中传递 salen,在 rdx 中传递 host,在 hostlen 中传递 hostlen rcxservr8servlenr9flags 在堆栈上。