将 C++ 转换为 ARM 程序集
将 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
输出结果:
可以使用一系列 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;
}
输出:
使用 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++ 标准的交互方式不同。
相关文章
在 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++ 字符串是否是数字。在我们深入研究之前,需要注意的是,以下方法只与单字节字符串和十进制整数兼容。
如何在 c++ 中查找字符串中的子字符串
发布时间:2023/04/09 浏览次数:65 分类:C++
-
本文介绍了在 C++ 中检查一个字符串是否包含子字符串的多种方法。使用 find 方法在 C++ 中查找字符串中的子字符串
如何在 C++ 中把字符串转换为 Char 数组
发布时间:2023/04/09 浏览次数:107 分类:C++
-
本文介绍了在 C++ 中把字符串转换为 char 数组的多种方法。使用 std::basic_string::c_str 方法将字符串转换为 char 数组