迹忆客 专注技术分享

当前位置:主页 > 学无止境 > 编程语言 > C++ >

将 C++ 转换为 ARM 程序集

作者:迹忆客 最近更新:2023/03/31 浏览次数:

将 C++ 与 ARM 汇编接口以多种方式为程序员服务,这也是一个简单的过程,可帮助 C++ 访问以汇编语言定义的各种函数和变量,反之亦然。 本教程将教您如何将 C++ 代码或函数转换为 ARM 汇编。

程序员可以使用单独的汇编代码模块将它们与 C++ 编译的模块链接,以使用嵌入在 C++ 中的汇编变量和内联汇编或修改编译器生成的汇编代码。

最重要的是,您必须保留由函数修改的任何专用寄存器,启用中断例程以保存所有寄存器,确保函数根据其 C++ 声明正确返回值,没有使用 .cinit 部分的汇编模块,使编译器能够分配链接名称 到所有外部对象,并在将 C++ 转换为 ARM 程序集之前,使用程序集修饰符中从 C++ 访问或调用的 .def.global 指令声明每个对象和函数。

在 C++ 文件中定义使用 C 的汇编语言调用的函数(原型为 extern C 的函数)。 在 .bss 部分定义变量或为它们分配一个链接器符号,以便稍后识别哪个需要转换。


使用 GCC 编译器将 C++ 转换为 ARM 程序集

gcc 是在执行期间从 C++ 代码获取中间输出的重要来源。 它是使用 -S 选项获取汇编程序输出的功能。

-S 选项用于在将代码发送到汇编程序之前编译代码之后的输出。

它的语法是 gcc –S your_program.cpp ,你只要声明这个命令就可以编写一个简单的C++程序转换成 ARM 汇编。 除了是最简单的方法之一,它的输出也很复杂且难以理解,即使对于中级程序员也是如此。

GNN.cpp 文件:

#include <iostream>
using namespace std;
main() {
   int i, u, div;
   i = 2;
   u = 10;
   div = i / u;
   cout << "Answer: " << div << endl;
}

在 Microsoft Windows 中的 GCC 上运行此命令:

gcc –S GNN.cpp

输出结果:

gcc compiler

可以使用一系列 ASM 语句或单个 ASM 语句将单行汇编代码插入到编译器创建的 C++ 程序中的汇编文件中。 这些汇编语句将连续的代码行(汇编代码)放入编译器(C++ 编译器输出),没有中间代码(没有任何代码中断)。

但是,始终保持 C++ 环境,因为编译器不会检查/分析插入的指令。 始终避免将标签或 ump 插入到 C++ 代码中,因为它们可能会产生不可预测的结果并混淆代码生成的寄存器跟踪算法。

此外,ASM 语句不是插入汇编器指令的有效选择,您可以使用 symdebug:dwarf 命令或 -g 命令而不更改汇编环境并避免在 C++ 代码中创建汇编宏,因为 C++ 环境调试信息。


创建 MOD(汇编时模数)函数以将 C++ 转换为 ARM 汇编

由于 ARM Assembly 缺少 MOD 命令,您可以使用 subs 创建 MOD 函数并轻松地将 C++ 转换为 ARM Assembly。 你需要通过 ldr reg, =var 加载变量的内存地址,如果你想加载变量,它需要用那个 reg 做另一个 ldr,比如 ldr r0, =carry ldr r0, [r0] 来加载 值存储在 r0 中的内存地址。

使用 sdiv 是因为它比减法循环快得多,除了最小输入,其中循环只运行一次或两次。

概念:

;Precondition: R0 % R1 is the required computation
;Postcondition: R0 has the result of R0 % R1
              : R2 has R0 / R1

; Example comments for 10 % 7
UDIV R2, R0, R1      ; 1 <- 10 / 7       ; R2 <- R0 / R1
MLS  R0, R1, R2, R0  ; 3 <- 10 - (7 * 1) ; R0 <- R0 - (R1 * R2 )
#include <iostream>
using namespace std;
main() {
    int R0, R1, R2;
    R1 = 7;
    R2 = 1;
    R0 = 10;
    int Sol1, Sol2;
    Sol1 = R2 <- R0 / R1;
    Sol2 = R0 <- R0 - (R1 * R2);

    cout<<Sol1<<endl;
    cout<<Sol2;
}

输出:

mod


使用 arm-linux-gnueabi-gcc 命令将 C++ 转换为 ARM 程序集

arm-linux-gnueabi-gcc 命令是将 C++ 转换为适用于 x86 和 x64 机器的 ARM 程序集的完美方式。 由于 gcc 没有可用的 ARM 目标,您不能将它用于一般系统,但前提是您在 ARM 系统上,您可以使用常规 gcc 代替。

