GNU Assembler

Aus C und Assembler mit Raspberry
Version vom 24. September 2024, 07:41 Uhr von Satyria (Diskussion | Beiträge) (→‎Best Practices für Kommentare)
(Unterschied) ← Nächstältere Version | Aktuelle Version (Unterschied) | Nächstjüngere Version → (Unterschied)

Der GNU Assembler, häufig als GAS bezeichnet, ist ein Teil der GNU Binutils und ein wesentlicher Bestandteil der GNU Compiler Collection (GCC). Hier sind einige wichtige Punkte über den GNU Assembler:

  1. Zweck und Funktion: GAS ist ein Assembler, der Quellcode in Assemblersprache in Maschinencode übersetzt. Dies ist ein wichtiger Schritt im Kompilierungsprozess, insbesondere bei der Umwandlung von Hochsprachen wie C oder C++ in ausführbare Programme.
  2. Unterstützte Architekturen: GAS unterstützt eine Vielzahl von Prozessorarchitekturen, darunter x86, ARM, MIPS, PowerPC und viele mehr. Diese Vielseitigkeit macht es zu einem äußerst nützlichen Werkzeug für die Entwicklung auf verschiedenen Plattformen.
  3. Syntax: GAS verwendet eine spezielle Syntax, die als AT&T-Syntax bekannt ist. Diese unterscheidet sich von der Intel-Syntax, die in vielen anderen Assemblern verwendet wird. Zum Beispiel verwendet GAS das Format opcode source, destination, während die Intel-Syntax opcode destination, source verwendet.
  4. Integration mit GCC: Da GAS ein Teil der GNU Binutils ist, arbeitet es nahtlos mit GCC zusammen. Wenn Sie ein Programm mit GCC kompilieren, wird der Quellcode in der Regel zuerst in Assemblercode und dann in Maschinencode umgewandelt, wobei GAS diese Umwandlung übernimmt.
  5. Befehlszeilenoptionen: GAS bietet eine Vielzahl von Befehlszeilenoptionen, die es Entwicklern ermöglichen, die Assemblierung fein abzustimmen. Einige gängige Optionen sind -o zum Festlegen des Ausgabedateinamens und -g zum Hinzufügen von Debugging-Informationen.
  6. Lizenz: GAS wird unter der GNU General Public License (GPL) veröffentlicht. Dies bedeutet, dass es frei verfügbar ist und jeder den Quellcode einsehen, modifizieren und weiterverbreiten kann, solange die Bedingungen der GPL eingehalten werden.

Im folgenden beschreibe ich eine Auswahl von Präprozessor-Direktiven, die Präfixoperatoren und Infix-Operatoren. Zusätzlich gehe ich auf Bedingte Assemblierung und Makros ein.

Zusätzliche Themen zum GNU Assembler:

Präprozessor-Direktiven

Präprozessor-Direktiven
Direktive Bedeutung
.align Ausrichtung , Füllwert, MaxAusrichtung Ausrichtung bis zu einer bestimmten Speichergrenze.
  • Optional: Füllwert gibt den Wert an, mit dem Aufgefüllt wird; MaxAusrichtung, wenn Ausrichtung höher wird, wird die Ausrichtung ignoriert.
  • Ausrichtung: Anzahl der niederwertigen Nullbits, die der Zähler nach dem Fortschreiten haben muss. Zum Beispiel .align 3 erhöht den Standortzähler, bis er ein Vielfaches von 8 ist.
.ascii "string"... .ascii erwartet kein oder mehrere durch Kommas getrennte Strings. Es fügt jeden String zu aufeinanderfolgenden Adressen zusammen. Die Strings werden nicht mit NULL beendet
.asciz "string"... Wie .ascii, jedoch wird jedem String eine NULL angehägt, wie es zum Beispiel unter C üblich.
.balign[wl] Ausrichtung, Füllwert, MaxAusrichtung Ausrichtung bis zu einer bestimmten Speichergrenze.
  • Optional: Füllwert gibt den Wert an, mit dem Aufgefüllt wird; MaxAusrichtung, wenn Ausrichtung höher wird, wird die Ausrichtung ignoriert.
  • Ausrichtung: Anzahl der Bytes, die der Zähler nach dem Fortschreiten haben muss. Zum Beispiel .balign 8 erhöht den Standortzähler, bis er ein Vielfaches von 8 ist. [w] richtet es nach Word-Länge (2 Bytes) aus und [l] als Longword (4 Bytes) aus.
