这个汇编函数调用安全/完整吗?

3 c x86 assembly function-calls inline-assembly

我没有装配经验,但这是我一直在努力的.如果我缺少传递参数和通过程序集中的指针调用函数的任何基本方面,我想输入.

举例来说,如果我应该恢复我想知道ecx,edx,esi,edi.我读到它们是通用寄存器,但我找不到它们是否需要恢复?打电话后我应该做什么样的清理工作?

这是我现在的代码,它确实有效:

#include "stdio.h"

void foo(int a, int b, int c, int d)
{
  printf("values = %d and %d and %d and %d\r\n", a, b, c, d);
}

int main()
{

  int a=3,b=6,c=9,d=12;
  __asm__(
          "mov %3, %%ecx;"
          "mov %2, %%edx;"
          "mov %1, %%esi;"
          "mov %0, %%edi;"
          "call %4;"
          :
          : "g"(a), "g"(b), "g"(c), "g"(d), "a"(foo)
          );

}
Run Code Online (Sandbox Code Playgroud)

Dav*_*erd 5

最初的问题是Is this assembly function call safe/complete?.答案是:不.虽然它似乎可以在这个简单的示例中工作(特别是如果禁用了优化),但是您违反了最终会导致失败的规则(非常难以追踪的规则).

我想谈谈如何使其安全的(明显的)后续问题,但如果没有OP对实际意图的反馈,我不能真的这样做.

所以,我会尽我所能,并尝试描述使其不安全的事情以及你可以采取的一些措施.

让我们从简化asm开始:

 __asm__(
          "mov %0, %%edi;"
          :
          : "g"(a)
          );
Run Code Online (Sandbox Code Playgroud)

即使使用这个单一语句,这段代码也已经不安全了.为什么?因为我们正在改变寄存器(edi)的值而不让编译器知道.

怎么编译器不知道你问?毕竟,它就在asm中!答案来自gcc文档中的这一行:

GCC不解析汇编程序指令本身,也不知道它们的含义,甚至不知道它们是否是有效的汇编程序输入.

在那种情况下,你如何让gcc知道发生了什么?答案在于使用约束(冒号后的东西)来描述asm的影响.

也许修复此代码的最简单方法是这样的:

  __asm__(
          "mov %0, %%edi;"
          :
          : "g"(a)
          : edi
          );
Run Code Online (Sandbox Code Playgroud)

这会将edi添加到clobber列表中.简而言之,这告诉gcc edi的值将由代码更改,并且当asm退出时,gcc不应假设任何特定值将在其中.

现在,虽然这是最简单的,但它不一定是最好的方式.考虑以下代码:

  __asm__(
          ""
          :
          : "D"(a)
          );
Run Code Online (Sandbox Code Playgroud)

这使用机器约束来告诉gcc将变量的值a放入edi寄存器中.这样做,gcc会在"方便"的时候为你加载寄存器,也许总是保留a在edi中.

这段代码有一个(重要的)警告:通过将参数放在第二个冒号之后,我们将它声明为输入.输入参数必须是只读的(即它们在退出asm时必须具有相同的值).

在您的情况下,该call声明意味着我们将无法保证不会更改edi,因此这不起作用.有几种方法可以解决这个问题.最简单的方法是在第一个冒号之后向上移动约束,使其成为输出,并指定"+D"指示值是读取+写入.但是a在asm(printf可以将其设置为任何内容)之后,内容将会非常未定义.如果毁灭a是不可接受的,总会有这样的事情:

int junk;
  __asm__ volatile (
          ""
          : "=D" (junk)
          : "0"(a)
          );
Run Code Online (Sandbox Code Playgroud)

这告诉gcc在启动asm时,它应该将变量的值a放在与输出约束#0(即edi)相同的位置.它还说在输出时,edi将不再存在a,它将包含变量junk.

编辑:由于实际上不会使用'junk'变量,我们需要添加volatile限定符.当没有任何输出参数时,Volatile是隐含的.

该行另外一点:你用一个分号结束它.这是合法的,并将按预期工作.但是,如果您想使用-S命令行选项来确切地查看生成的代码(如果您希望通过内联asm获得好处,那么您将会发现)会产生难以阅读的代码.我建议使用\n\t而不是分号.

所有这一切,我们仍然在第一线......

显然,同样适用于其他两个mov陈述.

这就是我们的call发言.

迈克尔和我都列出了很多原因,在内联电话中进行调用很困难.

  • 处理可能被函数调用的ABI破坏的所有寄存器.
  • 处理红区.
  • 处理对齐.
  • 记忆破坏.

如果这里的目标是"学习",那么随意进行实验.但我不知道在生产代码中这样做我会感到很自在.即使看起来它有效,我也不会有信心没有一些我错过的奇怪案例.这与我对使用内联asm的正常担忧不同.

我知道,这是很多信息.可能比你想要的更多是作为gcc asm命令的介绍,但你已经选择了一个具有挑战性的起点.

如果您还没有这样做,请花时间查看gcc 汇编语言界面中的所有文档.那里有很多好的信息以及试图解释它是如何工作的例子.