x86 - 使用内联汇编设置位

Posted

技术标签:

【中文标题】x86 - 使用内联汇编设置位【英文标题】:x86 - setting a bit using inline assembly 【发布时间】:2015-11-24 08:53:29 【问题描述】:

我正在编写一个函数,通过内联汇编使用bts 指令设置数字 x 中的第 n 位。这是我的功能:

uint32_t set_bit_assembly(uint32_t x, uint32_t n)

    asm( "movl %1, %%eax; bts %0, %%eax;"
         :"=&r"(x)
         :"r"(n)
        );
    return x;

我希望变量 'n' 和 'x' 分别成为 movlbts 的第一个操作数。但是,当我编译时,movl 需要'x' 而完全忽略'n'。 (我尝试交换 %0 和 %1,但没有帮助)。你能告诉我哪里出错了吗?下面是生成的汇编代码:

00000043 <set_bit_assembly>:
  43:   55                      push   %ebp
  44:   89 e5                   mov    %esp,%ebp
  46:   83 ec 10                sub    $0x10,%esp
  49:   8b 55 0c                mov    0xc(%ebp),%edx
  4c:   89 d0                   mov    %edx,%eax
  4e:   0f ab c0                bts    %eax,%eax
  51:   89 45 fc                mov    %eax,-0x4(%ebp)
  54:   8b 45 fc                mov    -0x4(%ebp),%eax
  57:   c9                      leave  
  58:   c3                      ret    

【问题讨论】:

您知道| 运算符,对吗? (如果你想要使用内联汇编,我接受,只是指出你没有。) 是的,当然,我知道按位运算符。我只是想在汇编中尝试一些位操作。 只是想确认一下。你不会相信有些人的想法。 ;-) 【参考方案1】:

如何在asm中使用bts指令

在您的代码中,这一行:

bts %0, %%eax;  

应替换为:

bts %%eax, %0;

说明

给定一般形式 asm("code" : output : inputs : clobbers) GCC 将“code”中的 %0、%1 和 %2 替换为在冒号后面保存参数的寄存器。 BTS 的定义说第一个操作数是位串,第二个是位索引。所以解决方案似乎是: bts %0, %1 你已经在你的代码中完成了。然而,这不是 bts 的工作方式:bts 想要地址作为第二个操作数,而要设置的位作为第一个操作数:bts %1, %0。查看正确用法here。

更好的解决方案

虽然您的代码可以使用建议的更正,但还有更好的选择,如下所示:

uint32_t set_bit_assembly2(uint32_t x, uint32_t n)

    asm( "bts %1,%0"
         :"+r"(x)
         :"r"(n)
        );
    return x;

正如@DavidWohlferd 在评论中指出的那样,我们应该使用“+r”,因为 x 将被 bts 指令读写。

此外,可以通过使用 symbolic names 来增加可读性:

asm( "bts %[bit],%[value]"
     : [value] "+rm"(value) 
     : [bit] "r"(bit)
     :"cc");

还有一种可能是 (see this post):

uint32_t set_bit_assembly3(uint32_t x, uint32_t n)

    asm( "bts %1,%0": "+rm"(x) : "r"(n));
    return x;


进一步阅读:

想要使用 bts 的人可能会感兴趣的这个页面:http://lxr.free-electrons.com/source/arch/x86/include/asm/bitops.h#L41

在postPeter Cordes 中解释了为什么内存操作数上的 bts 对性能很不利。

【讨论】:

看看你的第一个例子,不应该是"+&amp;r"吗?甚至"+&amp;rm"?由于这会影响标志,因此您应该添加“cc”clobber。就我自己而言,我发现使用符号名称更清晰。所以更像asm( "bts %[bit],%[value]": [value] "+&amp;rm"(value) : [bit] "r"(argc):"cc"); @balajimc55 给出了一般形式 asm("code" : outputs : inputs : clobbers) GCC 用在冒号后面保存参数的寄存器替换了“code”中的 %0、%1 和 %2。 BTS 的定义说第一个操作数是位串,第二个是位索引。所以解决方案似乎是: bts %0, %1 你已经在你的代码中完成了。然而,这不是 bts 的工作方式:bts 想要地址作为第二个操作数,而要设置的位作为第一个操作数:bts %1, %0。看到这个(lxr.free-electrons.com/source/arch/x86/include/asm/bitops.h)。 哦,另外,bts 可以采用立即操作数,因此位数约束应该是 ri。否则,当内联到使用编译时常量计数的调用者时,gcc 必须将计数单独mov 放入寄存器中。并首先使用正确的约束,而不是离开=&amp;r,然后在下一段中说这是错误的。 我认为对内联汇编的解释仍然存在一些小问题。首先是“&”实际上不是必需的。 “+”本身就足以将寄存器标记为“读/写”。 '&' 表示“早期破坏”,只有在有多个汇编指令时才会出现。这里是安全的,但不会改变编译器的行为。官方解释见gcc.gnu.org/onlinedocs/gcc/Modifiers.html 第二个问题更大。除非您可以保证 n intel.com/content/dam/www/public/us/en/documents/manuals/…。当彼得说“疯狂的 CISC 语义”时,他是认真的!

以上是关于x86 - 使用内联汇编设置位的主要内容,如果未能解决你的问题,请参考以下文章

优化系列汇编优化技术:x86架构内联汇编及demo

x86 内联汇编器标志

x86平台转x64平台关于内联汇编不再支持的解决

64 位应用程序和内联汇编

64 位应用程序和内联汇编

内联汇编