首页 > 解决方案 > 有没有办法在 linux 平台上编译微软风格的内联汇编代码?

问题描述

正如标题中提到的,我想知道有没有办法在 linux 操作系统(例如 ubuntu)中编译微软风格的内联汇编代码(如下所示)。

_asm{
    mov edi, A;
    ....
    EMMS;
}

示例代码是内联汇编代码的一部分,可以使用 cl.exe 编译器在 win10 上成功编译。有没有办法在linux上编译它?我是否必须以 GNU c/c++ 风格重写它(即 __asm__{;;;})?

标签: linuxgccvisual-c++inline-assembly

解决方案


有没有办法在 linux 平台上编译微软风格的内联汇编代码?

对的,这是可能的。有点儿。

对于 GCC,您必须同时使用 Intel 和 AT&T 语法。由于问题 24232,它不适用于 Clang,内联汇编操作数不适用于 .intel_syntax和问题 39895,错误:使用内联 asm 的表达式中的未知标记

这是模式。汇编器模板使用.intel_syntax. 然后,在您的 asm 模板结束时,您切换回.att_syntax模式,以便它处于编译器生成的其余 asm 的正确模式。

#include <cstddef>
int main(int argc, char* argv[])
{
    size_t ret = 1, N = 0;
    asm __volatile__
    (
        ".intel_syntax   noprefix ;\n"
        "xor esi, esi    ;\n"           // zero RSI
        "neg %1          ;\n"           // %1 is replaced with the operand location chosen by the compiler, in this case RCX
        "inc %1          ;\n"
        "push %1         ;\n"           // UNSAFE: steps on the red-zone
        "pop rax         ;\n"
        ".att_syntax     prefix ;\n"
        : "=a" (ret)      // output-only operand in RAX
          "+c" (N)        // read-write operand in RCX
        :                 // no read-only inputs
        : "%rsi"          // RSI is clobbered: input and output register constraints can't pick it
    );
    return (int)ret;
}

如果您使用任何内存操作数,这将不起作用,因为例如,编译器会将 AT&T 语法替换4(%rsp)为模板而不是[rsp + 4].

这也仅在您使用gcc -masm=intel. 否则,当 GCC 发出 Intel 语法时,您会将汇编器置于 AT&T 模式。因此,使用.intel_syntax noprefix会破坏您在 GCC 中使用任一语法的能力。


mov edi, A;

我帮助的代码不像你展示的那样在汇编器中使用变量。我不知道它与 Intel 风格的 ASM 配合得如何(很差?)。我知道不支持 MASM 样式语法。

您可以使用asmSymbolicNames. 有关详细信息,请参阅 GCC扩展 ASM HowTo

但是,要转换为 GCC 可以使用的东西,您只需要使用位置参数:

__asm__ __volatile__
(
    ".intel_syntax   noprefix ;\n"
    "mov edi, %0     \n";            // inefficient: use a "D" constraint instead of a mov
    ...
    ".att_syntax     prefix ;\n"
    : : "r" (A) : "%edi"
);

或者更好的是,首先使用"D"约束来询问 EDI / RDI 中的变量。如果 GNU C 内联 asm 语句曾经以 开头或结尾mov,那通常表明您做错了。


关于asmSymbolicNames,以下是 GCC Extended ASM HowTo对它们的评价:

此代码没有使用可选的 asmSymbolicName。因此它将第一个输出操作数引用为 %0(如果有第二个,它将是 %1 等)。第一个输入操作数的编号比最后一个输出操作数的编号大一个。在这个 i386 示例中,这使得 Mask 被引用为 %1:

uint32_t Mask = 1234;
uint32_t Index;

  asm ("bsfl %1, %0"
     : "=r" (Index)
     : "r" (Mask)
     : "cc");

该代码覆盖变量索引 ('='),将值放入寄存器 ('r')。使用通用 'r' 约束而不是特定寄存器的约束允许编译器选择要使用的寄存器,这可以产生更高效的代码。如果汇编指令需要特定的寄存器,这可能是不可能的。

以下 i386 示例使用 asmSymbolicName 语法。它产生与上面的代码相同的结果,但有些人可能认为它更具可读性或更易于维护,因为在添加或删除操作数时不需要重新排序索引号。名称 aIndex 和 aMask 仅在此示例中用于强调在何处使用哪些名称。重复使用名称 Index 和 Mask 是可以接受的。

uint32_t Mask = 1234;
uint32_t Index;

  asm ("bsfl %[aMask], %[aIndex]"
     : [aIndex] "=r" (Index)
     : [aMask] "r" (Mask)
     : "cc");

示例代码是内联汇编代码的一部分,可以使用 cl.exe 编译器在 win10 上成功编译...

回到 10,000 英尺,如果您正在寻找易于使用的东西来集成内联 ASM,例如在 Microsoft 环境中,那么您在 Linux 上没有它。GCC 内联 ASM 绝对烂。GCC 内联汇编器是一个古老的、难以使用的工具,我鄙视与之交互。

(而且您还没有经历过带有虚假线路信息的难以理解的错误消息)。


推荐阅读