Cygwin:使用 asm 标签编译 cpp 文件

Posted

技术标签:

【中文标题】Cygwin:使用 asm 标签编译 cpp 文件【英文标题】:Cygwin: Compile cpp file with asm tag 【发布时间】:2016-12-18 10:33:42 【问题描述】:

我是汇编新手,目前正在尝试使用 asm 标签创建 c++ 代码。我正在使用cygwin进行编译。这是我的代码:

#include <iostream>
using namespace std;

int main()  
  
    float flp1_num, flp2_num, flp_rslt1;

    cin >>flp1_num >>flp2_num;

    __asm
    
        FLD flp1_num
        FLDPI
        FADD flp2_num
        FST flp_rslt1
    

    cout << flp_rslt1;
  

使用的语法来自here。

我正在使用 g++ arq.cpp -o arq.exe 进行编译,这给了我错误提示:

arq.cpp: In function ‘int main()’:
arq.cpp:13:5: error: expected ‘(’ before ‘’ token
     
     ^
arq.cpp:14:9: error: ‘FLD’ was not declared in this scope
         FLD flp1_num
         ^

然后我尝试将__asm 更改为__asm(),它给了我不同的错误:

arq.cpp: In function ‘int main()’:
arq.cpp:14:9: error: expected string-literal before ‘FLD’
         FLD flp1_num

我四处搜索,发现很少有替代方案可能有效,但它们对我不起作用。例如__asm__("fld flp1_num");asm("fld flp1_num"); 都给我错误提示/tmp/cccDDfUP.o:arq.cpp:(.text+0x32): undefined reference to flp1_num

如何解决这个错误?

【问题讨论】:

如果您使用的是 gcc,为什么要阅读 Microsoft 编译器文档?内联汇编的所有内容都是特定于实现的。 所以没有办法使用cygwin和编译内联汇编? 您需要查看 gcc 文档,而不是 Microsoft 文档。 【参考方案1】:

正如其他人所说,您正在查看 Microsoft 的编译器文档,该编译器的内联汇编形式与 GCC 使用的汇编形式截然不同。事实上,it is a substantially less powerful form 在很多方面,虽然它确实具有更容易学习使用的优点。

您需要查阅有关 Gnu 内联汇编语法的文档,该文档可在 here 获得。更温和的介绍,有一个很好的教程here,我特别喜欢David Wohlferd 的回答here。尽管这是一个不相关的问题,但如果您只是按照他的解释进行操作,他就会很好地介绍内联汇编的基础知识。

无论如何,针对您的具体问题。几个迫在眉睫的问题:

    代码很可能不会像您认为的那样做。您的代码实际上所做的是将 pi 添加到flp2_num,然后将结果放入flp_rslt1。它对flp1_num 没有任何作用。

    如果我不得不猜测,我会假设您想将flp1_num、pi 和flp2_num 加在一起,然后在flp_rslt1 中返回结果。 (但也许不是;这不是很清楚,因为您没有任何 cmets 说明您的意图,也没有描述性的函数名称。)

    您的代码也损坏,因为它没有正确清理浮点堆栈。您有两个“加载”指令,但没有弹出指令!您推送/加载到浮点堆栈的所有内容都必须弹出/卸载,否则您会导致浮点堆栈不平衡,这会导致重大问题。

因此,在 MSVC 语法中,您的代码应该如下所示(为了方便和清晰,将其包装成一个函数):

float SumPlusPi(float flp1_num, float flp2_num)

    float flp_rslt1;
    __asm
    
       fldpi                       ; load the constant PI onto the top of the FP stack
       fadd  DWORD PTR [flp2_num]  ; add flp2_num to PI, and leave the result on the top of the stack
       fadd  DWORD PTR [flp1_num]  ; add flp1_num to the top of the stack, again leaving the result there
       fstp  DWORD PTR [flp_rslt1] ; pop the top of the stack into flp_rslt1
    
    return flp_rslt1;

我只推了一次(fldpi),所以我只弹出了一次(fstp)。对于添加,我使用了fadd 的形式,它适用于内存操作数;这会导致该值被隐式加载到堆栈上,但在其他方面似乎作为单个指令执行。然而,你可以用许多不同的方式来写这个。重要的是要平衡推送次数和弹出次数。有些指令显式弹出 (fstp),还有其他指令执行操作然后弹出 (例如faddp)。不同的指令组合,按照特定的顺序,很可能比其他指令更优化,但我上面的代码确实有效。

下面是翻译成 GAS 语法的等效代码:

float SumPlusPi(float flp1_num, float flp2_num)

    float flp_rslt1;
    __asm__("fldpi        \n\t"
            "faddl %[two] \n\t"
            "faddl %[one]"
           : [result] "=t" (flp_rslt1)   // tell compiler result is left at the top of the floating-point stack,
                                         //  making an explicit pop unnecessary
           : [one]    "m" (flp1_num),    // input operand from memory (inefficient)
             [two]    "m" (flp2_num));   // input operand from memory (inefficient)
    return flp_rslt1;

虽然这可行,但它也不是最理想的,因为它没有利用 GAS 内联汇编语法的高级功能,特别是使用已经加载到浮点堆栈中的值作为输入的能力。

不过,最重要的是,不要错过the reasons why you should not use inline assembly(也是 David Wohlferd 的)!这是内联汇编的真正毫无意义的用法。 编译器会生成更好的代码,并且您需要显着减少工作因此,最好像这样编写上述函数:

#include <cmath>    // for M_PI constant
float SumPlusPi(float flp1_num, float flp2_num)

    return (flp1_num + flp2_num + static_cast<float>(M_PI));

请注意,如果您确实想要实现与我之前假设不同的逻辑,那么更改此代码以执行您想要的操作是微不足道的。

如果您不相信我生成的代码与您的内联汇编一样好——如果不是更好——这里是 GCC 6.2 为上述函数生成的确切目标代码 ( Clang 发出相同的代码):

fld     DWORD PTR [flp2_num]  ; load flp2_num onto top of FPU stack
fadd    DWORD PTR [flp1_num]  ; add flp1_num to value at top of FPU stack
fadd    DWORD PTR [M_PI]      ; add constant M_PI to value at top of FPU stack
ret                           ; return, with result at top of FPU stack

使用fldpi 与像 GCC 一样从常量加载值并没有速度上的优势。如果有的话,强制使用该指令实际上是一种悲观,因为这意味着您的代码永远无法利用 SSE/SSE2 指令来比旧的 x87 更有效地处理浮点值 FPU。为上述 C 代码启用 SSE/SSE2 就像抛出编译器开关(或指定支持它的目标架构,这将隐式启用它)一样简单。这将为您提供以下信息:

sub       esp, 4                      ; reserve space on the stack    
movss     xmm0, DWORD PTR [M_PI]      ; load M_PI constant
addss     xmm0, DWORD PTR [flp2_num]  ; add flp2_num
addss     xmm0, DWORD PTR [flp1_num]  ; add flp1_num
movss     DWORD PTR [esp], xmm0       ; store result in temporary space on stack
fld       DWORD PTR [esp]             ; load result from stack to top of FPU stack
add       esp, 4                      ; clean up stack space
ret                                   ; return, with result at top of FPU stack

【讨论】:

您的回答非常有帮助。非常感谢!请再问一个问题,是否有像emu8086这样的模拟器,但适用于32位处理器?我的意思是模拟器会使用 32 位版本的寄存器,并且会有工具在运行代码时逐步检查寄存器? @rasty 我不知道任何带有内置调试器的模拟器,但它可能存在,我只是没看过。 Visual Studio 内置了这个,这就是我使用的。如果您使用的是 Cygwin,那么您有 GDB,它也是一个出色的调试器,无论是用于 C 还是汇编。 x86 tag wiki info 页面底部有调试器的链接和信息。 @Rasty:BOCHS 有一个内置调试器,但它模拟整个 PC 并且没有运行单个用户空间二进制文件的模式。 Qemu 确实有一个模式,可以作为一个 gdb 服务器,让你连接到本地运行的 gdb。 IDK 如果它可以在 cygwin 下运行。我wrote a guide to using qemu to debug an ARM binary on an x86 desktop,但是如果您由于某种原因不能在 x86 主机上运行 x86 二进制文件并以正常方式使用调试器单步执行它们(例如 gdb ./a.out,cygwin 可以做)【参考方案2】:

查看此文档:How to embed assembler code in gcc

在使用 gcc/g++ 时需要以不同的方式嵌入汇编代码:

int src = 1;
int dst;    
asm ("mov %1, %0\n\t"
     "add $1, %0"
      : "=r" (dst)
      : "r" (src));

而cygwin是gcc的windows端口。

【讨论】:

以上是关于Cygwin:使用 asm 标签编译 cpp 文件的主要内容,如果未能解决你的问题,请参考以下文章

将 EXE 反编译为 ASM

vs2015编译纯ASM文件

cocos2dx C++如何使用cygwin去编译cocos2dx项目中的C++文件

使用 libtool 和 autoconf 构建 ASM 代码

内联asm:'out'的操作数类型不匹配

win7下使用cygwin编译VLC