gcc 5.3内联汇编程序错误?

时间:2016-02-02 15:49:09

标签: gcc assembly x86 inline-assembly

我有这个32位代码:

unsigned long long load(volatile unsigned long long *target) {
   unsigned long long result;

   __asm__ __volatile__
       (
       "movl %%ecx, %%edx\n\t"
       "movl %%ebx, %%eax\n\t"
       "lock cmpxchg8b %0\n\t"
       "movl %%edx, 4%1\n\t"
       "movl %%eax, %1\n\t"
       : "+m" (*target)
       : "o" (result)
       : "eax", "ebx", "ecx", "edx", "memory", "cc"
       );
   return result;
}

当使用gcc版本5.3编译时,代码的尾端会生成此汇编代码(为清晰起见,稍作编辑):

lock cmpxchg8b (%esi)
movl %edx, 48(%esp)
movl %eax, 8(%esp)
movl    8(%esp), %eax
movl    12(%esp), %edx

调用cmpxchg8b的结果是在EDX:EAX中。生成的代码将EDX存储在48(%esp),从12(%esp)重新加载EDX,因此返回的值是无意义的。其他版本的gcc也是如此。

有没有人知道这个bug的解决方法?或者我误解了关于gcc的内联asm的基本信息(这不会让我感到惊讶)?

1 个答案:

答案 0 :(得分:4)

o约束意味着一个小整数可以添加到该地址,结果也是一个有效的内存地址,所以正确的表达式为{ {1}}。如果生成的地址恰好使用4+%1之类的负偏移量,那么您的版本可能会意外运行,在这种情况下,替换后它将变为-8(%ebp)。如果偏移是正的,就像在破碎的情况4-8%(ebp)中一样,它当然会扩展为8(%esp),这是错误的。 48(%esp)在两种情况下均可正常使用,因为4+%14+-8(%esp)一样有效。这与编译器版本没有直接关系。

也就是说,这个内联asm效率不高,如果你只是将它们声明为输出并将它留给编译器来处理,那么可以避免存储4+8(%esp)eax的整个业务。它:

edx

另请注意,unsigned long long load(volatile unsigned long long *target) { unsigned long long result; __asm__ __volatile__ ( "movl %%ecx, %%edx\n\t" "movl %%ebx, %%eax\n\t" "lock cmpxchg8b %0\n\t" : "+m" (*target), "=&A" (result) : : "cc" ); return result; } ebx都没有被修改,所以没有必要将它们列为clobbers,当然也没有触及其他内存,因此ecx也可以删除。

以上所有都不是必需的,因为gcc有原子内置,所以整个事情只归结为memory。编译器还知道__atomic_load_n(target, __ATOMIC_SEQ_CST)很慢,并且可以根据目标环境选择更有效的指令。这种内置也更便携。