gcc 内联汇编行为异常

gcc inline assembly behave strangely

我目前正在学习 GCC 的扩展内联汇编。我写了一个 A + B 函数,想检测 ZF 标志,但事情表现得很奇怪。

我使用的编译器是 x86-64 Arch 上的 gcc 7.3.1 Linux。

我从下面的代码开始,这段代码会正确打印 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版本(输出是flag时clang似乎不支持),从4.5.4版到4.7.4版给出了正确的结果a + b,从4.8.1版本开始输出都是a + a.

我的问题是:是我代码写错了还是gcc有问题?

问题是您在所有输入(在您的情况下为 %2)被消耗之前破坏了 %0

"movl %1, %0\n"
"addl %2, %0\n"

%0%2 被消耗之前被第一个 MOV 修改。优化编译器可以 re-use 一个用于输出约束的输入约束寄存器。在您的情况下,其中一个编译器选择对 %2%0 使用相同的寄存器,这导致了错误的结果。

要解决在消耗所有输入之前更改正在修改的寄存器的问题,请使用 & 标记输出约束。 & 是修饰符,表示 Early Clobber:

‘&’ Means (in a particular alternative) that this operand is an earlyclobber operand, which is written before the instruction is finished using the input operands. Therefore, this operand may not lie in a register that is read by the instruction or as part of any memory address.

‘&’ applies only to the alternative in which it is written. In constraints with multiple alternatives, sometimes one alternative requires ‘&’ while others do not. See, for example, the ‘movdf’ insn of the 68000.

A operand which is read by the instruction can be tied to an earlyclobber operand if its only use as an input occurs before the early result is written. Adding alternatives of this form often allows GCC to produce better code when only some of the read operands can be affected by the earlyclobber. See, for example, the ‘mulsi3’ insn of the ARM.

Furthermore, if the earlyclobber operand is also a read/write operand, then that operand is written only after it’s used.

‘&’ does not obviate the need to write ‘=’ or ‘+’. As earlyclobber operands are always written, a read-only earlyclobber operand is ill-formed and will be rejected by the compiler.

对代码的更改是将 "=r"(sum) 修改为 "=&r"(sum)。这将防止编译器将用于输出约束的寄存器用于输入约束之一。

警告。 GCC 内联汇编是强大而邪恶的。如果您不知道自己在做什么,很容易出错。只在必须时使用它,尽可能避免它。