5

我有以下代码,可以使用 gcc 命令编译gcc ./example.c。程序本身调用函数“add_two”,它只是将两个整数相加。要在扩展汇编指令中使用 intel 语法,我首先需要切换到 intel,然后再切换回 AT&T。根据 gcc 文档,可以使用gcc -masm=intel ./exmaple.

每当我尝试用开关编译它时,-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;
}

通过编译上述程序的错误消息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' 
4

2 回答 2

5

在内联汇编中使用-masm=intel使用任何.att_syntax指令。 这适用于 GCC,我认为是 ICC,以及您使用的任何约束。其他方法没有。(请参阅 Can I use Intel syntax of x86 assembly with GCC?以获得一个简单的答案;这个答案探讨了究竟出了什么问题,包括 clang 13 和更早版本。)

这也适用于clang 14及更高版本。(尚未发布,但补丁是当前主干的一部分;请参阅https://reviews.llvm.org/D113707)。

Clang 13 和更早版本总是将 AT&T 语法用于内联 asm,无论是在替换操作数还是在组装 as 时op src, dst。但更糟糕的clang -masm=intel是,即使在使用诸如asm ("add {att | intel}“: ... )` 之类的方言替代方案的 asm 模板的 Intel 方面也会这样做!

clang -masm=intel在其内置汇编程序将语句转换为指令的某种内部表示之后,它仍然控制了它如何打印asm。asm()例如Godbolt显示 clang13-masm=intel转动add %0, 1add dword ptr [1], eax,但 clang 主干产生add eax, 1

这个答案的其余部分谈论铿锵声尚未针对这个新的铿锵补丁进行更新。

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-syntax。 在 asm 模板的底部使用.att_syntax会破坏编译器的 asm ,因此错误消息PTR [rbp-4]看起来像汇编器的垃圾(需要 AT&T 语法)。

“mov 的操作数太多”是因为在 AT&T 语法中,mov eax, ebxmov从内存操作数(带有符号名称eax)到内存操作数(带有符号名称ebx


有些人建议使用.intel_syntax noprefix.att_syntax prefix围绕您的 asm 模板。这有时可以工作,但它是有问题的。并且与首选方法不兼容-masm=intel

“三明治”方法的问题:

当编译器将操作数替换为您的 asm 模板时,它将根据-masm=. 这对于内存操作数总是会中断(寻址模式语法完全不同)。

即使是寄存器,它也会因clang而中断。 Clang 的内置汇编器%eax在 Intel 语法模式下不接受作为寄存器名称,并且不接受.intel_syntax prefixnoprefix与 Intel 语法通常使用的相反)。

考虑这个函数:

int foo(int x) {
    asm(".intel_syntax noprefix \n\t"
        "add  %0, 1  \n\t"
        ".att_syntax"
         : "+r"(x)
        );
    return x;
}

它与 GCC ( Godbolt )组装如下:

        movl    %edi, %eax
        .intel_syntax noprefix 
         add %eax, 1                    # AT&T register name in Intel syntax
        .att_syntax

三明治方法依赖于 GAS 接受%eax作为寄存器名称,即使在 Intel 语法模式下也是如此。来自 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
            ^

(错误消息的第一行没有很好地处理多行字符串文字。如果您使用;代替\n\t并将所有内容放在一行上,则 clang 错误消息效果更好,但源代码一团糟。)


"ri"当编译器选择立即数时,我没有检查约束会发生什么;如果 GAS 在 Intel 语法模式下也默默地忽略它,它仍然会用$IDK 装饰它。


PS:您的 asm 语句有一个错误:您在输出操作数上忘记了一个 early-clobber,因此没有什么能阻止编译器为%0输出和%2输入选择相同的寄存器,直到第二条指令才读取。然后mov会破坏一个输入。

但是使用movasm 模板的第一条或最后一条指令通常也是一个错过优化的错误。在这种情况下,您可以并且应该只使用lea %0, [%1 + %2]让编译器将结果添加到第三个寄存器中,非破坏性的。或者只是包装add指令(使用"+r"操作数和"r",让编译器担心数据移动。)如果无论如何它必须从内存中加载值,它可以将它放在正确的寄存器中,因此mov不需要。


PS:可以使用GNU C inline asm dialect alternatives编写与-masm=intelor一起使用的内联 asm 。例如att

void atomic_inc(int *p) {
    asm( "lock add{l $1, %0 | %0, 1}"
       : "+m" (*p)
       :: "memory"
    );
}

gcc -O2用(-masm=att是默认值)编译为

atomic_inc(int*):
    lock addl $1, (%rdi) 
    ret

或与-masm=intel

atomic_inc(int*):
    lock add DWORD PTR [rdi], 1
    ret

请注意,lAT&T 需要后缀dword ptr,intel 需要后缀,因为 memory, immediate 并不意味着操作数大小。并且编译器为这两种情况填写了有效的寻址模式语法。

这适用于 clang,但只有 AT&T 版本被使用过。

于 2019-09-29T11:55:49.883 回答
3

请注意,这-masm=也会影响默认的内联汇编器语法:

使用选定的方言输出汇编指令。还会影响基本“asm”和扩展“asm”使用哪种方言。支持的选项(按方言顺序)是 att 或 intel。默认值为 att。达尔文不支持英特尔。

这意味着您的第一个.intel_syntax指令是多余的,而最后一个指令是.att_syntax错误的,因为您的 GCC 调用将 C 编译为 Intel 汇编代码。

IOW,要么坚持或夹在和指令-masm=intel之间的内联英特尔汇编代码部分- 但不要两者都做。.intel_syntax noprefix.att_syntax prefix

请注意,三明治方法与所有内联汇编器约束不兼容 - 例如,涉及m(即内存操作数)的约束将在 ATT 语法中插入一个操作数,这将产生类似“错误:表达式后的垃圾 (%rbp)”之类的错误。在这些情况下,您必须使用-masm=intel.

于 2019-09-28T14:14:05.090 回答