[英]Is this assembly function call safe/complete?
我没有组装经验,但这是我一直在研究的。 如果我缺少传递参数和通过汇编中的指针调用函数的任何基本方面,我想要输入。
例如,我想知道是否应该恢复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)
);
}
最初的问题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 和我都列出了在内联汇编中调用很困难的许多原因。
如果这里的目标是“学习”,那么请随意尝试。 但我不知道在生产代码中这样做会不会让我感到自在。 即使它看起来有效,我也永远不会确信我没有遗漏一些奇怪的案例。 除了我对使用内联 asm 的正常担忧之外。
我知道,这是很多信息。 作为对 gcc 的asm
命令的介绍,可能比您想要的要多,但是您选择了一个具有挑战性的起点。
如果您还没有这样做,请花时间查看 gcc 的汇编语言界面中的所有文档。 那里有很多很好的信息和示例,试图解释它是如何工作的。
我读到它们是通用寄存器,但我找不到它们是否需要恢复?
我不是该领域的专家,但从我对x86-64 ABI (图 3.4)的阅读来看,以下寄存器: %rdi
、 %rsi
、 %rdx
和%rcx
不在函数调用之间保留,因此显然没有t 需要恢复。
正如 David Wohlferd 评论的那样,您应该小心,因为无论哪种方式,编译器都不会意识到“自定义”函数调用,因此您可能会陷入困境,特别是因为它可能不知道寄存器修改。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.