.byte Werte Erwartet Werte in Bytegröße, die durch Kommas getrennt sein können
.data Unterabschnitt Daten werden in den Abschnitt ".data" abgelegt
.double flonums Erwartet Gleitkommazahlen, die durch Kommas getrennt sein können
.equ symbol, expression Definiert ein Symbol mit einem Wert (siehe auch .set)
.float flonums Erwartet Gleitkommazahlen, die durch Kommas getrennt sein können
.global symbol
.globl symbol
Macht ein Symbol global bekannt
.hword Werte Erwartet Werte in 16-Bit-Zahl-Wert, die durch Kommas getrennt sein können
.include "file" Inkludiert eine Datei, die Code oder Daten beinhaltet. Der Inhalt wird an der Stelle des .include-Befehls eingefügt
.int Werte Erwartet Werte, die durch Kommas getrennt sein können. Die Größe ist abhängig von der Kompilierungsumgebung
.long Werte Wie .int
.octa Werte Erwartet Werte in 16 bytes-Zahl-Wert, die durch Kommas getrennt sein können
.org new-pos , fill Richtet den Code an die "new-pos" an. Mit "fill" (optional) wird angegeben, mit welchen Werten der übersprungen Speicher gefüllt wird
.quad Werte Erwartet Werte in 8 bytes-Zahl-Wert, die durch Kommas getrennt sein können
name .req registername Dadurch wird ein Alias ​​für den Registernamen mit dem Namen "name" erstellt .
Beispiel:
       Register0 .req x0
.set symbol, expression Definiert ein Symbol mit einem Wert
.short Werte Erwartet Werte, die durch Kommas getrennt sein können. Die Größe ist abhängig von der Kompilierungsumgebung
.single flonums Wie .float
.space größe, fill belegt Speicher von "größe" gefüllt optional mit "fill"
.string "str" Wie .asciz
.unreq alias-name Dadurch wird ein Registeralias gelöscht, der zuvor mit der .req Direktive definiert wurde.

Präfixoperator

Präfixoperator
- negiert den absolute Wert
~ Bitweise NOT vom absoluten Wert

Infix-Operatoren

Nach Priorität sortiert:

Infix-Operatoren
* Multiplikation
/ Division
% Rest
<< Bitweise nach links verschieben
>> Bitweise nach rechts verschieben
ǀ Bitweises Inklusives ODER
& Bitweises UND
^ Bitweises Exklusiv-Oder
! Bitweise oder nicht
+ Addition
- Subtraktion
== Ist gleich
<>
 !=
Ist nicht gleich
< Ist kleiner als
> Ist größer als
>= Ist größer als oder gleich
<= Ist kleiner als oder gleich
&& Logisches UND
ǀǀ Logisches ODER

Bedingte Assemblierung

Bedingte Assemblierung im GNU Assembler (GAS) ist eine mächtige Technik, die es ermöglicht, Teile des Assemblercodes basierend auf bestimmten Bedingungen einzuschließen oder auszuschließen. Dies ist besonders nützlich, wenn man für verschiedene Architekturen oder Konfigurationen, wie z.B. ARM64, entwickelt. Hier sind die wichtigsten Aspekte der bedingten Assemblierung im Zusammenhang mit ARM64-Assembler:

Grundlegende Direktiven für Bedingte Assemblierung

  • .if / .else / .endif: Diese Direktiven werden verwendet, um Codeblöcke basierend auf einer Bedingung ein- oder auszuschließen.
  • .ifdef / .ifndef: Diese Direktiven überprüfen, ob ein Symbol definiert ist oder nicht.
  • .elif: Diese Direktive ermöglicht alternative Codepfade, wenn die ursprüngliche Bedingung nicht erfüllt ist.
  • .define / .undef: Diese Direktiven definieren Symbole oder heben deren Definition auf.

Beispiele für Bedingte Assemblierung im ARM64-Assembler

Einfache Bedingte Assemblierung mit .if, .else und .endif

.equ DEBUG, 1  // Definiert ein Symbol DEBUG mit dem Wert 1

.if DEBUG
    // Dieser Code wird assembliert, wenn DEBUG nicht null ist
    .ascii "Debugging enabled\n"
.else
    // Dieser Code wird assembliert, wenn DEBUG null ist
    .ascii "Debugging disabled\n"
.endif

Bedingte Assemblierung mit .ifdef und .ifndef

#define FEATURE_ENABLED  // Definiert ein Symbol FEATURE_ENABLED

.ifdef FEATURE_ENABLED
    // Dieser Code wird assembliert, wenn FEATURE_ENABLED definiert ist
    .ascii "Feature is enabled\n"
.else
    // Dieser Code wird assembliert, wenn FEATURE_ENABLED nicht definiert ist
    .ascii "Feature is disabled\n"
.endif

#undef FEATURE_ENABLED  // Hebt die Definition von FEATURE_ENABLED auf

.ifndef FEATURE_ENABLED
    // Dieser Code wird assembliert, wenn FEATURE_ENABLED nicht definiert ist
    .ascii "Feature is now disabled\n"
