内联 AT&T asm 语法,用于直接使用操作码而不是助记符



由于我无法进入的不幸原因,我必须支持一个古老的汇编程序,它没有我需要的助记符的映射。

我知道硬件支持它,但我似乎无法在网上找到任何有关如何使用操作码而不是助记符的文档。

有没有人参考如何在 GCC 上的内联 AT&T 语法中做到这一点。

试试这个:

long result;
char success = 0; /* make sure we don't get surprised by setc only writing 8 bits */
/* "rdrand %%rax ; setc %b1" */
asm volatile (".byte 0x48, 0x0f, 0xc7, 0xf0; setc %b1" : "=a"(result), "=qm"(success) :: "cc");

a约束强制编译器使用rax寄存器进行result。 这是尽可能普遍的,而不会令人讨厌。 我建议您添加一个配置测试来检查汇编程序是否理解rdrand并使用以下代码:

long result;
char success = 0;
#ifdef HAVE_RDRAND
asm volatile ("rdrand %0; setc %b1" : "=r"(result), "=qm"(success) :: "cc");
#else
asm volatile (".byte 0x48, 0x0f, 0xc7, 0xf0; setc %b1" : "=a"(result), "=qm"(success) :: "cc");
#endif

虽然如果汇编程序不理解rdrand,强制编译器使用rax寄存器可能会有很小的性能损失,但允许使用任何寄存器所需的复杂障碍远远超过了它。

幸运的是,rdrand只接受一个参数,那就是寄存器。因此,如果您想允许编译器自由选择,则只需要涵盖几种情况。当心,它仍然很丑:)

inline int rdrand()
{
int result;
__asm__ __volatile__ (
".byte 0x0f, 0xc7nt"
".ifc %0, %%eaxnt"
".byte 0xf0nt"
".elsent"
".ifc %0, %%ebxnt"
".byte 0xf3nt"
".elsent"
".ifc %0, %%ecxnt"
".byte 0xf1nt"
".elsent"
".ifc %0, %%edxnt"
".byte 0xf2nt"
".elsent"
".ifc %0, %%esint"
".byte 0xf6nt"
".elsent"
".ifc %0, %%edint"
".byte 0xf7nt"
".elsent"
".ifc %0, %%ebpnt"
".byte 0xf5nt"
".elsent"
".error "uknown register"nt"
".endifnt"
".endifnt"
".endifnt"
".endifnt"
".endifnt"
".endifnt"
".endifnt"
: "=R" (result) : : "cc");
// "=R" excludes r8d..r15d in 64-bit mode
return result;
}

对于 64 位操作数大小,您需要 REX。W (0x48( 前缀,但"=R"约束而不是"=r"将避免需要在 REX 前缀中设置任何其他位。

请注意,rdrand还使用携带标志,作为读者的练习留给读者处理。 GCC6 可以使用标志输出操作数,这比setcc更有效。

从 binutils 2.41 开始,一个新的指令.insn允许生成任意指令。 虽然这可能对您和您的旧汇编程序没有特别帮助,但对于将来遇到此问题的读者可能会很有用。 对于rdrand的例子,这可能看起来像这样:

#define rdrand(x) asm (".insn 0x0fc7/6, %0" : "=r"(x))

.insn指令根据给定的操作数自动确定要使用的编码和指令大小,因此我们可以对所有操作数大小使用上述宏,即使存在内存操作数(尽管对于rdrand当然不允许这些(。例如

long long example(void)
{
char foo;
short bar;
int baz;
long long quux;
rdrand(foo);
rdrand(bar);
rdrand(baz);
rdrand(quux);
return (foo + bar + baz + quux);
}

成为

0000000000000000 <example>:
0:   0f c7 f0                rdrand %eax
3:   66 0f c7 f2             rdrand %dx
7:   0f be c0                movsbl %al,%eax
a:   0f bf d2                movswl %dx,%edx
d:   01 d0                   add    %edx,%eax
f:   0f c7 f6                rdrand %esi
12:   01 f0                   add    %esi,%eax
14:   48 98                   cltq
16:   48 0f c7 f1             rdrand %rcx
1a:   48 01 c8                add    %rcx,%rax
1d:   c3  
ret

您可以看到.insn如何无法区分 8 位和 32 位操作数,因为 x86 指令编码实现了具有单独操作码而不是前缀的 8 位指令。 这可能会导致无效结果。 如果需要,可以添加断言以避免这种情况:

#define rdrand(x) do { 
_Static_assert(sizeof (x) > sizeof (char)); 
asm (".insn 0x0fc7/6, %0" : "=r"(x)); 
} while (0)

最新更新