ARM64 Assembly Tutorial – Teil 2: Register, Arithmetik und Speicher

ARM64 Assembly für Einsteiger – Teil 2: Register, Arithmetik und Speicher

← Zurück zu Teil 1

Einleitung

Im ersten Teil hast du dein Entwicklungssystem eingerichtet und dein erstes „Hello World“-Programm geschrieben. Jetzt tauchen wir tiefer ein und lernen die fundamentalen Bausteine der ARM64-Architektur kennen.

In diesem Teil lernst du:

  • Was Register sind und wie man sie benutzt
  • Einfache Berechnungen durchführen
  • Daten im Speicher ablegen und laden
  • Mit Arrays arbeiten

Register – Die Bausteine der CPU

Was sind Register?

Stell dir Register als winzige Schubladen direkt in der CPU vor. Sie können Zahlen speichern und sind extrem schnell – viel schneller als der normale Arbeitsspeicher (RAM). Der Nachteil: Es gibt nur wenige davon.

ARM64 hat 31 allgemeine Register plus ein paar spezielle:

  • x0 bis x30 – 64-Bit Register (können große Zahlen bis 2^64 speichern)
  • w0 bis w30 – 32-Bit Versionen (die unteren 32 Bits der x-Register)
  • sp – Stack Pointer (spezielles Register)
  • pc – Program Counter (wird automatisch verwaltet)

x-Register vs. w-Register

Jedes Register kann auf zwei Arten verwendet werden:

x0 = 64 Bit: 0x0000000000000042 (komplette 8 Bytes)
w0 = 32 Bit: 0x00000042          (nur untere 4 Bytes)

Wenn du w0 verwendest, werden die oberen 32 Bits automatisch auf 0 gesetzt!

Dein erstes Rechenbeispiel

Zahlen in Register schreiben

Erstelle eine neue Datei rechnen.s:

.global _start
.align 2

.text
_start:
    mov x0, #5          // x0 = 5
    mov x1, #10         // x1 = 10

    // Programm beenden
    mov x0, #0
    mov x16, #1
    svc #0x80

Was passiert hier?

  • mov x0, #5 – Schreibe die Zahl 5 in Register x0
  • mov x1, #10 – Schreibe die Zahl 10 in Register x1
  • Das # bedeutet „direkte Zahl“ (Immediate Value)

Nach diesen zwei Zeilen enthält x0 den Wert 5 und x1 den Wert 10. Die Register behalten diese Werte, bis du sie überschreibst!

Arithmetische Operationen

Addition und Subtraktion

.global _start
.align 2

.text
_start:
    mov x0, #5          // x0 = 5
    mov x1, #3          // x1 = 3

    add x2, x0, x1      // x2 = x0 + x1 = 8
    sub x3, x0, x1      // x3 = x0 - x1 = 2

    // Exit
    mov x0, #0
    mov x16, #1
    svc #0x80

Syntax von Arithmetik-Befehlen:

add Ziel, Quelle1, Quelle2
sub Ziel, Quelle1, Quelle2

Das Ergebnis wird im Ziel-Register gespeichert, die Quell-Register bleiben unverändert.

Multiplikation

.global _start
.align 2

.text
_start:
    mov x0, #6          // x0 = 6
    mov x1, #7          // x1 = 7

    mul x2, x0, x1      // x2 = 6 * 7 = 42

    // Exit
    mov x0, #0
    mov x16, #1
    svc #0x80

Übung: Was kommt heraus?

Versuche mental auszurechnen, was in den Registern steht:

mov x0, #10
mov x1, #4
mov x2, #2
add x3, x0, x1      // x3 = ?
mul x4, x3, x2      // x4 = ?
sub x5, x4, x1      // x5 = ?

Lösung anzeigen

x3 = 10 + 4 = 14
x4 = 14 * 2 = 28
x5 = 28 - 4 = 24

Negative Zahlen

Können Register negative Zahlen speichern?

Ja! Register speichern nur Bits (Nullen und Einsen). Ob eine Zahl positiv oder negativ ist, hängt davon ab, wie wir sie interpretieren.

mov x0, #10
mov x1, #15
sub x2, x0, x1      // x2 = 10 - 15 = -5

Computer verwenden das Zweierkomplement für negative Zahlen:

Binär:        11111111111111111111111111111111111111111111111111111111111111011
Als unsigned: 18446744073709551611 (riesige positive Zahl)
Als signed:   -5 (negative Zahl)

