gcc 5.3 内联汇编器错误?

Pet*_*ker 0 x86 assembly gcc 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;
}
Run Code Online (Sandbox Code Playgroud)

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

lock cmpxchg8b (%esi)
movl %edx, 48(%esp)
movl %eax, 8(%esp)
movl    8(%esp), %eax
movl    12(%esp), %edx
Run Code Online (Sandbox Code Playgroud)

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

有谁知道这个错误的解决方法?或者我是否误解了 gcc 内联汇编的一些基本内容(这不会让我感到惊讶)?

Jes*_*ter 5

o约束意味着可以将一个小整数添加到该地址,并且结果也是有效的内存地址,因此正确的表达式是4+%1。如果生成的地址碰巧使用负偏移量,您的版本可能会意外工作,例如-8(%ebp)在替换后它变成的情况4-8%(ebp)。如果偏移量为正,就像在损坏的情况下一样,8(%esp)它当然会扩展到48(%esp)错误的位置。4+%1在这两种情况下都会正确工作,因为4+-8(%esp)与 一样有效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;
}
Run Code Online (Sandbox Code Playgroud)

另请注意, 和 都ebx没有ecx被修改,因此没有必要将它们列为破坏者,当然没有其他内存被触及,因此memory也可以被删除。

上述所有内容并不是真正必要的,因为 gcc 有原子内置函数,所以整个事情都归结为__atomic_load_n(target, __ATOMIC_SEQ_CST). 编译器也知道这lock cmpxchg8b很慢,并且可以根据目标环境选择更有效的指令。这个内置也更便携。