完整的命令 arm-linux-gnueabi-gcc -S -O2 -march=armv8-a GNN.cpp 非常强大,其中 -S 代表输出程序集并告诉 gcc,**-02** 是代码优化器并减少调试混乱 结果。 -02 是可选的; 另一方面,**-march=armv8-a** 是强制性的,告诉它在编译时使用 ARM v8 目标。

您可以在编译时使用不同版本的 ARM v8 更改 ARM 目标,包括: armv8-a、armv8.1-a 到 armv8.6-a、armv8-m.base、armv8-m.main 和 armv8.1-m.main 每个都略有不同,您可以深入执行 分析并选择最适合您需求的那个。

命令中的 power.c 告诉要编译哪个文件,如果您没有指定输出文件,如 -o output.asm,程序集将输出到类似文件名 power.s。

arm-linux-gnueabi-gcc 是在使用常规 gcc 提供目标或输出程序集的 arm 机器上进行编译的绝佳替代方案。

gcc 允许程序员使用 -march=xxx 指定目标体系结构,并且您必须知道识别您机器的 apt 包以选择正确的包。

GNN.cpp 文件:

#include <iostream>
using namespace std;

int power(int x, int y)
{
    if (x == 0){
        return 0;
    }
    else if (y < 0){
        return 0;
    }
    else if (y == 0){
        return 1;
    }
    else {
        return x * power(x, y - 1);
    }
}

main() {
    int x, y, sum;
    x = 2; y = 10;
    sum = power(x, y);
    cout<<sum;
}

运行下面的命令

arm-linux-gnueabi-gcc -S -O2 -march=armv8-a GNN.cpp

输出结果:

power(int, int):
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     DWORD PTR [rbp-4], edi
        mov     DWORD PTR [rbp-8], esi
        cmp     DWORD PTR [rbp-4], 0
        jne     .L2
        mov     eax, 0
        jmp     .L3
.L2:
        cmp     DWORD PTR [rbp-8], 0
        jns     .L4
        mov     eax, 0
        jmp     .L3
.L4:
        cmp     DWORD PTR [rbp-8], 0
        jne     .L5
        mov     eax, 1
        jmp     .L3
.L5:
        mov     eax, DWORD PTR [rbp-8]
        lea     edx, [rax-1]
        mov     eax, DWORD PTR [rbp-4]
        mov     esi, edx
        mov     edi, eax
        call    power(int, int)
        imul    eax, DWORD PTR [rbp-4]
.L3:
        leave
        ret
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     DWORD PTR [rbp-4], 2
        mov     DWORD PTR [rbp-8], 10
        mov     edx, DWORD PTR [rbp-8]
        mov     eax, DWORD PTR [rbp-4]
        mov     esi, edx
        mov     edi, eax
        call    power(int, int)
        mov     DWORD PTR [rbp-12], eax
        mov     eax, DWORD PTR [rbp-12]
        mov     esi, eax
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
        mov     eax, 0
        leave
        ret
__static_initialization_and_destruction_0(int, int):
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     DWORD PTR [rbp-4], edi
        mov     DWORD PTR [rbp-8], esi
        cmp     DWORD PTR [rbp-4], 1
        jne     .L10
        cmp     DWORD PTR [rbp-8], 65535
        jne     .L10
        mov     edi, OFFSET FLAT:_ZStL8__ioinit
        call    std::ios_base::Init::Init() [complete object constructor]
        mov     edx, OFFSET FLAT:__dso_handle
        mov     esi, OFFSET FLAT:_ZStL8__ioinit
        mov     edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
        call    __cxa_atexit
.L10:
        nop
        leave
        ret
_GLOBAL__sub_I_power(int, int):
        push    rbp
        mov     rbp, rsp
        mov     esi, 65535
        mov     edi, 1
        call    __static_initialization_and_destruction_0(int, int)
        pop     rbp
        ret

或者,您可以通过运行 module load arm<major-version>/<package-version> 加载 ARM 编译器的模块来安装适用于 Linux 的 ARM 编译器,其中 <package-version><major-version>.<minor-version >{.<patch-version>},例如:module load arm21/21.0

armclang -S <source>.c 命令可以帮助您编译 C++ 源代码并指定汇编代码输出,其中 -S 表示汇编代码输出,<source>.s 是将包含转换代码的文件。


使用 ARM Compiler for Linux 中的 armclang 命令将 C++ 转换为 ARM 汇编

您可以使用 ARM C++ 编译器生成带注释的汇编代码,这是了解编译器如何向量化循环的第一步。 用于 Linux 操作系统的 ARM 编译器是从 C++ 生成汇编代码的先决条件。