.endif

Verwendung von .elif

.equ MODE, 2

.if MODE == 1
    .ascii "Mode 1 enabled\n"
.elif MODE == 2
    .ascii "Mode 2 enabled\n"
.else
    .ascii "Unknown mode\n"
.endif

Praktische Anwendungen für ARM64

Plattformübergreifende Assemblierung

Sie können spezifischen Code für verschiedene Prozessorarchitekturen schreiben.

.ifdef __aarch64__
    // Code spezifisch für ARM64-Architektur
    .ascii "ARM64 architecture\n"
.endif

.ifdef __x86_64__
    // Code spezifisch für x86_64-Architektur
    .ascii "x86_64 architecture\n"
.endif

Debugging und Optimierung

Aktivieren oder deaktivieren Sie Debugging-Code basierend auf definierten Symbolen.

.equ DEBUG, 1

.if DEBUG
    // Debugging-Code
    .ascii "Debug mode active\n"
.else
    // Produktionscode
    .ascii "Production mode active\n"
.endif

Feature-Toggles

Sie können bestimmte Funktionen basierend auf definierten Symbolen aktivieren oder deaktivieren.

.define FEATURE_X

.ifdef FEATURE_X
    // Code für Feature X
    .ascii "Feature X is enabled\n"
.else
    // Alternativer Code
    .ascii "Feature X is disabled\n"
.endif

Bedingte Assemblierung im ARM64-Assembler

Hier ist ein Beispiel, das verschiedene Aspekte der bedingten Assemblierung für ARM64-Assembler zeigt:

.section .data
    .equ DEBUG, 1
    .asciz "Debug: "

.section .text
    .globl _start

_start:
    .if DEBUG
        ldr x0, =message_debug
        bl  printf
    .else
        ldr x0, =message_production
        bl  printf
    .endif

    // Exit system call
    mov x8, #93
    mov x0, #0
    svc #0

message_debug:
    .asciz "Debug mode active\n"
message_production:
    .asciz "Production mode active\n"

Erklärung des Beispiels: .section .data: Definiert den Datenabschnitt, in dem die Zeichenketten gespeichert werden. .section .text: Definiert den Textabschnitt, in dem der ausführbare Code gespeichert wird. .globl _start: Markiert den Einstiegspunkt des Programms. .if DEBUG: Bedingte Assemblierung, die überprüft, ob DEBUG definiert und nicht null ist. ldr x0, =message_debug: Lädt die Adresse der Debug-Nachricht in das Register x0, wenn DEBUG definiert ist. bl printf: Ruft die printf-Funktion auf, um die Nachricht auszugeben. svc #0: Führt den svc (SuperVisor Call) Befehl aus, um das Programm zu beenden. Dieses Beispiel zeigt, wie bedingte Assemblierung verwendet werden kann, um unterschiedliche Nachrichten basierend auf dem Wert des DEBUG-Symbols auszugeben.

Makros

.macro

Mit den Befehlen .macro und .endm können Sie Makros definieren, die Assembly-Ausgaben generieren. Diese Definition gibt beispielsweise ein Makro an, sum das eine Zahlenfolge in den Speicher einfügt:

.macro  sum from=0, to=5
.long   \from
.if     \to-\from
sum     "(\from+1)",\to
.endif
.endm

Mit dieser Definition, SUM 0,5 ist gleichwertig mit dieser Assemblyeingabe:

.long   0
.long   1
.long   2
.long   3
.long   4
.long   5
.macro macname
.macro macname macargs …
Damit beginnt die Definition eines Makros namens macname . Wenn die Makrodefinition Argumente erfordert, werden nach dem Macronamen ihre Namen übergeben, die per Komma oder Leerzeichen definiert werden können. Jedem Argument kann ein Standardwert zugeordnet werden, indem dem Namen ein "= def" zugeordnet wird.
.macro comm
Der Beginn eines Makros ohne Argumente.
.macro plus1 p, p1
.macro plus1 p p1
Erzeugt ein Makro namens plus1, das zwei Argumente annimmt. Beide Schreibweisen sind möglich. Um auf die Argumente innerhalb des Makros zuzugreifen wird \p oder \p1 verwendet.
.macro reserve_str p1=0 p2
Erzeugt ein Makro namens reserve_str, das zwei Argumente annimmt. Das erste Argument "p1" hat als Standardwert "0", wenn dieses nicht angegeben wurde. Wird ein Parameter übergeben, wird der Standardwert überschrieben. Um "nur" das zweite Argument "p2" anzusprechen wird das Makro wie volgt aufgerufen:
reserve_str ,b
.macro m p1:req, p2=0, p3:vararg
Erzeugt ein Makro namens m, das mind. drei Argumente annimmt. Das erste Argument muss mit übergeben werden. Hier wird das Schlüsselwort :req verwendet, welches besagt, dass ein Wert mit übergeben werden muss. Das zweite Argument kann übergeben werden, wenn dieser kein Argument enthält, wird der Standradwert "0" verwendet. Das dritte Argument bekommt alle Werte zugesprochen, die sonst noch angegeben wurden. Hier wird Schlüsselwort :vararg verwendet.
.endm
Definiert das Ende des Makros.
.exitm
Definiert ein vorzeitiges Ende des Makros.
\@ (Backslash-At):
\@ ist eine Erweiterung, die eine eindeutige, numerische Kennung für jedes Auftreten eines Makros generiert. Diese Nummer ändert sich bei jedem Aufruf des Makros und kann daher verwendet werden, um eindeutige Namen für Labels, Register oder Variablen zu erstellen.
Die generierte Zahl ist immer gleich innerhalb desselben Makroaufrufs, aber sie ändert sich bei jedem neuen Makroaufruf.
Beispiel:
.macro UNIQUE_LABEL
label_\@:
.endm
Bei jedem Aufruf des Makros UNIQUE_LABEL wird label_\@ zu einem eindeutigen Label wie label_1, label_2 usw.
\+ (Backslash-Plus):
\+ ist ein Zähler, der jedes Mal erhöht wird, wenn er verwendet wird. Anders als \@ bleibt \+ nicht auf den Bereich eines Makros beschränkt und kann über mehrere Makroaufrufe hinweg zunehmen.
Es kann nützlich sein, um eindeutig nummerierte Labels oder Variablen zu erstellen, die eine sequentielle Reihenfolge benötigen.
Beispiel:
.macro INCREMENT_LABEL
label_\+:
.endm
Bei jedem Aufruf des Makros INCREMENT_LABEL erhöht sich der Wert von \+ um eins, sodass die Labels aufeinanderfolgende Nummern wie label_0, label_1, label_2 usw. erhalten.

Kommentare

Grundlegende Syntax

Einzeilige Kommentare beginnen mit einem //-Zeichen, @-Zeichen oder einem #-Zeichen. Alles, was nach diesem Zeichen folgt, wird vom Assembler ignoriert.

// Dies ist ein Kommentar
mov x0, #1  // Setzt den Wert 1 in das Register x0

Mehrzeilige Kommentare können, wie in C, auch mehrzeilig sein. Dazu wird der Kommentar zwischen den Zeichen /* und */ gesetzt.

/*
Dieser Text ist ein
mehrzeiliger Kommentar
*/

Best Practices für Kommentare

Kommentare sollten klar und prägnant sein. Sie sollten den Zweck des Codes erklären, ohne überflüssig zu sein.

// Initialisiert das Register x0 mit dem Wert 1
mov x0, #1

Verwende die Kommentare, um komplexe oder nicht intuitive Teile des Codes zu erklären.

// Berechnet die Summe der ersten 10 natürlichen Zahlen
// und speichert das Ergebnis in x0
mov x0, #0  // Setzt x0 auf 0
mov x1, #1  // Setzt x1 auf 1 (Schleifenzähler)
loop_start:
add x0, x0, x1  // Addiert den Wert von x1 zu x0
add x1, x1, #1  // Erhöht den Wert von x1 um 1
cmp x1, #10     // Vergleicht x1 mit 10
ble loop_start  // Springt zurück zu loop_start, wenn x1 <= 10

Notiere die Annahmen, die über den Zustand des Systems oder der Register.

// Annahme: x2 enthält die Anzahl der zu verarbeitenden Elemente
mov x0, #0  // Initialisiert den Summenzähler auf 0

Wenn bestimmte Teile des Codes wartungsintensiv sind oder besondere Aufmerksamkeit erfordern, solle das in Kommentaren vermerken werden.

// Achtung: Dieser Codeabschnitt ist leistungskritisch
// Optimierungen können erforderlich sein

Verwendung von TODOs und FIXMEs: Markiere die Bereiche des Codes, die noch bearbeitet oder verbessert werden müssen.

// TODO: Fehlerbehandlung hinzufügen
mov x0, #1

Kommentare können auch beim Debugging hilfreich sein, indem sie den Status und Änderungen von Registern oder Speicheradressen dokumentieren:

// Debugging: Überprüfen des Werts von x0 nach der Addition
mov x0, #5
add x0, x0, #3  // x0 sollte jetzt 8 enthalten

Halte Dich an eine konsistente Kommentierungsstrategie im gesamten Code, um die Lesbarkeit und Wartbarkeit zu erhöhen.


< Zurück (GNU Compiler Collection) < Hauptseite > Weiter (GNU C Compiler) >