本文介绍了在.intel_syntax GNU C内联汇编中引用内存操作数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

限时删除!!

在编译和链接带有内联汇编的源文件时,我遇到了链接错误.

I'm catching a link error when compiling and linking a source file with inline assembly.

以下是测试文件:

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

实际程序更复杂.该例程的寄存器不足,因此标志rnds_00_15必须是一个内存操作数. rnds_00_15的使用对于asm块是本地的.它在C代码中声明,以确保在堆栈上分配内存,仅此而已.就C代码而言,我们不会对其进行读取或写入.我们将其列为内存输入,以便GCC知道我们已使用它并在扩展的ASM中连接"C变量名".

The real program is more complex. The routine is out of registers so the flag rnds_00_15 must be a memory operand. Use of rnds_00_15 is local to the asm block. It is declared in the C code to ensure the memory is allocated on the stack and nothing more. We don't read from it or write to it as far as the C code is concerned. We list it as a memory input so GCC knows we use it and wire up the "C variable name" in the extended ASM.

为什么我会收到链接错误,该如何解决?

Why am I receiving a link error, and how do I fix it?

推荐答案

您正在将%operand替换到Extended-Asm模板(使用单个 %)中的情况混为一谈,而最终的asm汇编程序会看到.

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.

您需要%%才能在最终汇编中使用文字%.您不会在英特尔语法内联汇编中使用"mov %%eax, 1",但仍会使用"mov %0, 1"%[named_operand].

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].

请参见 https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html .在Basic asm(无操作数)中,没有替换,并且%在模板中不是特殊的,因此,如果出于某种原因您未使用,请在Basic asm中编写mov $1, %eax,而在Extended中编写mov $1, %%eax. mov $1, %[tmp]mov $1, %0之类的操作数.

See 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.

uint32_t rnds_00_15;是具有自动存储功能的本地文件.当然,没有使用该名称的asm符号.

uint32_t rnds_00_15; is a local with automatic storage. Of course it there's no asm symbol with that name.

使用%[rnds_00_15]并使用-masm=intel 进行编译(并删除最后的.att_syntax;这将破坏随后的编译器生成的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.)

您还需要删除DWORD PTR,因为操作数扩展已包含该内容,例如DWORD PTR [rsp - 4],以及DWORD PTR DWORD PTR [rsp - 4]上的c铛错误. (GAS接受它很好,但是第二个优先,所以它是毫无意义的,并且可能会引起误解.)

You also need to remove the DWORD PTR, because the operand-expansion already includes that, e.g. DWORD PTR [rsp - 4], and clang errors on DWORD PTR DWORD PTR [rsp - 4]. (GAS accepts it just fine, but the 2nd one takes precendence so it's pointless and potentially misleading.)

如果希望编译器在堆栈上保留一些暂存空间,则需要一个"=m"输出操作数.即使在C语言中没有使用纯输入操作数,也不能修改它.也许编译器决定它可以与其他内容重叠,因为它没有被编写和初始化(即UB). (我不确定您的"memory"垃圾邮件是否可以保证安全,但是这里没有理由不使用早期垃圾邮件输出操作数.)

And you'll want a "=m" output operand if you want the compiler to reserve you some scratch space on the stack. 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 (i.e. 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.)

并且您希望通过使用%=获取唯一编号来避免标签名称冲突.

And you'll want to avoid label name conflicts by using %= to get a unique number.

,.您可以使用二进制模式"(11010按钮)来证明它在编译为asm且没有警告的情况下实际上已经组装完毕.

Working example (GCC and ICC, but not clang unfortunately), on the Godbolt compiler explorer. You can use "binary mode" (the 11010 button) to prove that it actually assembles after compiling to asm without warnings.

int libtest_intel()
{
    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__
    (  // ".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;
}

编译(使用gcc -O3)此asm.当然也可以与gcc -m32一起使用:

Compiles (with gcc -O3) to this asm. Also works with gcc -m32 of course:

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

    xor     eax, eax
    ret

我无法使它与clang一起使用:当我明确将其留在.intel_syntax noprefix上时,它在.intel_syntax noprefix上cho住了.

I couldn't get this to work with clang: It choked on .intel_syntax noprefix when I left that in explicitly.

您必须使用%b[tmp]来使编译器替代BYTE PTR [rsp-4],以便仅访问dword输入操作数的低字节.如果您想做很多事情,我建议您使用AT& T语法.

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. I'd recommend AT&T syntax if you want to do much of this.

那是因为您在不告知编译器的情况下切换到了Intel语法.如果要使用Intel寻址模式,请使用-masm=intel 进行编译,以便编译器可以使用正确的语法替换模板.

That's because you switched to Intel syntax without telling the compiler. 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.

您只是错误地使用了它.这有点麻烦,但是很有意义,而且如果您了解它的设计原理,那么大多数情况下都可以很好地工作.

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.

跟着我重复:编译器根本不解析asm字符串 ,只是对%operand 进行文本替换.这就是为什么它不注意您的.intel_syntax noprefex并不断替换AT& T语法的原因.

Repeat after me: The compiler doesn't parse the asm string at all, except to do text substitutions of %operand. This is why it doesn't notice your .intel_syntax noprefex and keeps substituting AT&T syntax.

例如,使用AT& T语法确实可以更好,更轻松地工作.用于覆盖内存操作数的操作数大小,或添加偏移量. (例如4 + %[mem]使用AT& T语法).

It does work better and more easily with AT&T syntax though, e.g. for overriding the operand-size of a memory operand, or adding an offset. (e.g. 4 + %[mem] works in AT&T syntax).

如果要编写不依赖-masm=intel的内联汇编,请使用方言替代方法(这会使您的代码非常丑陋;不建议用于包装一条或两条指令)

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):

还演示了操作数大小的覆盖

#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;
}

使用Intel语法,gcc将其编译为:

With Intel syntax, gcc compiles it to:

     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

使用AT& T语法,编译为:

With AT&T syntax, compiles to:

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

    xorl    %eax, %eax
    ret

这篇关于在.intel_syntax GNU C内联汇编中引用内存操作数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

1403页,肝出来的..

09-07 03:10