简体   繁体   English

在 .intel_syntax GNU C 内联汇编中引用内存操作数

[英]Referencing memory operands in .intel_syntax GNU C inline assembly

I'm catching a link error when compiling and linking a source file with inline assembly.使用内联汇编编译和链接源文件时,我发现链接错误。

Here are the test files:以下是测试文件:

via:$ cat test.cxx
extern int libtest();
int main(int argc, char* argv[])
{
    return libtest();
}

$ cat lib.cxx
#include <stdint.h>
int libtest()
{
    uint32_t rnds_00_15;    
    __asm__ __volatile__
    (
        ".intel_syntax noprefix         ;\n\t"
        "mov DWORD PTR [rnds_00_15], 1  ;\n\t"
        "cmp DWORD PTR [rnds_00_15], 1  ;\n\t"
        "je  done                       ;\n\t"
        "done:                          ;\n\t"
        ".att_syntax noprefix           ;\n\t"
        :
        : [rnds_00_15] "m" (rnds_00_15)
        : "memory", "cc"
    );

    return 0;
}

Compiling and linking the program results in:编译和链接程序会导致:

via:$ g++ -fPIC test.cxx lib.cxx -c
via:$ g++ -fPIC lib.o test.o -o test.exe
lib.o: In function `libtest()':
lib.cxx:(.text+0x1d): undefined reference to `rnds_00_15'
lib.cxx:(.text+0x27): undefined reference to `rnds_00_15'
collect2: error: ld returned 1 exit status

The real program is more complex.真正的程序更复杂。 The routine is out of registers so the flag rnds_00_15 must be a memory operand.该例程没有寄存器,因此标志rnds_00_15必须是内存操作数。 Use of rnds_00_15 is local to the asm block. rnds_00_15使用是 asm 块本地的。 It is declared in the C code to ensure the memory is allocated on the stack and nothing more.它在 C 代码中声明,以确保在堆栈上分配内存,仅此而已。 We don't read from it or write to it as far as the C code is concerned.就 C 代码而言,我们不读取它或写入它。 We list it as a memory input so GCC knows we use it and wire up the "C variable name" in the extended ASM.我们将它列为内存输入,以便 GCC 知道我们使用它并在扩展 ASM 中连接“C 变量名称”。

Why am I receiving a link error, and how do I fix it?为什么我会收到链接错误,我该如何解决?

Compile with gcc -masm=intel and don't try to switch modes inside the asm template string.使用gcc -masm=intel编译并且不要尝试在 asm 模板字符串中切换模式。 AFAIK there's no equivalent for clang (note that MacOS installs clang as gcc / g++ by default.) AFAIK 没有等效的 clang(请注意,MacOS 默认将 clang 安装为gcc / g++ 。)

Also, of course you need to use valid GNU C inline asm, using operands to tell the compiler which C objects you want to read and write.此外,当然您需要使用有效的 GNU C 内联 asm,使用操作数告诉编译器您想要读取和写入哪些 C 对象。


I don't believe Intel syntax uses the percent sign.我不相信英特尔语法使用百分号。 Perhaps I am missing something?也许我错过了什么?

You're getting mixed up between %operand substitutions into the Extended-Asm template (which use a single % ) , vs. the final asm that the assembler sees.您在%operand替换到 Extended-Asm 模板(使用单个%与汇编程序看到的最终 asm之间混淆了

You need %% to use a literal % in the final asm.您需要%%在最终汇编中使用文字% You wouldn't use "mov %%eax, 1" in Intel-syntax inline asm, but you do still use "mov %0, 1" or %[named_operand] .您不会在英特尔语法内联汇编中使用"mov %%eax, 1" ,但您仍然使用"mov %0, 1"%[named_operand]

See https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html .请参阅https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html In Basic asm (no operands), there is no substitution and % isn't special in the template, so you'd write mov $1, %eax in Basic asm vs. mov $1, %%eax in Extended, if for some reason you weren't using an operand like mov $1, %[tmp] or mov $1, %0 .在 Basic asm(无操作数)中,没有替换并且 % 在模板中并不特殊,因此如果出于某种原因,您可以在 Basic asm 中编写mov $1, %eax与在 Extended 中编写mov $1, %%eax你没有使用像mov $1, %[tmp]mov $1, %0这样的操作数。


uint32_t rnds_00_15; is a local with automatic storage.是具有自动存储功能的本地。 Of course it there's no asm symbol with that name.当然,它没有带有该名称的 asm 符号。

Use %[rnds_00_15] and compile with -masm=intel (And remove the .att_syntax at the end; that would break the compiler-generate asm that comes after.)使用%[rnds_00_15]并使用-masm=intel编译(并在最后删除.att_syntax ;这会破坏后面的编译器生成的 asm。)

You also need to remove the DWORD PTR , because the operand-expansion already includes that, eg DWORD PTR [rsp - 4] , and clang errors on DWORD PTR DWORD PTR [rsp - 4] .您还需要删除DWORD PTR ,因为操作数扩展已经包含了它,例如DWORD PTR [rsp - 4]DWORD PTR DWORD PTR [rsp - 4]上的 clang 错误。 (GAS accepts it just fine, but the 2nd one takes precendence so it's pointless and potentially misleading.) (GAS 接受它就好了,但第二个优先,所以它毫无意义并且可能会产生误导。)

And you'll want a "=m" output operand if you want the compiler to reserve you some scratch space on the stack.如果您希望编译器在堆栈上为您保留一些暂存空间,您将需要一个"=m"输出操作数。 You must not modify input-only operands, even if it's unused in the C. Maybe the compiler decides it can overlap something else because it's not written and not initialized (ie UB).您不能修改仅输入操作数,即使它在 C 中未使用。也许编译器决定它可以与其他内容重叠,因为它没有被写入并且没有初始化(即 UB)。 (I'm not sure if your "memory" clobber makes it safe, but there's no reason not to use an early-clobber output operand here.) (我不确定你的"memory" clobber 是否使它安全,但没有理由不在这里使用早期的clobber 输出操作数。)

And you'll want to avoid label name conflicts by using %= to get a unique number.并且您将希望通过使用%=获取唯一编号来避免标签名称冲突。

Working example (GCC and ICC, but not clang unfortunately) , on the Godbolt compiler explorer (which uses -masm=intel depending on options in the dropdown).工作示例(GCC 和 ICC,但不幸的是不是 clang)在 Godbolt 编译器资源管理器(根据下拉列表中的选项使用-masm=intel )。 You can use "binary mode" (the 11010 button) to prove that it actually assembles after compiling to asm without warnings.您可以使用“二进制模式”(11010 按钮)来证明它在编译为 asm 后实际组装而没有警告。

int libtest_intel()
{
    uint32_t rnds_00_15;
    // Intel syntax operand-size can only be overridden with operand modifiers
    // because the expansion includes an explicit DWORD PTR
    __asm__ __volatile__
    (  // ".intel_syntax noprefix \n\t"
        "mov %[rnds_00_15], 1  \n\t"
        "cmp %[rnds_00_15], 1  \n\t"
        "je  .Ldone%=                 \n\t"
        ".Ldone%=:                    \n\t"
        : [rnds_00_15] "=&m" (rnds_00_15)
        :
        : // no clobbers
    );
    return 0;
}

Compiles (with gcc -O3 -masm=intel ) to this asm.编译(使用gcc -O3 -masm=intel )到这个 asm。 Also works with gcc -m32 -masm=intel of course:当然也适用于gcc -m32 -masm=intel

libtest_intel:
    mov DWORD PTR [rsp-4], 1  
    cmp DWORD PTR [rsp-4], 1  
    je  .Ldone8                 
.Ldone8:                    

    xor     eax, eax
    ret

I couldn't get this to work with clang: It choked on .intel_syntax noprefix when I left that in explicitly .我无法.intel_syntax noprefix与 clang 一起工作:当我将其明确留在.intel_syntax noprefix时,它会窒息


Operand-size overrides:操作数大小覆盖:

You have to use %b[tmp] to get the compiler to substitute in BYTE PTR [rsp-4] to only access the low byte of a dword input operand.您必须使用%b[tmp]来让编译器在BYTE PTR [rsp-4]替换以仅访问双字输入操作数的低字节。 I'd recommend AT&T syntax if you want to do much of this.如果你想做很多事情,我会推荐 AT&T 语法。



Using %[rnds_00_15] results in Error: junk '(%ebp)' after expression.使用%[rnds_00_15]导致Error: junk '(%ebp)' after expression.

That's because you switched to Intel syntax without telling the compiler.那是因为您在没有告诉编译器的情况下切换到 Intel 语法。 If you want it to use Intel addressing modes, compile with -masm=intel so the compiler can substitute into the template with the correct syntax.如果您希望它使用 Intel 寻址模式,请使用-masm=intel编译,以便编译器可以使用正确的语法替换模板。

This is why I avoid that crappy GCC inline assembly at nearly all costs.这就是为什么我几乎不惜一切代价避免糟糕的 GCC 内联汇编。 Man I despise this crappy tool.伙计,我鄙视这种蹩脚的工具。

You're just using it wrong.你只是用错了。 It's a bit cumbersome, but makes sense and mostly works well if you understand how it's designed.这有点麻烦,但很有意义,而且如果您了解它的设计方式,则大多数情况下都能很好地工作。

Repeat after me: The compiler doesn't parse the asm string at all , except to do text substitutions of %operand .之后我再说一遍编译器不分析所有的ASM字符串,除了做的文本替换%operand This is why it doesn't notice your .intel_syntax noprefex and keeps substituting AT&T syntax.这就是为什么它没有注意到您的.intel_syntax noprefex并不断替换 AT&T 语法的原因。

It does work better and more easily with AT&T syntax though, eg for overriding the operand-size of a memory operand, or adding an offset.不过,使用 AT&T 语法确实可以更好、更轻松地工作,例如用于覆盖内存操作数的操作数大小,或添加偏移量。 (eg 4 + %[mem] works in AT&T syntax). (例如4 + %[mem]在 AT&T 语法中工作)。


Dialect alternatives:方言替代:

If you want to write inline asm that doesn't depend on -masm=intel or not, use Dialect alternatives (which makes your code super-ugly; not recommended for anything other than wrapping one or two instructions):如果您想编写不依赖于-masm=intel或不依赖于的内联汇编,请使用方言替代方案(这会使您的代码超级丑陋;除了包装一两个指令外,不建议用于任何其他用途):

Also demonstrates operand-size overrides还演示了操作数大小覆盖

#include <stdint.h>
int libtest_override_operand_size()
{
    uint32_t rnds_00_15;
    // Intel syntax operand-size can only be overriden with operand modifiers
    // because the expansion includes an explicit DWORD PTR
    __asm__ __volatile__
    (
        "{movl $1, %[rnds_00_15] | mov %[rnds_00_15], 1}  \n\t"
        "{cmpl $1, %[rnds_00_15] | cmp %k[rnds_00_15], 1}  \n\t"
        "{cmpw $1, %[rnds_00_15] | cmp %w[rnds_00_15], 1}  \n\t"
        "{cmpb $1, %[rnds_00_15] | cmp %b[rnds_00_15], 1}  \n\t"
        "je  .Ldone%=                     \n\t"
        ".Ldone%=:                        \n\t"
        : [rnds_00_15] "=&m" (rnds_00_15)
    );
    return 0;
}

With Intel syntax, gcc compiles it to:使用 Intel 语法,gcc 将其编译为:

     mov DWORD PTR [rsp-4], 1  
     cmp DWORD PTR [rsp-4], 1  
     cmp WORD PTR [rsp-4], 1  
     cmp BYTE PTR [rsp-4], 1  
    je  .Ldone38                     
.Ldone38:                        

    xor     eax, eax
    ret

With AT&T syntax, compiles to:使用 AT&T 语法,编译为:

    movl $1, -4(%rsp)   
    cmpl $1, -4(%rsp)   
    cmpw $1, -4(%rsp)   
    cmpb $1, -4(%rsp)   
    je  .Ldone38                     
.Ldone38:                        

    xorl    %eax, %eax
    ret

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM