【问题标题】:x86_64 gcc inline assembly constraints for rax, rbx,rax、rbx 的 x86_64 gcc 内联汇编约束,
【发布时间】:2012-11-12 14:20:26
【问题描述】:

如下:

#include <string.h>

struct cpuidOut
{
   long a ;
   long b ;
   long c ;
   long d ;
} ;

void callcpuid( cpuidOut * p, long a )
{
   memset( p, 0xFF, sizeof(*p) ) ;
   p->a = a ;

   __asm__ ( "cpuid"
             : "+a"(p->a), "=b"(p->b), "=c"(p->c), "=d"(p->d)  // output
             :                                                 // no (only) inputs
             : "a", "b", "c", "d"                              // clobbered registers
           ) ;
}

我得到一个编译错误:

t.C:22: error: unknown register name 'd' in 'asm'
t.C:22: error: unknown register name 'c' in 'asm'
t.C:22: error: unknown register name 'b' in 'asm'
t.C:22: error: unknown register name 'a' in 'asm'

(来自 g++ 或 clang++ 的相同类型的错误)

这让我很吃惊,因为我在 gcc 文档的 i386 clobbers 中看到了 a、b、c、d 列

http://gcc.gnu.org/onlinedocs/gcc/Machine-Constraints.html#Machine-Constraints

我可以通过显式来修复clobber约束:

"%rax", "%rbx", "%rcx", "%rdx"                  // clobbered registers

但我很惊讶我必须这样做?我只需要它在 x86_64 上工作,但我认为“a”、“b”、“c”、“d”样式约束会更好,以防以后在 i386 上也需要代码。

编辑:我最初发布了错误的 asm,经过几次调整以使其正常工作,并在此过程中搞混了。上面的 asm 与我最初的问题一致,但是会导致编译器无法调度 A 寄存器的编译错误。这似乎可行:

void callcpuid( cpuidOut * p, long a, long b )
{
   __asm__ ( "cpuid"
             : "=a"(p->a), "=b"(p->b), "=c"(p->c), "=d"(p->d)  // output
             : "0"(a), "1"(b)                                  // inputs
           ) ;
}

但请注意,它完全避免了任何 clobber 约束的要求,因为所有 clobber 都是输出。这可能是正确的方法,尽管在阅读 gcc 文档后我仍然感到惊讶,我不能在 clobber 约束中使用通用的 reg 名称“a”、“b”、“c”、“d”,而不是必须使用 "%eax", "%rax", ...

【问题讨论】:

  • 根据 PIC 的使用和内存模型,您可能需要保存 EBX/RBX,因为根据 ABI 规范,调用者拥有它。 "=b"(p-&gt;b) 破坏 EBX/RBX
  • 如果我将 rbx 列为输出,编译器是否不必为 -fpic 本身进行溢出(而不是我必须明确地这样做)?

标签: gcc clang inline-assembly


【解决方案1】:

这让我很吃惊,因为我在 gcc 文档的 i386 clobbers 中看到了 a、b、c、d 列

破坏者不是约束。

当您告诉 GCC 为 insn 操作数分配寄存器时使用了约束,这些约束定义了可接受的寄存器类,从中可以绘制寄存器。

另一方面,在输入/输出约束不明显的情况下,clobbers 会告诉 GCC 有关由 insns 修改的寄存器,例如修改固定寄存器的 insn,即不是它的操作数之一,或者当您在内联程序集中使用硬编码的寄存器名称时。

这是必需的,因此在执行内联 asm 之前,GCC 可以隐藏值,它恰好保存在被破坏的寄存器中。

附言。对于输入输出操作数,您可以使用"+" 修饰符:

void callcpuid( cpuidOut * p, long a, long b )
{
   __asm__ ( "cpuid" : "+a"(p->a), "+b"(p->b), "=c"(p->c), "=d"(p->d)) ;
}

附言。生成的 32 位代码:

movl    (%esi), %eax  ; load p->a
movl    4(%esi), %ebx ; load p->b
cpuid
movl    %ebx, 4(%esi)  ; write back into p->b
movl    (%esp), %ebx   
movl    %eax, (%esi)   ; write back into p->a
movl    %ecx, 8(%esi)  ; write p->c
movl    %edx, 12(%esi) ; write p->d

生成的 64 位代码:

movq    (%rdi), %rax   ; load p->a
movq    8(%rdi), %rbx  ; load p->b
cpuid
movq    %rbx, 8(%rdi)  ; write back p->b
movq    %rax, (%rdi)   ; write back p->a
movq    %rcx, 16(%rdi) ; write p->c
movq    %rdx, 24(%rdi) ; write p->d

【讨论】:

  • 谢谢。我总是称它们为clobber约束,所以看起来我只是被命名法弄糊涂了。附言。您的带有 + 的示例无法完成这项工作,因为它没有将 ab 参数分别放入 p-&gt;ap-&gt;b 中。
  • 哦,它确实有效,我已经用过很多次了,事实上我也在这个例子中检查过它。稍后检查编辑。
  • 我要说的是,在我的示例中,ab 两个参数是输入。您有一个带有一对未使用参数的不同界面。如果您删除了这两个参数并要求将值放入结构中,那么您的代码将完成这项工作(这不是一个坏方法)。
  • 小心调用callcpuid。根据内存模型和 PIC 的使用,EBX/RBX 可能需要保留。然后,当您尝试保留EBX/RBX 时,您会遇到像What ensures reads/writes of operands occurs at desired times with extended ASM? 这样的善意
【解决方案2】:

如果您可以等待下一个 GCC 4.8 版本,或者如果您可以使用 GCC 的最新快照(即从 svn 源编译主干),请考虑使用新的builtins__builtin_cpu_is__builtin_cpu_supports

否则,请执行您的建议,例如明确使用clobber约束。

注意:您所指的 link 也适用于未来的 GCC 4.8,而不适用于 4.7 或更早版本

【讨论】:

  • 好建议。在这种情况下,我的最终目标编译器实际上是 clang(它实现了 gcc 样式 asm)......只是测试编译有问题的代码。
猜你喜欢
  • 2011-04-23
  • 2015-12-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-14
  • 2012-10-20
  • 2013-04-06
相关资源
最近更新 更多