C 此汇编函数调用是否安全/完成?
我没有组装方面的经验,但这就是我一直在做的。如果我缺少传递参数和通过汇编中的指针调用函数的任何基本方面,我希望输入 例如,我想知道我是否应该恢复C 此汇编函数调用是否安全/完成?,c,assembly,x86,inline-assembly,function-calls,C,Assembly,X86,Inline Assembly,Function Calls,我没有组装方面的经验,但这就是我一直在做的。如果我缺少传递参数和通过汇编中的指针调用函数的任何基本方面,我希望输入 例如,我想知道我是否应该恢复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\
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)
);
}
我读到它们是通用寄存器,但我找不到它们是否是通用寄存器
需要恢复吗
我不是该领域的专家,但从我对(图3.4)的阅读来看,以下寄存器:%rdi
、%rsi
、%rdx
和%rcx
在函数调用之间不被保留,因此显然不需要恢复
正如David Wohlferd所评论的,您应该小心,因为无论哪种方式,编译器都不会意识到“自定义”函数调用,因此您可能会妨碍它,特别是因为它可能不会意识到寄存器修改
我读到它们是通用寄存器,但我找不到它们是否是通用寄存器
需要恢复吗
我不是该领域的专家,但从我对(图3.4)的阅读来看,以下寄存器:%rdi
、%rsi
、%rdx
和%rcx
在函数调用之间不被保留,因此显然不需要恢复
正如David Wohlferd所评论的,您应该小心,因为无论哪种方式,编译器都不会意识到“自定义”函数调用,因此您可能会妨碍它,特别是因为它可能不会意识到寄存器修改。最初的问题是
此汇编函数调用是否安全/完整?
。答案是:不。虽然在这个简单的例子中它可能会起作用(特别是在禁用优化的情况下),但您违反了最终会导致失败的规则(那些很难跟踪的规则)
我想解决(明显的)后续问题,即如何使其安全,但如果没有OP对实际意图的反馈,我真的无法做到这一点
因此,我将尽我所能,描述使其不安全的因素,以及您可以采取的措施
让我们从简化asm开始:
__asm__(
"mov %0, %%edi;"
:
: "g"(a)
);
即使只有这一条语句,这段代码也已经不安全了。为什么?因为我们在不让编译器知道的情况下更改寄存器(edi)的值
编译器怎么可能不知道你在问什么?毕竟,它就在asm中!答案来自下面的一行:
GCC不解析汇编指令本身,也不
知道它们的意思,甚至知道它们是否是有效的汇编程序输入
在这种情况下,您如何让gcc知道发生了什么?答案在于使用约束(冒号后面的内容)来描述asm的影响
修复此代码的最简单方法可能如下所示:
__asm__(
"mov %0, %%edi;"
:
: "g"(a)
: edi
);
int junk;
__asm__ volatile (
""
: "=D" (junk)
: "0"(a)
);
这会将edi添加到数据包中。简言之,这告诉gcc edi的值将由代码更改,并且gcc不应假设asm退出时edi中会有任何特定的值
现在,虽然这是最简单的方法,但不一定是最好的方法。考虑这个代码:
__asm__(
""
:
: "D"(a)
);
这将使用a来告诉gcc将变量a
的值放入edi寄存器中。通过这种方式,gcc将在“方便”的时候为您加载寄存器,可能是始终在edi中保留a
这段代码有一个(重要的)警告:通过将参数放在第二个冒号之后,我们将其声明为输入。输入参数必须是只读的(即退出asm时必须具有相同的值)
在您的情况下,call
语句意味着我们无法保证edi不会被更改,因此这不起作用。有几种方法可以解决这个问题。最简单的方法是将约束上移到第一个冒号之后,使其成为输出,并指定“+D”
以指示该值为读写。但是在asm之后,a
的内容将几乎没有定义(printf可以将其设置为任何内容)。如果销毁a
是不可接受的,那么总会有这样的情况:
__asm__(
"mov %0, %%edi;"
:
: "g"(a)
: edi
);
int junk;
__asm__ volatile (
""
: "=D" (junk)
: "0"(a)
);
这告诉gcc,在启动asm时,它应该将变量a
的值放在与输出约束#0(即edi)相同的位置。它还表示,在输出时,edi将不再是a
,它将包含变量junk
Edit:由于实际上不使用“junk”变量,我们需要添加volatile
限定符。当没有任何输出参数时,Volatile是隐式的
这条线的另一点是:以分号结束。这是合法的,将按预期工作。但是,如果您想使用-S
命令行选项查看生成了哪些代码(如果您想更好地使用内联asm,您会发现这会产生难以阅读的代码)。我建议使用\n\t
而不是分号
所有这些,我们仍然在第一线
显然,这同样适用于另外两个mov
语句
这就引出了call
语句
Michael和我都列举了一些原因,说明在内联asm中调用很困难
- 处理所有可能被函数调用的ABI阻塞的寄存器
- 处理红色区域
- 处理对齐
- 记忆冲击器