x86 - 如何使用 Clang 11、intel 语法和替换变量进行内联汇编

标签 x86 clang inline-assembly intel-syntax

我很难让它发挥作用:


我尝试过以下方法:

 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
    ^

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


因此,我决定将 %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 作为输出,这证明了替换在某种程度上是有效的。

最佳答案

我不知道有什么好的方法可以做到这一点,我推荐 GNU C 内联汇编的 AT&T 语法(或方言替代add {%1,%0 | %0,%1} 所以它对 GCC 来说是双向的。)像 -masm=intel 这样的选项不会像 GCC 那样用 clang 来替换裸寄存器名称。

(更新:clang 14 更改:How to set gcc or clang to use Intel syntax permanently for inline asm() statements?)

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


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

(它也不适用于 "m" 内存操作数来获取 dword ptr [rsp + 16] 或任何编译器选择的寻址模式,但它更好总比没有好。尽管在我看来,这并不比仅使用 AT&T 语法更好。)


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

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


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

关于x86 - 如何使用 Clang 11、intel 语法和替换变量进行内联汇编,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66532417/

相关文章:

assembly - .code16 和 .code32 有什么区别

assembly - 如何在 IA32 上将带符号的整数求和为更宽的和。 32 位有符号整数的 64 位和?

x86 - 使用输入字符串和 ret 进行溢出攻击,x86

c++ - std::string 与 unicode 的奇怪行为

c++11 - 返回统一的初始化引用是否有效?

compiler-construction - 什么是破坏者?

gcc - 支持 RDTSCP 的 gcc cpu 类型是什么?

c++ - linux 上的 clangd 找不到默认 header

c++ - FPU 控制字设置问题

c - 使用内联汇编时使用 DevCpp 编译 C 程序时出错