Konvertieren Sie C++ in ARM-Assembly
- Verwenden Sie den GCC-Compiler, um C++ in ARM-Assembly zu konvertieren
- Erstellen Sie eine MOD-Funktion (Assembly-Time Modulus), um C++ in ARM-Assembly zu konvertieren
-
Verwenden Sie den Befehl
arm-linux-gnueabi-gcc
, um C++ in ARM Assembly zu konvertieren -
Verwenden Sie den Befehl
armclang
im ARM-Compiler für Linux, um C++ in ARM-Assembly zu konvertieren -
Verwenden Sie das Schlüsselwort
__asm
, um C++ in ARM Assembly zu konvertieren
Die Schnittstelle zwischen C++ und ARM-Assembler dient den Programmierern in vielerlei Hinsicht, und es ist auch ein unkomplizierter Prozess, der C++ beim Zugriff auf verschiedene Funktionen und Variablen hilft, die in der Assemblersprache definiert sind, und umgekehrt. In diesem Tutorial erfahren Sie, wie Sie C++-Code oder -Funktionen in ARM-Assembly konvertieren.
Programmierer können separate Assembler-Code-Module verwenden, um sie mit C++-kompilierten Modulen zu verknüpfen, um die in C++ eingebetteten Assembler-Variablen und Inline-Assembler zu verwenden oder den vom Compiler erzeugten Assembler-Code zu ändern.
Am wichtigsten ist, dass Sie alle dedizierten Register, die von einer Funktion geändert wurden, beibehalten, Interrupt-Routinen zum Speichern aller Register aktivieren, sicherstellen, dass Funktionen Werte gemäß ihrer C++-Deklaration korrekt zurückgeben, kein Assembly-Modul verwenden, das den Abschnitt .cinit
verwendet, und den Compiler für die Zuweisung aktivieren verknüpfen Sie Namen mit allen externen Objekten und deklarieren Sie jedes Objekt und jede Funktion mit der Direktive .def
oder .global
, auf die zugegriffen oder die von C++ im Assembly-Modifizierer aufgerufen wird, bevor Sie C++ in die ARM-Assembly konvertieren.
Definieren Sie die aus der Assemblersprache aufgerufenen Funktionen mit C
(Funktionen prototypisiert als externes C
) in einer C++-Datei. Definieren Sie Variablen im Abschnitt .bss
oder weisen Sie ihnen ein Linker-Symbol zu, um später zu erkennen, welches konvertiert werden muss.
Verwenden Sie den GCC-Compiler, um C++ in ARM-Assembly zu konvertieren
Der gcc
ist eine großartige Quelle, um Zwischenausgaben von C++-Code während seiner Ausführung zu erhalten. Es ist eine Funktion, die die Assembler-Ausgabe mit der Option -S
erhält.
Die Option -S
ist für die Ausgabe nach dem Kompilieren des Codes, bevor er an den Assembler gesendet wird.
Seine Syntax ist gcc –S your_program.cpp
, und Sie können ein einfaches C++-Programm schreiben, um es in eine ARM-Assembly zu konvertieren, indem Sie einfach diesen Befehl deklarieren. Abgesehen davon, dass es einer der einfachsten Ansätze ist, ist seine Ausgabe komplex und schwer zu verstehen, selbst für Programmierer auf mittlerem Niveau.
GNN.cpp
-Datei:
#include <iostream>
using namespace std;
main() {
int i, u, div;
i = 2;
u = 10;
div = i / u;
cout << "Answer: " << div << endl;
}
Führen Sie diesen Befehl auf GCC in Microsoft Windows aus:
gcc –S GNN.cpp
Ausgang:
Es ist möglich, eine Reihe von ASM-Anweisungen oder eine einzelne ASM-Anweisung für das Einfügen einer einzelnen Zeile des Assemblercodes in die Assemblerdatei innerhalb Ihres C++-Programms zu verwenden, das der Compiler erstellt. Diese Assembleranweisungen platzieren aufeinanderfolgende Codezeilen (Assemblercode) im Compiler (C++-Compilerausgabe) ohne dazwischenliegenden Code (ohne Codeunterbrechungen).
Pflegen Sie jedoch immer die C++-Umgebung, da der Compiler die eingefügten Anweisungen nicht prüft/analysiert. Vermeiden Sie immer das Einfügen von Labels oder Umps in C++-Code, da sie zu unvorhersehbaren Ergebnissen führen und die vom Code generierten Registerverfolgungsalgorithmen verwirren können.
Darüber hinaus sind die ASM-Anweisungen keine gültige Wahl zum Einfügen von Assembler-Direktiven, und Sie können den Befehl symdebug:dwarf
oder den Befehl -g
verwenden, ohne die Assembly-Umgebung zu ändern und die Erstellung von Assembly-Makros in C++-Code zu vermeiden, da die C++-Umgebung debuggt Informationen.
Erstellen Sie eine MOD-Funktion (Assembly-Time Modulus), um C++ in ARM-Assembly zu konvertieren
Da der ARM-Assembly die MOD-Befehle fehlen, können Sie eine MOD-Funktion mit Subs erstellen und C++ einfach in ARM-Assembly konvertieren. Sie müssen die Speicheradresse der Variablen über ldr reg, =var
laden, und falls Sie die Variable laden möchten, müssen Sie ein weiteres ldr
mit diesem reg
ausführen, z. B. ldr r0, =carry ldr r0, [r0]
, um den an der Speicheradresse in r0
gespeicherten Wert zu laden.
Verwenden Sie sdiv
, da es viel schneller ist als eine Subtraktionsschleife, mit Ausnahme von minimalen Eingaben, bei denen die Schleife nur ein- oder zweimal ausgeführt wird.
Konzept:
;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;
}
Ausgang:
Verwenden Sie den Befehl arm-linux-gnueabi-gcc
, um C++ in ARM Assembly zu konvertieren
Der Befehl arm-linux-gnueabi-gcc
ist eine perfekte Möglichkeit, C++ in ARM-Assembly für x86- und x64-Rechner zu konvertieren. Da der gcc
keine ARM-Ziele zur Verfügung hat, können Sie ihn nicht für allgemeine Systeme verwenden, sondern nur, wenn Sie sich auf einem ARM-System befinden, auf dem Sie stattdessen den regulären gcc
verwenden können.
Der vollständige Befehl arm-linux-gnueabi-gcc -S -O2 -march=armv8-a GNN.cpp
ist unglaublich stark, wobei -S
die Ausgabebaugruppe darstellt und gcc
darüber informiert, -02
ist ein Code-Optimierer und reduziert Debug-Unordnung aus dem Ergebnis. Das -02
ist optional; Andererseits ist -march=armv8-a
obligatorisch und weist es an, beim Kompilieren das ARM v8-Ziel zu verwenden.
Sie können das ARM-Ziel während des Kompilierens ändern, indem Sie die verschiedenen Versionen von ARM v8 verwenden, einschließlich; armv8-a
, armv8.1-a
bis armv8.6-a
, armv8-m.base
, armv8-m.main
und armv8.1-m.main
, wo jeweils Eines ist etwas anders, und Sie können eine eingehende Analyse durchführen und dasjenige auswählen, das Ihren Anforderungen perfekt entspricht.
Das power.c
des Befehls gibt an, welche Datei kompiliert werden soll, und wenn Sie keine Ausgabedatei wie -o output.asm
angegeben haben, wird die Assembly unter dem ähnlichen Dateinamen power.s
ausgegeben.
Das arm-linux-gnueabi-gcc
ist eine großartige Alternative zum Kompilieren auf einer arm
-Maschine, die die Ziel- oder Ausgabe-Assembly mit dem regulären gcc
versorgt.
Mit gcc
können Programmierer die Zielarchitektur mit -march=xxx
angeben, und Sie müssen wissen, wie Sie das apt
-Paket Ihrer Maschine identifizieren, um das richtige auszuwählen.
GNN.cpp
-Datei:
#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
Ausgang:
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
Alternativ können Sie den ARM-Compiler für Linux installieren, indem Sie das Modul für den ARM-Compiler laden, indem Sie module load arm<major-version>/<package-version>
ausführen, wobei <package-version>
<major-version>.<minor-version>{.<patch-version>}
, zum Beispiel: modul load arm21/21.0
.
Der Befehl armclang -S <Quelle>.c
kann Ihnen dabei helfen, Ihre C++-Quelle zu kompilieren und eine Assembler-Code-Ausgabe anzugeben, wobei -S
die Assembler-Code-Ausgabe darstellt und <Quelle>.s
die Datei ist, die den konvertierten Code enthält .
Verwenden Sie den Befehl armclang
im ARM-Compiler für Linux, um C++ in ARM-Assembly zu konvertieren
Sie können mit dem ARM-C++-Compiler kommentierten Assemblercode erstellen. Dies ist der erste Schritt, um zu lernen, wie der Compiler Schleifen vektorisiert. Ein ARM-Compiler für Linux OS ist eine Voraussetzung für die Generierung des Assembler-Codes aus C++.
Führen Sie nach dem Laden des Moduls für den ARM-Compiler den Befehl module load arm<major-version>/<package-version>
aus, zum Beispiel: module load arm21/21.0
, indem Sie <major-version>.<minor-version>{.<patch-version>}
wobei <package-version>
Teil des Befehls ist.
Kompilieren Sie Ihren Quellcode mit dem Befehl armclang -S <source>.cpp
und fügen Sie den Namen der Quelldatei an der Stelle von <source>.cpp
ein.
Der ARM-Assembly-Compiler macht etwas anderes als der GCC-Compiler, indem er SIMD-Anweisungen (Single Instruction Multiple Data) und Register verwendet, um den Code zu vektorisieren.
GNN.cpp
-Datei:
#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
Ausgang:
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
Verwenden Sie das Schlüsselwort __asm
, um C++ in ARM Assembly zu konvertieren
Es ist bekannt, dass dies der gültigste Ansatz ist, da der Compiler einen Inline-Assembler zum Schreiben von Assemblercode in Ihren C++-Quellcode bereitstellt und Ihnen den Zugriff auf Funktionen des Zielprozessors ermöglicht, die nicht Teil von C++ sind oder von C++ verfügbar sind.
Unter Verwendung der GNU-Inline-Assembly-Syntax hilft Ihnen das Schlüsselwort _arm
, Inline-Assembly-Code in eine Funktion einzufügen oder zu schreiben.
Es ist jedoch kein guter Ansatz, den Assembler-Code der armasm
-Syntax in die GNU-Syntax zu migrieren, da der Inline-Assembler keinen Legacy-Assembler-Code unterstützt, der in der armasm
-Assemblersyntax geschrieben wurde.
Der __asm [flüchtig] (Code); /* Grundlegende Inline-Assembly-Syntax */
Inline-Assembly-Anweisung zeigt die allgemeine Form einer _arm
-Anweisung, und es gibt auch eine erweiterte Version der Inline-Assembly-Syntax, die Sie im Beispielcode unten finden.
Die Verwendung des flüchtigen
Qualifizierers für Assembler-Anweisungen ist vorteilhaft, kann aber einige Nachteile haben, die der Compiler möglicherweise nicht kennt, einschließlich; die Möglichkeit, bestimmte Compiler-Optimierungen zu deaktivieren, die dazu führen können, dass der Compiler den Codeblock entfernt.
Da der Qualifizierer volatile
optional ist, kann seine Verwendung sicherstellen, dass der Compiler die Assembler-Code-Blöcke nicht entfernt, wenn er mit -01
oder höher kompiliert.
#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);
}
Ausgang:
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
Das Schlüsselwort code
in der Assembler-Anweisung _arm
ist die Assembler-Anweisung und code_template
ist ihre Vorlage; Wenn Sie es nur anstelle von code
angeben, müssen Sie die output_operand_list
angeben, bevor Sie die optionalen input_operand_list
und clobbered_register_list
angeben.
Die output_operand_list
(als Ausgabeoperandenliste) ist durch Kommas getrennt und jeder Operand besteht aus einem symbolischen Namen in eckigen Klammern im Format [result] "=r" (res)
.
Sie können die Inline-Assembly verwenden, um Symbole wie __asm (".global __use_no_semihosting\n\t");
zu definieren. oder um Labels mit dem :
-Zeichen nach dem Label-Namen zu definieren, wie __asm ("my_label:\n\t");
.
Darüber hinaus ermöglicht es Ihnen, mehrere Anweisungen innerhalb derselben _asm
-Anweisung zu schreiben, und ermöglicht Ihnen auch, eingebettete Assemblys mit dem Schlüsselwort __attribute__((naked))
zu schreiben.
Der Microsoft C++-Compiler (MSVC) kann auf der ARM-Architektur andere Ergebnisse liefern als auf x86- oder x64-Computern oder -Architekturen für denselben C++-Quellcode, und Sie können auf viele Migrations- oder Konvertierungsprobleme stoßen.
Die Probleme können undefiniertes, implementierungsdefiniertes oder nicht spezifiziertes Verhalten und andere Migrationsprobleme hervorrufen, die auf Hardwareunterschiede zwischen ARM- und x86- oder x64-Architekturen zurückzuführen sind, die unterschiedlich mit dem C++-Standard interagieren.
Hassan is a Software Engineer with a well-developed set of programming skills. He uses his knowledge and writing capabilities to produce interesting-to-read technical articles.
GitHub