Konvertieren Sie C++ in ARM-Assembly

Syed Hassan Sabeeh Kazmi 15 Februar 2024
  1. Verwenden Sie den GCC-Compiler, um C++ in ARM-Assembly zu konvertieren
  2. Erstellen Sie eine MOD-Funktion (Assembly-Time Modulus), um C++ in ARM-Assembly zu konvertieren
  3. Verwenden Sie den Befehl arm-linux-gnueabi-gcc, um C++ in ARM Assembly zu konvertieren
  4. Verwenden Sie den Befehl armclang im ARM-Compiler für Linux, um C++ in ARM-Assembly zu konvertieren
  5. Verwenden Sie das Schlüsselwort __asm, um C++ in ARM Assembly zu konvertieren
Konvertieren Sie C++ in ARM-Assembly

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:

gcc-Compiler

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:

mod

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.

Syed Hassan Sabeeh Kazmi avatar Syed Hassan Sabeeh Kazmi avatar

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