如何使用 Clang 11、intel 语法和替换变量进行内联汇编
How to inline-assembly with Clang 11, intel syntax and substitution variables
我费了很大的劲才让它生效:
我试过以下方法:
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
作为输出,这证明了替换以某种方式起作用。
我不知道有什么好的方法可以做到这一点,我推荐 GNU C 内联 asm 的 AT&T 语法(或方言替代 add {%1,%0 | %0,%1}
所以它对 GCC 有两种方式。)像 -masm=intel
不要像 GCC 那样用 clang 替换裸寄存器名称。
How to generate assembly code with clang in Intel syntax? 是关于用于 -S
output 的语法,与 GCC 不同的是,它与编译器的 inline-asm 输入语法无关. --x86-asm-syntax=intel
的行为没有 改变:它仍然以 Intel 语法输出,并且对内联汇编没有帮助。
您可以滥用%V0
或%V[i]
(而不是%0
或%[i]
)在template 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"
结束您的模板以切换回 assembler到 AT&T 模式到 assemble 后来编译器生成的 asm。 (如果你想让你的代码与 GCC 一起工作,则需要!clang 的内置 assembler 在汇编之前不会将你的内联 asm 文本合并到一个大的 asm 文本文件中,它直接进入编译器生成指令的机器代码.)
显然告诉编译器它可以使用 "=r"
选择任何寄存器,然后实际使用您自己的硬编码选择,当编译器选择不同时会产生未定义的行为。您将踩到编译器的脚趾并损坏它稍后要使用的值,并让它从错误的寄存器中获取垃圾作为输出。 IDK 为什么你费心把它包括在你的问题中;出于同样相当明显的原因,这将以与 AT&T 语法完全相同的方式中断。
我费了很大的劲才让它生效:
我试过以下方法:
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
作为输出,这证明了替换以某种方式起作用。
我不知道有什么好的方法可以做到这一点,我推荐 GNU C 内联 asm 的 AT&T 语法(或方言替代 add {%1,%0 | %0,%1}
所以它对 GCC 有两种方式。)像 -masm=intel
不要像 GCC 那样用 clang 替换裸寄存器名称。
How to generate assembly code with clang in Intel syntax? 是关于用于 -S
output 的语法,与 GCC 不同的是,它与编译器的 inline-asm 输入语法无关. --x86-asm-syntax=intel
的行为没有 改变:它仍然以 Intel 语法输出,并且对内联汇编没有帮助。
您可以滥用%V0
或%V[i]
(而不是%0
或%[i]
)在template 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"
结束您的模板以切换回 assembler到 AT&T 模式到 assemble 后来编译器生成的 asm。 (如果你想让你的代码与 GCC 一起工作,则需要!clang 的内置 assembler 在汇编之前不会将你的内联 asm 文本合并到一个大的 asm 文本文件中,它直接进入编译器生成指令的机器代码.)
显然告诉编译器它可以使用 "=r"
选择任何寄存器,然后实际使用您自己的硬编码选择,当编译器选择不同时会产生未定义的行为。您将踩到编译器的脚趾并损坏它稍后要使用的值,并让它从错误的寄存器中获取垃圾作为输出。 IDK 为什么你费心把它包括在你的问题中;出于同样相当明显的原因,这将以与 AT&T 语法完全相同的方式中断。