为 ARM 编译器加载模块后,运行 module load arm<major-version>/<package-version> 命令,例如:module load arm21/21.0 by putting <major-version>.<minor-version>{。 <patch-version>} 其中 <package-version> 是命令的一部分。

使用 armclang -S <source>.cpp 命令编译源代码,并将源文件名插入 <source>.cpp 的位置。

ARM 汇编编译器做一些与 GCC 编译器不同的事情,它使用 SIMD(单指令多数据)指令和寄存器来向量化代码。

GNN.cpp 文件:

#include <iostream>
using namespace std;

void subtract_arrays(int a, int b, int c)
{
    int sum;
    for (int i = 0; i < 5; i++)
    {
        a = (b + c) - i;
        sum = sum + a;
    }
    cout<<sum;
}
int main()
{
    int a = 1;
    int b = 2;
    int c = 3;
    subtract_arrays(a, b, c);
}
armclang -O1 -S -o source_O1.s GNN.cpp
Output:

subtract_arrays(int, int, int):
        push    rbp
        mov     rbp, rsp
        sub     rsp, 32
        mov     DWORD PTR [rbp-20], edi
        mov     DWORD PTR [rbp-24], esi
        mov     DWORD PTR [rbp-28], edx
        mov     DWORD PTR [rbp-8], 0
        jmp     .L2
.L3:
        mov     edx, DWORD PTR [rbp-24]
        mov     eax, DWORD PTR [rbp-28]
        add     eax, edx
        sub     eax, DWORD PTR [rbp-8]
        mov     DWORD PTR [rbp-20], eax
        mov     eax, DWORD PTR [rbp-20]
        add     DWORD PTR [rbp-4], eax
        add     DWORD PTR [rbp-8], 1
.L2:
        cmp     DWORD PTR [rbp-8], 4
        jle     .L3
        mov     eax, DWORD PTR [rbp-4]
        mov     esi, eax
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
        nop
        leave
        ret
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     DWORD PTR [rbp-4], 1
        mov     DWORD PTR [rbp-8], 2
        mov     DWORD PTR [rbp-12], 3
        mov     edx, DWORD PTR [rbp-12]
        mov     ecx, DWORD PTR [rbp-8]
        mov     eax, DWORD PTR [rbp-4]
        mov     esi, ecx
        mov     edi, eax
        call    subtract_arrays(int, int, int)
        mov     eax, 0
        leave
        ret
__static_initialization_and_destruction_0(int, int):
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     DWORD PTR [rbp-4], edi
        mov     DWORD PTR [rbp-8], esi
        cmp     DWORD PTR [rbp-4], 1
        jne     .L8
        cmp     DWORD PTR [rbp-8], 65535
        jne     .L8
        mov     edi, OFFSET FLAT:_ZStL8__ioinit
        call    std::ios_base::Init::Init() [complete object constructor]
        mov     edx, OFFSET FLAT:__dso_handle
        mov     esi, OFFSET FLAT:_ZStL8__ioinit
        mov     edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
        call    __cxa_atexit
.L8:
        nop
        leave
        ret
_GLOBAL__sub_I_subtract_arrays(int, int, int):
        push    rbp
        mov     rbp, rsp
        mov     esi, 65535
        mov     edi, 1
        call    __static_initialization_and_destruction_0(int, int)
        pop     rbp
        ret

使用 __asm 关键字将 C++ 转换为 ARM 汇编

众所周知,这是最有效的方法,因为编译器提供了一个内联汇编程序来在您的 C++ 源代码中编写汇编代码,并使您能够访问目标处理器的功能,这些功能不属于 C++ 的一部分或无法从 C++ 获得。

使用 GNU 内联汇编语法,_arm 关键字可帮助您将内联汇编代码合并或编写到函数中。

但是,将 armasm 语法汇编代码迁移到 GNU 语法并不是一个好的方法,因为内联汇编器不支持以 armasm 汇编语法编写的遗留汇编代码。

__asm [volatile](code); /* 基本内联汇编语法 */ 内联汇编语句显示了 _arm 语句的一般形式,还有一个扩展版本的内联汇编语法,您可以在下面的示例代码中找到。

对汇编程序指令使用 volatile 限定符是有益的,但可能有一些编译器可能不知道的缺点,包括: 禁用某些可能导致编译器删除代码块的编译器优化的机会。

由于 volatile 限定符是可选的,使用它可以确保编译器在使用 -01 或更高版本进行编译时不会删除汇编代码块。

#include <stdio.h>

int add(int x, int y)
{
  int sum = 0;
  __asm ("ADD %[_sum], %[input_x], %[input_y]"
    : [_sum] "=r" (sum)
    : [input_x] "r" (x), [input_y] "r" (y)
  );
  return sum;
}