Die CPU rechnet einfach mit den Bits. Du entscheidest, ob es eine negative oder positive Zahl ist!

Speicher – Der große Bruder der Register

Register vs. Speicher

EigenschaftRegisterSpeicher (RAM)
GeschwindigkeitExtrem schnellLangsamer
Anzahl31 RegisterGigabytes
ZugriffDirekt per NameÜber Adressen

Daten im Speicher definieren

.global _start
.align 2

.data
meine_zahl:
    .quad 42            // 64-bit Zahl (8 Bytes)

andere_zahl:
    .quad 100

.text
_start:
    // Lade Zahlen aus dem Speicher
    adrp x0, meine_zahl@PAGE
    add x0, x0, meine_zahl@PAGEOFF
    ldr x1, [x0]        // x1 = 42

    adrp x0, andere_zahl@PAGE
    add x0, x0, andere_zahl@PAGEOFF
    ldr x2, [x0]        // x2 = 100

    add x3, x1, x2      // x3 = 142

    // Exit
    mov x0, #0
    mov x16, #1
    svc #0x80

Wichtige Befehle:

  • .quad – Definiert eine 64-Bit Zahl im Speicher
  • adrp + add – Lädt die Adresse (nicht den Wert!)
  • ldr x1, [x0]Load – Lies den Wert von der Adresse in x0

Adressen verstehen

Eine Adresse ist wie eine Hausnummer:

adrp x0, meine_zahl@PAGE
add x0, x0, meine_zahl@PAGEOFF
// x0 enthält jetzt die "Hausnummer" von meine_zahl

ldr x1, [x0]
// "Gehe zur Hausnummer in x0 und hole den Wert dort"
// x1 enthält jetzt: 42

Die eckigen Klammern [x0] bedeuten: „Der Wert AN der Adresse in x0“.

Daten in den Speicher schreiben

Store – Der Gegenspieler von Load

.global _start
.align 2

.data
ergebnis:
    .quad 0             // Startwert: 0

.text
_start:
    mov x1, #99         // x1 = 99

    // Adresse laden
    adrp x0, ergebnis@PAGE
    add x0, x0, ergebnis@PAGEOFF

    str x1, [x0]        // Schreibe x1 in den Speicher

    // Zur Kontrolle: wieder lesen
    ldr x2, [x0]        // x2 = 99

    // Exit
    mov x0, #0
    mov x16, #1
    svc #0x80

Load vs. Store:

  • ldr x1, [x0]Load = Lesen: Speicher → Register
  • str x1, [x0]Store = Schreiben: Register → Speicher

Praktisches Beispiel: Wert erhöhen

.global _start
.align 2

.data
zaehler:
    .quad 5

.text
_start:
    // Adresse laden
    adrp x0, zaehler@PAGE
    add x0, x0, zaehler@PAGEOFF

    // Wert lesen
    ldr x1, [x0]        // x1 = 5

    // Erhöhen
    add x1, x1, #1      // x1 = 6

    // Zurück schreiben
    str x1, [x0]        // zaehler = 6

    // Exit
    mov x0, #0
    mov x16, #1
    svc #0x80

Arrays – Mehrere Werte hintereinander

Ein Array definieren

Ein Array ist einfach eine Liste von Werten, die hintereinander im Speicher liegen:

.data
zahlen:
    .quad 10, 20, 30, 40, 50    // 5 Zahlen

Im Speicher sieht das so aus:

Adresse    Wert
0x1000     10
0x1008     20  (8 Bytes weiter, weil .quad = 8 Bytes)
0x1010     30
0x1018     40
0x1020     50

Array-Elemente lesen

.global _start
.align 2

.data
zahlen:
    .quad 100, 200, 300, 400

