gcc 内联汇编行为异常
Posted
技术标签:
【中文标题】gcc 内联汇编行为异常【英文标题】:gcc inline assembly behave strangely 【发布时间】:2018-03-29 11:49:43 【问题描述】:我目前正在学习 GCC 的扩展内联汇编。我写了一个 A + B 函数,想检测 ZF 标志,但事情表现得很奇怪。
我使用的编译器是 x86-64 Arch Linux 上的 gcc 7.3.1。
我从下面的代码开始,这段代码会正确打印a + b
。
int a, b, sum;
scanf("%d%d", &a, &b);
asm volatile (
"movl %1, %0\n"
"addl %2, %0\n"
: "=r"(sum)
: "r"(a), "r"(b)
: "cc"
);
printf("%d\n", sum);
然后我只是添加了一个变量来检查标志,它给了我错误的输出。
int a, b, sum, zero;
scanf("%d%d", &a, &b);
asm volatile (
"movl %2, %0\n"
"addl %3, %0\n"
: "=r"(sum), "=@ccz"(zero)
: "r"(a), "r"(b)
: "cc"
);
printf("%d %d\n", sum, zero);
GAS 组件输出为
movl -24(%rbp), %eax # %eax = a
movl -20(%rbp), %edx # %edx = b
#APP
# 6 "main.c" 1
movl %eax, %edx
addl %edx, %edx
# 0 "" 2
#NO_APP
sete %al
movzbl %al, %eax
movl %edx, -16(%rbp) # sum = %edx
movl %eax, -12(%rbp) # zero = %eax
这一次,sum
将变为 a + a
。但是当我刚刚交换%2
和%3
时,输出将是正确的a + b
。
然后我在 wandbox.org 上检查了各种 gcc 版本(当输出是标志时,clang 似乎不支持它),从版本 4.5.4 到版本 4.7.4 给出了正确的结果a + b
,并从版本开始4.8.1 输出全部为a + a
。
我的问题是:我写错了代码还是 gcc 有什么问题?
【问题讨论】:
问题是你在所有输入被消耗之前破坏了 %0。允许优化器将相同的寄存器用于输入约束作为输出约束。为避免这种情况,您需要将输入约束 %0 设置为 early clobber。为此,请将"=r"(sum)
更改为 "=&r"(sum)
@MichaelPetch 感谢您的回复。这是否意味着我必须在使用 %0 之前使用其他寄存器来保存结果,或者只是限制使用哪个特定寄存器来保存结果,如果我不使用 early clobber?
如果您不想使用早期的 clobber 修饰符,那么您必须为输入约束指定一个特定的寄存器,为输出约束指定一个特定的寄存器(所有不同的寄存器)。您将无法使用"=r"
和"r"
来允许编译器自动选择空闲寄存器,从而生成效率较低的代码。
@MichaelPetch 再次非常感谢您。我的问题解决了。但是你为什么不把它作为答案而不是评论发布呢?
基本上是When to use earlyclobber constraint in extended GCC inline assembly?的副本
【参考方案1】:
问题是你在所有输入(在你的情况下为%2
)被消耗之前破坏了%0
:
"movl %1, %0\n"
"addl %2, %0\n"
%0
在 %2
被消耗之前被第一个 MOV 修改。优化编译器可以将用于输出约束的输入约束重新使用寄存器。在您的情况下,其中一个编译器选择对 %2
和 %0
使用相同的寄存器,这会导致错误结果。
要解决在消耗所有输入之前更改正在修改的寄存器的问题,请使用&
标记输出约束。 &
是一个修饰符,表示Early Clobber:
‘&’ 意味着(在特定的替代方案中)此操作数是一个 earlyclobber 操作数,它是在使用输入操作数完成指令之前写入的。因此,此操作数可能不在指令读取的寄存器中或作为任何内存地址的一部分。
‘&’ 仅适用于编写它的替代方案。在具有多种选择的约束中,有时一种选择需要“&”,而另一些则不需要。例如,参见 68000 的“movdf”insn。
如果指令读取的操作数在写入早期结果之前仅用作输入,则可以将其绑定到 earlyclobber 操作数。当只有一些读操作数会受到 earlyclobber 的影响时,添加这种形式的替代项通常可以让 GCC 生成更好的代码。例如,参见 ARM 的“mulsi3”insn。
此外,如果 earlyclobber 操作数也是一个读/写操作数,那么该操作数只有在使用后才被写入。
‘&’ 并不排除写‘=’或‘+’的必要性。由于 earlyclobber 操作数总是被写入,只读的 earlyclobber 操作数格式错误,将被编译器拒绝。
对代码的更改是将"=r"(sum)
修改为"=&r"(sum)
。这将阻止编译器将用于输出约束的寄存器用于输入约束之一。
警告词。 GCC 内联汇编是强大而邪恶的。如果你不知道自己在做什么,很容易出错。仅在必要时使用,尽可能避免使用。
【讨论】:
以上是关于gcc 内联汇编行为异常的主要内容,如果未能解决你的问题,请参考以下文章