【问题标题】:gcc 5.3 inline assembler bug?gcc 5.3 内联汇编程序错误?
【发布时间】:2016-02-02 15:49:08
【问题描述】:

我有这个 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 可以做到这一点。

有人知道这个错误的解决方法吗?还是我误解了 gcc 的内联 asm 的一些基本内容(这不会让我感到惊讶)?

【问题讨论】:

  • 4%1?这到底是什么意思?你想要4+%1 吗?请注意,o 约束意味着可以在地址中添加一个小整数,结果也是有效的内存地址。
  • 当然,内联汇编的使用不是很好,最好将输出留在edxeax 中并让编译器处理它。 gcc 也有 atomic builtins,所以你可能根本不需要内联 asm。
  • @HubertApplebaum - gcc -S test.cpp,使用 gcc 5.3。
  • 除了 Jester 回答中的优点外,%1 是一个输入。如果你正在写它,它需要是一个输出或输入/输出。
  • @Jester - 谢谢,将 4%1 更改为 4+%1 修复了其他地方的一些问题,尽管不是这个特定的问题。

标签: gcc assembly x86 inline-assembly


【解决方案1】:

o 约束意味着可以在地址上添加一个小整数,结果也是一个有效的内存地址,所以正确的表达式是4+%1 .如果生成的地址碰巧使用负偏移量,例如-8(%ebp),您的版本可能会意外工作,在这种情况下,替换后它会变为4-8%(ebp)。如果偏移量是正数,例如在损坏的情况下 8(%esp) 它当然会扩展到 48(%esp) 这是错误的。 4+%1 在这两种情况下都能正常工作,因为4+-8(%esp)4+8(%esp) 一样有效。这与编译器版本没有直接关系。

也就是说,这个内联 asm 效率不高,如果您将 eaxedx 声明为输出并将其留给编译器处理,则可以避免存储整个业务:

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;
}

还请注意,ebxecx 都没有被修改,因此将它们列为破坏者毫无意义,当然也不会触及其他内存,因此也可以删除 memory

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

【讨论】:

  • 我不清楚如何在不初始化的情况下使用 ecx 和 ebx。是否假设 load() 的调用约定将填充它们?如果它被内联,那会起作用吗?为了以防万一,我很想为他们使用输入。虽然 cmpxchg8b 工作不需要“内存”破坏器,但写入此值可能会触发另一个线程,该线程将开始从当前线程的数据结构中读取值。大多数时候我在想lock我在想“记忆”。所有这些都只是突出了你所说的:使用原子(__atomic_compare_exchange_n?)。
  • 我们不关心ebxecx 中的值。
  • 感谢建议的代码,以及 gcc 内置函数的提及。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-03-08
  • 1970-01-01
  • 2011-05-27
  • 2012-10-20
  • 2013-09-14
  • 1970-01-01
  • 2010-11-24
相关资源
最近更新 更多