【问题标题】:How to inline-assembly with Clang 11, intel syntax and substitution variables如何使用 Clang 11、intel 语法和替换变量进行内联汇编
【发布时间】:2021-06-06 11:42:15
【问题描述】:

我很难让它工作:


我尝试了以下方法:

 uint32_t reverseBits(volatile uint32_t n) {
        uint32_t i = n;
    __asm__ (".intel_syntax\n"
            "xor eax, eax \n" 
            "inc eax \n"
       "myloop: \n"
            "shr %0, 1 \n"
            "adc eax, eax \n"
            "jnc short myloop \n"
            "mov %1, %0  \n"
            : [i] "=r"(i),  [n] "=r"(n));;

        return n;
    }

我会得到:

Line 11: Char 14: error: unknown token in expression
            "shr %0, 1 \n"
             ^
<inline asm>:5:5: note: instantiated into assembly here
shr %edx, 1
    ^

显然编译器将%0 替换为%register,但仍保留'%'...


因此,我决定将%0 替换为edx,将%1 替换为ecx

 uint32_t reverseBits(volatile uint32_t n) {
        uint32_t i = n;
    __asm__ (".intel_syntax\n"
            "xor eax, eax \n" 
            "inc eax \n"
       "myloop: \n"
            "shr edx, 1 \n"
            "adc eax, eax \n"
            "jnc short myloop \n"
            "mov ecx, edx  \n"
            : [i] "=r"(i),  [n] "=r"(n));;

        return n;
    }

并得到结果错误:

AddressSanitizer:DEADLYSIGNAL
=================================================================
==31==ERROR: AddressSanitizer: SEGV on unknown address 0x0001405746c8 (pc 0x00000034214d bp 0x7fff1363ed90 sp 0x7fff1363ea20 T0)
==31==The signal is caused by a READ memory access.
    #1 0x7f61ff3970b2  (/lib/x86_64-linux-gnu/libc.so.6+0x270b2)
AddressSanitizer can not provide additional info.
==31==ABORTING

我怀疑编译器优化了一些东西并内联了被调用的函数(所以不是 ret),但我仍然不知道该怎么做。

注意:我无法将编译器从 clang 更改为 gcc,因为它不是我,而是使用 clang 11 的远程服务器。我也已经有 read this link,但它已经很老了(2013 年),如果有事情发生,我会感到惊讶从那以后就没有改变过。


编辑:按照 Peter Cordes 的出色回答,我能够让它工作得更好一点:

uint32_t reverseBits(volatile uint32_t n) {
        uint32_t i = n;

    __asm__ (".intel_syntax noprefix\n"
            "xor rax,rax \n" 
            "inc rax \n"

       "myloop: \n"
            "shr %V0, 1 \n"
            "adc eax, eax \n"
            "jnc short myloop \n"
            "mov %V0, rax \n"
   
             ".att_syntax"
            : [i] "=r"(i));;
    
        return i;
    }

但是有两件事:

1/ 我不得不将 eax 更改为 rax,因为 %V0 占用 64 位 (r13),这很奇怪,因为 i 应该只占 32 位 (uint32_t)。

2/ 我没有得到想要的输出:

input is :             00000010100101000001111010011100
output is:   93330624 (00000101100100000001110011000000)
expected:   964176192 (00111001011110000010100101000000)

注意:我测试了 "mov %V0, 1 \n" 并正确地得到了1 作为输出,这证明了替换以某种方式起作用。

【问题讨论】:

  • Re:您的编辑:您使用 RAX 而不将其声明为 clobber。此外,[i] "=r"(i) 告诉编译器操作数是仅输出的,因此它可以优化掉任何会写入它选择的寄存器的东西。如果您坚持使用它,请参阅 stackoverflow.com/tags/inline-assembly/info 中的 inline-asm 指南以了解基础知识。

标签: x86 clang inline-assembly intel-syntax


【解决方案1】:

我不知道有什么好的方法可以做到这一点,我推荐 GNU C 内联 asm 的 AT&T 语法(或方言替代品add {%1,%0 | %0,%1},因此它适用于 GCC。)-masm=intel 等选项不让 clang 像在 GCC 中那样替换裸寄存器名称。

How to generate assembly code with clang in Intel syntax? 是关于用于-S 输出 的语法,并且与 GCC 不同,它没有连接到编译器的 inline-asm 输入的语法。 --x86-asm-syntax=intel 的行为没有改变:它仍然以 Intel 语法输出,并且对内联 asm 没有帮助。


您可以滥用%V0%V[i](而不是%0%[i])在模板中打印“裸”全注册名称 https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#x86Operandmodifiers,但是很烂,因为它只打印 full 寄存器名称。即使对于选择 EAX 的 32 位 int,它也会打印 RAX 而不是 EAX

(对于"m" 内存操作数获取dword ptr [rsp + 16] 或任何编译器选择的寻址模式也不起作用,但总比没有好。虽然IMO 并不比仅使用AT&T 语法更好。)


或者您可以选择像"=a"(var) 这样的硬寄存器,然后直接使用EAX 而不是%0。但这更糟,并且破坏了约束系统的一些优化优势。

您的模板中仍然需要".intel_syntax noprefix\n",并且您应该".att_syntax" 结束您的模板,以将汇编器切换回AT&T 模式以汇编稍后编译器生成的asm。 (如果您希望您的代码与 GCC 一起使用,则需要!clang 的内置汇编器不会在汇编之前将您的内联 asm 文本合并到一个大的 asm 文本文件中,它会直接进入机器代码以获取编译器生成的指令。)


显然告诉编译器它可以使用"=r" 选择任何寄存器,然后实际使用您自己的硬编码选择,当编译器选择不同时会产生未定义的行为。您将踩到编译器的脚趾并破坏它以后想要使用的值,并让它从错误的寄存器中获取垃圾作为输出。 IDK 你为什么费心在你的问题中包含这个;出于同样明显的原因,这会以完全相同的方式破坏 AT&T 语法。

【讨论】:

  • 嗨,彼得,非常感谢,我听从了您的建议,请参阅我的编辑。我选择您的见解作为答案,因为它似乎回答了原始问题,但有些事情仍然不起作用,请看一下我的编辑,特别是我希望 %V0 充当 32 位寄存器〜跨度>
  • @AntoninGAVREL:我又看了一遍文档;事实证明,RAX 预计用于%V0。如果 clang 选择 EAX,V 会告诉它打印 full 寄存器的裸名,即 RAX。或 R13 而不是 R13D。
猜你喜欢
  • 2012-06-14
  • 2017-04-24
  • 2012-12-15
  • 1970-01-01
  • 2021-05-20
  • 1970-01-01
  • 2012-10-28
  • 2013-01-15
相关资源
最近更新 更多