int main(void)
{
  int x = 1;
  int y = 2;
  int z = 0;

  z = add(x, y);

  printf("Result of %d + %d = %d\n", x, y, z);
}

输出结果:

add(int, int):
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-20], edi
        mov     DWORD PTR [rbp-24], esi
        mov     DWORD PTR [rbp-4], 0
        mov     eax, DWORD PTR [rbp-20]
        mov     edx, DWORD PTR [rbp-24]
        ADD eax, eax, edx
        mov     DWORD PTR [rbp-4], eax
        mov     eax, DWORD PTR [rbp-4]
        pop     rbp
        ret
.LC0:
        .string "Result of %d + %d = %d\n"
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     DWORD PTR [rbp-4], 1
        mov     DWORD PTR [rbp-8], 2
        mov     DWORD PTR [rbp-12], 0
        mov     edx, DWORD PTR [rbp-8]
        mov     eax, DWORD PTR [rbp-4]
        mov     esi, edx
        mov     edi, eax
        call    add(int, int)
        mov     DWORD PTR [rbp-12], eax
        mov     ecx, DWORD PTR [rbp-12]
        mov     edx, DWORD PTR [rbp-8]
        mov     eax, DWORD PTR [rbp-4]
        mov     esi, eax
        mov     edi, OFFSET FLAT:.LC0
        mov     eax, 0
        call    printf
        mov     eax, 0
        leave
        ret

_arm 汇编语句中的code关键字是汇编指令,code_template 是它的模板; 如果您只指定它而不是代码,那么您必须在指定可选的 input_operand_list 和 clobbered_register_list 之前指定 output_operand_list。

output_operand_list(作为输出操作数列表)由逗号分隔,每个操作数由方括号中的符号名称组成,格式为 [result] "=r" (res)。

我们可以使用内联汇编来定义符号,例如 __asm (".global __use_no_semihosting\n\t"); 或者在标签名称后使用 : 符号定义标签,如 __asm ("my_label:\n\t");

此外,它使您能够在同一 _asm 语句中编写多条指令,还使您能够使用 __attribute__((naked)) 关键字编写嵌入式程序集。

对于相同的 C++ 源代码,Microsoft C++ 编译器 (MSVC) 可以在 ARM 架构上提供与在 x86 或 x64 机器或架构上不同的结果,您可能会遇到许多迁移或转换问题。

这些问题可能会调用未定义、实现定义或未指定的行为以及其他迁移问题,这些问题归因于 ARM 与 x86 或 x64 架构之间的硬件差异,这些架构与 C++ 标准的交互方式不同。

转载请发邮件至 1244347461@qq.com 进行申请,经作者同意之后,转载请以链接形式注明出处

本文地址:

相关文章

在 C++ 中通过掷骰子生成随机值

发布时间:2023/04/09 浏览次数:169 分类:C++

本文解释了如何使用时间因子方法和模拟 C++ 中的掷骰子的任意数方法生成随机数。了解它是如何工作的以及它包含哪些缺点。提供了一个 C++ 程序来演示伪数生成器。

在 C++ 中使用模板的链表

发布时间:2023/04/09 浏览次数:158 分类:C++

本文解释了使用模板在 C++ 中创建链表所涉及的各个步骤。工作程序演示了一个链表,该链表使用模板来避免在创建新变量时声明数据类型的需要。

在 C++ 中添加定时延迟

发布时间:2023/04/09 浏览次数:142 分类:C++

本教程将为你提供有关在 C++ 程序中添加定时延迟的简要指南。这可以使用 C++ 库为我们提供的一些函数以多种方式完成。

在 C++ 中创建查找表

发布时间:2023/04/09 浏览次数:155 分类:C++

本文重点介绍如何创建查找表及其在不同场景中的用途。提供了三个代码示例以使理解更容易,并附有代码片段以详细了解代码。

如何在 C++ 中把字符串转换为小写

发布时间:2023/04/09 浏览次数:63 分类:C++

介绍了如何将 C++ std::string 转换为小写的方法。当我们在考虑 C++ 中的字符串转换方法时,首先要问自己的是我的输入字符串有什么样的编码

如何在 C++ 中确定一个字符串是否是数字

发布时间:2023/04/09 浏览次数:163 分类:C++

本文介绍了如何检查给定的 C++ 字符串是否是数字。在我们深入研究之前,需要注意的是,以下方法只与单字节字符串和十进制整数兼容。

扫一扫阅读全部技术教程

社交账号
  • https://www.github.com/onmpw
  • qq:1244347461

最新推荐

教程更新

热门标签

扫码一下
查看教程更方便