.text
_start:
    // Startadresse laden
    adrp x0, zahlen@PAGE
    add x0, x0, zahlen@PAGEOFF

    // Elemente mit Offset lesen
    ldr x1, [x0]        // x1 = 100 (Element 0)
    ldr x2, [x0, #8]    // x2 = 200 (Element 1, +8 Bytes)
    ldr x3, [x0, #16]   // x3 = 300 (Element 2, +16 Bytes)
    ldr x4, [x0, #24]   // x4 = 400 (Element 3, +24 Bytes)

    // Exit
    mov x0, #0
    mov x16, #1
    svc #0x80

Offset-Berechnung:

  • Element 0: Offset 0 (0 × 8)
  • Element 1: Offset 8 (1 × 8)
  • Element 2: Offset 16 (2 × 8)
  • Element 3: Offset 24 (3 × 8)

Array-Summe berechnen

.global _start
.align 2

.data
zahlen:
    .quad 5, 10, 15, 20, 25

.text
_start:
    // Startadresse laden
    adrp x0, zahlen@PAGE
    add x0, x0, zahlen@PAGEOFF

    mov x1, #0          // Summe = 0

    // Alle 5 Zahlen addieren
    ldr x2, [x0]
    add x1, x1, x2      // Summe += zahlen[0]

    ldr x2, [x0, #8]
    add x1, x1, x2      // Summe += zahlen[1]

    ldr x2, [x0, #16]
    add x1, x1, x2      // Summe += zahlen[2]

    ldr x2, [x0, #24]
    add x1, x1, x2      // Summe += zahlen[3]

    ldr x2, [x0, #32]
    add x1, x1, x2      // Summe += zahlen[4]

    // x1 = 5 + 10 + 15 + 20 + 25 = 75

    // Exit
    mov x0, #0
    mov x16, #1
    svc #0x80

Im nächsten Teil lernst du, wie man das mit Schleifen elegant löst!

Byte-Operationen

Nicht nur 64-Bit!

Manchmal brauchst du kleinere Datentypen:

  • .byte – 1 Byte (8 Bit)
  • .hword – 2 Bytes (16 Bit)
  • .word – 4 Bytes (32 Bit)
  • .quad – 8 Bytes (64 Bit)

Und entsprechende Load/Store-Befehle:

  • ldrb / strb – Byte (8 Bit)
  • ldrh / strh – Halfword (16 Bit)
  • ldr / str – Word/Doubleword (32/64 Bit)

Beispiel mit Bytes

.global _start
.align 2

.data
buchstaben:
    .byte 'A', 'B', 'C', 'D'

.text
_start:
    adrp x0, buchstaben@PAGE
    add x0, x0, buchstaben@PAGEOFF

    ldrb w1, [x0]       // w1 = 'A' (65)
    ldrb w2, [x0, #1]   // w2 = 'B' (66)
    ldrb w3, [x0, #2]   // w3 = 'C' (67)

    // Exit
    mov x0, #0
    mov x16, #1
    svc #0x80

Beachte: Bei Bytes ist der Offset +1 (nicht +8), weil Bytes nur 1 Byte groß sind!

Übungen

Übung 1: Einfache Berechnung

Schreibe ein Programm, das:

  1. x0 = 7, x1 = 3 setzt
  2. x2 = x0 + x1 berechnet
  3. x3 = x2 * 2 berechnet

Was enthält x3 am Ende?

Übung 2: Speicher-Manipulation

Erstelle ein Array mit drei Zahlen: 10, 20, 30. Lade alle drei, addiere sie, und schreibe das Ergebnis in eine vierte Variable im Speicher.

Übung 3: Mittelwert

Berechne den Mittelwert von drei Zahlen:

zahlen: .quad 6, 9, 12
// Mittelwert = (6 + 9 + 12) / 3 = 9

Hinweis: Für Division kannst du udiv verwenden:

udiv x2, x0, x1     // x2 = x0 / x1 (unsigned)

Zusammenfassung

In diesem Teil hast du gelernt:

  • ✅ Register sind schnelle Speicher in der CPU (x0-x30)
  • ✅ Arithmetik-Befehle: add, sub, mul
  • ✅ Negative Zahlen funktionieren automatisch (Zweierkomplement)
  • ✅ Speicher wird über Adressen angesprochen
  • ldr = Lesen aus Speicher, str = Schreiben in Speicher
  • ✅ Arrays sind aufeinanderfolgende Werte mit Offsets
  • ✅ Verschiedene Datengrößen: byte, hword, word, quad

Nächste Schritte

Im nächsten Teil lernst du Kontrollfluss:

  • Bedingungen (if/else)
  • Vergleiche
  • Sprünge (Branches)
  • Schleifen (Loops)

Damit wirst du dann richtig dynamische Programme schreiben können!


Zurück zu Teil 1 | Weiter zu Teil 3 →

2 Gedanken zu „ARM64 Assembly Tutorial – Teil 2: Register, Arithmetik und Speicher“

Die Kommentare sind geschlossen.