In diesem Abschnitt lernen Sie, wie Daten zwischen den schnellen CPU-Registern und dem größeren, aber langsameren Hauptspeicher bewegt werden. Dies ist die Grundlage jeder Datenmanipulation in Assembler.
Register und Speicher: Die Grundlagen
- Register sind extrem schnelle Speicherplätze direkt in der CPU. ARM64 besitzt 31 allgemeine 64-Bit-Register (`
x0` bis `x30`). Die unteren 32 Bit eines Registers können auch separat als `w0` bis `w30` angesprochen werden. - Der Hauptspeicher (RAM) wird über Adressen angesprochen. Die CPU kann nicht direkt im Speicher rechnen. Daher ist der Standardablauf immer:
1. Adresse der Variable ermitteln.
2. Wert aus dem Speicher in ein Register laden (`ldr`).
3. Wert im Register manipulieren (z. B. mit `add` oder `mov`).
4. Wert zurück in den Speicher schreiben (`str`).
Der grundlegende Ablauf: `ldr` und `str`
Um mit einer Variable im Speicher zu arbeiten, müssen wir streng zwischen der Adresse der Variable und dem Wert an dieser Adresse unterscheiden.
Beispiel: Lesen und Schreiben einer Variable
.data
meine_zahl:
.quad 0 // Reserviert 8 Byte (64 Bit) im Speicher, initialisiert mit 0
.text
.global _start
_start:
// 1. Adresse der Variable laden (Pseudo-Instruktion)
// Der Assembler sorgt dafür, dass die Adresse von 'meine_zahl' in x0 steht.
ldr x0, =meine_zahl
// 2. Einen neuen Wert vorbereiten
mov x1, #42 // x1 enthält jetzt den Wert 42
// 3. Wert in den Speicher schreiben (Store)
// Schreibe den Inhalt von x1 (42) an die Adresse, die in x0 steht.
str x1, [x0]
// 4. Wert aus dem Speicher lesen (Load)
// Lese den Wert an der Adresse in x0 und speichere ihn in x2.
ldr x2, [x0] // x2 enthält jetzt 42
(Tipp für GDB: Um nach dem `str`-Befehl zu prüfen, ob der Wert im Speicher steht, nutzen Sie `x /1g $x0`. Das `g` steht für "giant word" (64 Bit) und `$x0` ist die Adresse, die wir ansehen wollen.)
Datentypen und Suffixe
Standardmäßig laden `ldr` und speichern `str` 64 Bit (bei `x`-Registern) oder 32 Bit (bei `w`-Registern). Um kleinere Datenmengen (Bytes oder Halfwords) zu verarbeiten, hängen wir ein Suffix direkt an den Befehl.
| Suffix | Befehl (Beispiel) | Bedeutung | Wichtiger Hinweis |
| (ohne) | ldr x0, [x1] |
64-Bit (Doubleword) | Standard bei `x`-Registern |
| (ohne) | ldr w0, [x1] |
32-Bit (Word) | Standard bei `w`-Registern |
| b | ldrb / strb |
Byte (8 Bit) | Liest/Schreibt 1 Byte. |
| sb | ldrsb |
Signed Byte | Nur beim Laden! Liest 1 Byte und füllt die oberen Bits mit dem Vorzeichen auf 64 Bit auf. |
| h | ldrh / strh |
Halfword (16 Bit) | Liest/Schreibt 2 Bytes. |
| sh | ldrsh |
Signed Halfword | Nur beim Laden! Liest 2 Bytes und erweitert das Vorzeichen. |
| sw | ldrsw |
Signed Word | Nur beim Laden! Liest 4 Bytes und erweitert das Vorzeichen auf 64 Bit. |
Wichtig: Es gibt keine "Signed"-Store-Befehle (wie strsb). Beim Speichern werden einfach nur die unteren Bits des Registers in den Speicher geschrieben; das Vorzeichen ist dafür irrelevant.
Adressierungsarten (Wie die Adresse in `[...]` gebildet wird)
Der Teil in den eckigen Klammern [...] bestimmt, wo im Speicher gelesen oder geschrieben wird. ARM64 bietet hierfür flexible Modi.
Register-indirekt (Basis)
Die Adresse steht komplett in einem Register.
ldr x0, [x1] // Lese von der Adresse, die in x1 steht
Basis + Immediate-Offset (Vorindexiert)
Ein fester Wert (Offset) wird zur Basisadresse addiert. Nützlich für Strukturen.
ldr x0, [x1, #8] // Lese von (Adresse in x1) + 8 Bytes
str w0, [x1, #4] // Schreibe 32-Bit-Wert an (Adresse in x1) + 4 Bytes
Register-Offset (Indizierung für Arrays)
Ein zweites Register wird als Offset verwendet. Oft wird dieser Wert noch verschoben (`lsl`), um die Größe der Array-Elemente zu berücksichtigen.
// Angenommen, x1 ist die Basisadresse eines Arrays aus 32-Bit-Wörtern (4 Byte)
// und w2 enthält den Index (z.B. 3).
ldr w0, [x1, w2, uxtw #2] // Adresse = x1 + (w2 * 4)
(Hinweis: `uxtw` bedeutet "Unsigned Extend Word" und stellt sicher, dass das 32-Bit-Register `w2` korrekt als 64-Bit-Adresse verrechnet wird).
Post-Indexierte Adressierung
Die Adresse wird für den Zugriff genutzt, und anschließend wird das Basisregister automatisch um den Offset erhöht. Perfekt für das Durchlaufen von Arrays oder Strings.
ldr x0, [x1], #8 // 1. Lese Wert von Adresse in x1 nach x0.
// 2. Erhöhe danach x1 um 8 (x1 = x1 + 8).
Praxisbeispiel: Array in einer Schleife verarbeiten
Hier kombinieren wir Register-Offset und Schleifen, um ein Array zu füllen.
.data
mein_array:
.fill 10, 4, 0 // 10 Elemente, je 4 Byte (Word), initialisiert mit 0
.text
.global _start
_start:
ldr x0, =mein_array // x0 = Basisadresse des Arrays
mov w1, #1 // w1 = Laufvariable i (Start bei 1)
forloop:
// 1. Berechne den Wert: i * 8
// Linksverschiebung um 3 ist dasselbe wie Multiplikation mit 8
lsl w4, w1, #3
// 2. Speichere den Wert ins Array: Adresse = x0 + (i * 4)
// Wir nutzen w1 als Index und verschieben ihn um 2 (== *4) für die Word-Größe
str w4, [x0, w1, uxtw #2]
// 3. Laufvariable erhöhen: i = i + 1
add w1, w1, #1
// 4. Schleifenbedingung: Wenn i <= 10, springe zurück
cmp w1, #10
b.le forloop
// Ende der Schleife
Effiziente Mehrfachzugriffe: `ldp` und `stp`
Um die Leistung zu steigern, erlaubt ARM64 das gleichzeitige Laden oder Speichern von zwei 64-Bit-Registern (insgesamt 128 Bit) mit einem einzigen Befehl. Dies reduziert die Anzahl der Instruktionen und nutzt den Speicherbus effizienter.
ldp(Load Pair): Lädt zwei Register aus aufeinanderfolgenden Speicherstellen.stp(Store Pair): Speichert zwei Register in aufeinanderfolgende Speicherstellen.
.data
grosser_wert:
.octa 0x11111111111111112222222222222222 // 16 Byte (128 Bit)
.text
ldr x0, =grosser_wert
// Lade die unteren 64 Bit in x1, die oberen 64 Bit in x2
ldp x1, x2, [x0]
// Modifiziere die Werte...
add x1, x1, #1
// Schreibe beide Register zurück in den Speicher
stp x1, x2, [x0]
Zusammenfassung der Befehle
| Aufgabe | Befehl | Beispiel | Erklärung |
| Konstante laden | mov |
mov x0,#42 |
Lädt kleine, direkte Werte in ein Register. |
| Adresse laden | ldr (Pseudo) |
ldr x0, =var |
Lädt die Adresse einer Variable/Konstante (Assembler-Magie). |
| Wert lesen | ldr |
ldr x1,[x0] |
Liest den Inhalt an der Adresse in x0 nach x1. |
| Wert schreiben | str |
str x1,[x0] |
Schreibt den Inhalt von x1 an die Adresse in x0 |
| Teilweise lesen | ldrb, ldrh ... |
ldrb w1, [x0] |
Liest nur 1 oder 2 Byte (mit oder ohne Vorzeichenerweiterung). |
| Paarweise zugreifen | ldp / stp |
ldp x1, x2, [x0] |
Lädt/Speichert zwei 64-Bit-Register gleichzeitig für bessere Performance. |