繁体   English   中英

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

[英]Is this assembly function call safe/complete?

我没有组装经验,但这是我一直在研究的。 如果我缺少传递参数和通过汇编中的指针调用函数的任何基本方面,我想要输入。

例如,我想知道是否应该恢复ecxedxesiedi 我读到它们是通用寄存器,但我找不到它们是否需要恢复? 打电话后我应该做些什么清理工作?

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

#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)
          );

}

最初的问题Is this assembly function call safe/complete? . 答案是:不。 虽然它可能会出现工作这个简单的例子(特别是如果优化是禁用的),你违反了规则,这将最终导致失败的(那些真的很难追查)。

我想解决如何使其安全的(明显的)后续问题,但如果没有来自 OP 对实际意图的反馈,我真的无法做到这一点。

因此,我将尽我所能利用我们拥有的一切,并尝试描述使其不安全的事情以及您可以做的一些事情。

让我们从简化那个 asm 开始:

 __asm__(
          "mov %0, %%edi;"
          :
          : "g"(a)
          );

即使有这个单一的语句,这段代码也已经不安全了。 为什么? 因为我们在不让编译器知道的情况下更改了寄存器 (edi) 的值。

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

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

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

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

  __asm__(
          "mov %0, %%edi;"
          :
          : "g"(a)
          : edi
          );

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

现在,虽然这是最简单的方法,但不一定是最好的方法。 考虑这个代码:

  __asm__(
          ""
          :
          : "D"(a)
          );

这使用机器约束告诉 gcc 将变量a的值放入 edi 寄存器中。 这样做,gcc 将在“方便”的时间为您加载寄存器,也许是通过始终在 edi 中保留a

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

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

int junk;
  __asm__ volatile (
          ""
          : "=D" (junk)
          : "0"(a)
          );

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

编辑:由于实际上不会使用“垃圾”变量,因此我们需要添加volatile限定符。 当没有任何输出参数时,Volatile 是隐式的。

该行的另一点是:以分号结尾。 这是合法的,将按预期工作。 但是,如果您想使用-S命令行选项准确查看生成的代码(并且如果您想使用内联 asm,您会这样做),您会发现这会生成难以阅读的代码。 我建议使用\\n\\t而不是分号。

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

显然,这同样适用于其他两个mov语句。

这将我们带到了call语句。

Michael 和我都列出了在内联汇编中调用很困难的许多原因。

  • 处理可能被函数调用的 ABI 破坏的所有寄存器。
  • 处理红区。
  • 处理对齐。
  • 内存破坏者。

如果这里的目标是“学习”,那么请随意尝试。 但我不知道在生产代码中这样做会不会让我感到自在。 即使它看起来有效,我也永远不会确信我没有遗漏一些奇怪的案例。 除了我对使用内联 asm 的正常担忧之外。

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

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

我读到它们是通用寄存器,但我找不到它们是否需要恢复?

我不是该领域的专家,但从我对x86-64 ABI (图 3.4)的阅读来看,以下寄存器: %rdi%rsi%rdx%rcx不在函数调用之间保留,因此显然没有t 需要恢复。

正如 David Wohlferd 评论的那样,您应该小心,因为无论哪种方式,编译器都不会意识到“自定义”函数调用,因此您可能会陷入困境,特别是因为它可能不知道寄存器修改。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM