Den*_*nis 5 x86 assembly gcc inline-assembly intel-syntax
我有以下代码,可以用 gcc 命令很好地编译gcc ./example.c
。程序本身调用函数“add_two”,它只是将两个整数相加。要在扩展汇编指令中使用 intel 语法,我需要首先切换到 intel,然后再切换回 AT&T。根据 gcc 文档,可以使用gcc -masm=intel ./exmaple
.
每当我尝试使用 switch 编译它时,-masm=intel
它都不会编译,我不明白为什么?我已经尝试删除该指令,.intel_syntax
但它仍然无法编译。
#include <stdio.h>
int add_two(int, int);
int main(){
int src = 3;
int dst = 5;
printf("summe = %d \n", add_two(src, dst));
return 0;
}
int add_two(int src, int dst){
int sum;
asm (
".intel_syntax;" //switch to intel syntax
"mov %0, %1;"
"add %0, %2;"
".att_syntax;" //switch to at&t syntax
: "=r" (sum) //output
: "r" (src), "r" (dst) //input
);
return sum;
}
Run Code Online (Sandbox Code Playgroud)
通过编译上述程序的错误消息gcc -masm=intel ./example.c
是:
tmp/ccEQGI4U.s: Assembler messages:
/tmp/ccEQGI4U.s:55: Error: junk `PTR [rbp-4]' after expression
/tmp/ccEQGI4U.s:55: Error: too many memory references for `mov'
/tmp/ccEQGI4U.s:56: Error: too many memory references for `mov'
Run Code Online (Sandbox Code Playgroud)
在您的内联汇编中使用-masm=intel
和不使用任何指令。.att_syntax
这适用于 GCC,我认为 ICC,以及您使用的任何约束。其他方法则不然。(请参阅 我可以在 GCC 中使用 x86 汇编的 Intel 语法吗?以获得一个简单的答案;这个答案探讨了到底出了什么问题,包括 clang 13 及更早版本。)
这也适用于clang 14及更高版本。(尚未发布,但该补丁是当前主干的一部分;请参阅https://reviews.llvm.org/D113707)。
Clang 13 及更早版本始终使用 AT&T 语法进行内联 asm,无论是替换操作数还是汇编为op src, dst
。但更糟糕的是,即使使用像“: ... )`clang -masm=intel
这样的方言替代品来获取 asm 模板的 Intel 端,也会这样做!asm ("add {att | intel}
clang -masm=intel
在其内置汇编器将语句转换为指令的某种内部表示之后,仍然控制着它如何打印asm。asm()
例如,Godbolt显示 clang13-masm=intel
转动add %0, 1
为add dword ptr [1], eax
,但 clang trunk 产生add eax, 1
。
这个答案中有关 clang 的其余部分尚未针对这个新的 clang 补丁进行更新。
Clang 确实支持 MSVC 风格的 asm 块内的 Intel 语法,但这很糟糕(没有限制,因此输入/输出必须通过内存。
如果您使用 clang 对寄存器名称进行硬编码,-masm=intel
则可以使用(或等效的-mllvm --x86-asm-syntax=intel
)。但它mov %eax, 5
在 Intel 语法模式下会阻塞,因此您不能%0
扩展为 AT&T 语法寄存器名称。
-masm=intel
使编译器.intel_syntax noprefix
在其 asm 输出文件的顶部使用,并在 inline-asm 语句之外从 C 生成 asm 时使用 Intel 语法。 在 asm 模板的底部使用.att_syntax
会破坏编译器的 asm ,因此错误消息PTR [rbp-4]
对于汇编器来说看起来像垃圾(需要 AT&T 语法)。
“mov 的操作数太多”是因为在 AT&T 语法中,mov eax, ebx
是mov
从内存操作数(带有符号名称eax
)到内存操作数(带有符号名称ebx
)
有些人建议使用.intel_syntax noprefix
and .att_syntax prefix
around 你的 asm 模板。这有时可行,但有问题。并且与首选方法不兼容-masm=intel
。
当编译器将操作数替换为 asm 模板时,它将根据-masm=
. 对于内存操作数来说,这总是会中断(寻址模式语法完全不同)。
即使对于寄存器来说,它也会与 clang 中断。 Clang 的内置汇编器不接受%eax
Intel 语法模式下的寄存器名称,并且不接受.intel_syntax prefix
(与noprefix
通常与 Intel 语法一起使用的相反)。
考虑这个函数:
int foo(int x) {
asm(".intel_syntax noprefix \n\t"
"add %0, 1 \n\t"
".att_syntax"
: "+r"(x)
);
return x;
}
Run Code Online (Sandbox Code Playgroud)
它与 GCC ( Godbolt )的组装如下:
movl %edi, %eax
.intel_syntax noprefix
add %eax, 1 # AT&T register name in Intel syntax
.att_syntax
Run Code Online (Sandbox Code Playgroud)
即使在 Intel 语法模式下,三明治方法也依赖于 GAS 接受%eax
作为寄存器名称。GNU Binutils 的 GAS 可以,但 clang 的内置汇编器则不能。
在 Mac 上,即使使用真正的 GCC,asm 输出也必须使用as
基于 clang 的汇编,而不是基于 GNU Binutils。
在该源代码上使用 clang 会抱怨:
<source>:2:35: error: unknown token in expression
asm(".intel_syntax noprefix \n\t"
^
<inline asm>:2:6: note: instantiated into assembly here
add %eax, 1
^
Run Code Online (Sandbox Code Playgroud)
(错误消息的第一行不能很好地处理多行字符串文字。如果您使用;
代替\n\t
并将所有内容放在一行上,则 clang 错误消息效果更好,但源代码很混乱。)
我没有检查"ri"
当编译器选择立即数时约束会发生什么;$
如果 GAS 在 Intel 语法模式下也默默地忽略它,它仍然会用 but IDK 来装饰它。
PS:您的 asm 语句有一个错误:您忘记了输出操作数上的早期破坏,因此没有什么可以阻止编译器为输出%0
和输入选择相同的%2
寄存器,直到第二条指令为止。然后mov
将销毁一个输入。
但用作mov
asm 模板的第一条或最后一条指令通常也是一个错过优化的错误。在这种情况下,您可以而且应该使用lea %0, [%1 + %2]
让编译器将结果非破坏性地添加到第三个寄存器。或者只是包装add
指令(使用"+r"
操作数和"r"
,让编译器担心数据移动。)如果无论如何都必须从内存加载值,它可以将其放入正确的寄存器中,因此不需要mov
。
PS:可以使用GNU C 内联 asm方言替代品-masm=intel
来编写与或 一起使用的内联 asm 。例如att
void atomic_inc(int *p) {
asm( "lock add{l $1, %0 | %0, 1}"
: "+m" (*p)
:: "memory"
);
}
Run Code Online (Sandbox Code Playgroud)
编译为gcc -O2
(-masm=att
默认)到
atomic_inc(int*):
lock addl $1, (%rdi)
ret
Run Code Online (Sandbox Code Playgroud)
或者与-masm=intel
:
atomic_inc(int*):
lock add DWORD PTR [rdi], 1
ret
Run Code Online (Sandbox Code Playgroud)
请注意,l
AT&T 需要后缀,而dword ptr
intel 需要后缀,因为内存、立即数并不意味着操作数大小。并且编译器为这两种情况填写了有效的寻址模式语法。
这适用于 clang,但只有 AT&T 版本被使用。