Makros in Assembler

Aus C und Assembler mit Raspberry

Makros sind ein mächtiges Werkzeug im Assembler, das die Wiederverwendung von Code erleichtert. Sie funktionieren ähnlich wie Funktionen in Hochsprachen, jedoch auf Quelltextebene – d. h. sie werden vom Assembler vor dem eigentlichen Assemblierungsvorgang expandiert.

Was sind Makros?

Ein Makro ist eine benannte Codevorlage, die beim Aufruf durch die entsprechende Makrodefinition ersetzt wird. Dadurch kann redundanter Code vermieden und die Lesbarkeit verbessert werden.

Makros werden mit den Direktiven .macro und .endm definiert.

Syntax

.macro name [Parameter[, Parameter...]]
    ; Anweisungen
.endm

Einfaches Beispiel

.macro save_regs reg1, reg2
    str \reg1, [sp, #-16]!
    str \reg2, [sp, #-16]!
.endm

Aufruf:

    save_regs x0, x1

Parameter in Makros

Makros können benannte Parameter enthalten, die beim Aufruf ersetzt werden. Parameter werden mit einem umgekehrten Schrägstrich (`\`) verwendet, um sie im Makro zu referenzieren.

Beispiel mit drei Parametern

.macro add_and_store dest, src1, src2
    add \dest, \src1, \src2
    str \dest, [sp, #-16]!
.endm

Aufruf:

    add_and_store x3, x1, x2

Makros mit Labels

Ein Problem bei Makros kann die Wiederverwendung von Labels sein. Labels innerhalb eines Makros sollten lokal sein, sonst kann es bei mehrfacher Verwendung zu Namenskonflikten kommen.

Lösung: Lokale Labels verwenden

Verwende numerische Labels oder eindeutige Namen mit \@:

.macro blink delay
1:
    nop
    subs \delay, \delay, #1
    bne 1b
.endm

Oder mit \@ für eindeutige Labels:

.macro wait_loop delay
loop\@:
    subs \delay, \delay, #1
    bne loop\@
.endm

Verschachtelte Makros und Bedingungen

Makros dürfen auch andere Makros aufrufen oder einfache Bedingungen enthalten:

.macro print_val reg
    mov x0, \reg
    mov x8, #64   // write syscall
    svc #0
.endm

Bedingte Makro-Ersetzungen in GAS (GNU Assembler)

GNU as erlaubt präprozessorartige Bedingungen mit folgenden Direktiven:

  • .if, .ifdef, .ifndef, .else, .elseif, .endif
  • .irp, .irpc, .rept – für Wiederholungen
  • .macro – mit bedingten Code-Blöcken innerhalb der Makrodefinition

Beispiel: Bedingung basierend auf Parameterwert

.macro load_value reg, val
.ifc \val, #0
    mov \reg, xzr
.else
    mov \reg, \val
.endif
.endm

Erklärung:

  • .ifc A, B prüft, ob die Strings A und B gleich sind
  • .ifnc prüft, ob sie nicht gleich sind

Aufruf:

    load_value x0, #0      // ergibt: mov x0, xzr
    load_value x1, #42     // ergibt: mov x1, #42

Weitere Beispiele:

.macro set_direction pin, dir
.ifc \dir, out
    // mache was für Ausgang
.elseifc \dir, in
    // mache was für Eingang
.else
    .error "Unbekannter Parameter bei set_direction"
.endif
.endm

Einschränkungen:

  • Es handelt sich nicht um Laufzeitbedingungen, sondern Assemblerzeitbedingungen
  • Es ist nur ein Stringvergleich möglich, keine numerischen Bedingungen wie if \param > 5
  • Du kannst .if, .ifc, .ifdef, .ifne, .ifeq u.a. verwenden

Übersicht der Direktiven

Direktive Bedeutung
.if expr Wenn `expr` ≠ 0
.ifeq expr Wenn `expr` = 0
.ifne expr Wenn `expr` ≠ 0
.ifc A, B Wenn A = B (String)
.ifnc A, B Wenn A ≠ B
.ifdef symbol Wenn Symbol definiert
.ifndef symbol Wenn nicht definiert
.else, .endif, .elseif wie in C

Tipps zur Makro-Programmierung

  • Verwende eindeutige Labels innerhalb von Makros.
  • Teste Makros isoliert, bevor du sie mehrfach einsetzt.
  • Makros sind zur Compilezeit sichtbar – Fehler treten oft erst beim Expandieren auf.
  • Nutze `\@` für eindeutige IDs (z. B. für Labels).
  • Makros können nicht rekursiv aufgerufen werden (kein Call-Stack).

Nützliche Anwendungen von Makros

  • Wiederholte Codeblöcke kapseln (z. B. Register sichern/wiederherstellen)
  • Debug-Ausgaben standardisieren
  • GPIO-Ansteuerung vereinfachen
  • Speicherzugriffe vereinheitlichen

Einschränkungen

  • Keine Rückgabewerte
  • Keine Laufzeitparameterprüfung
  • Nur syntaktische Ersetzung – kein Kontrollfluss wie bei echten Funktionen

Praktische Makros

.macro push1 register
str \register,[sp,#-16]!
.endm
.macro push2 register1,register2
stp \register1,\register2,[sp,#-16]!
.endm
.macro pop1 register
ldr \register,[sp],#16
.endm
.macro pop2 register1,register2
ldp \register1,\register2,[sp],#16
.endm

Zusammenfassung

Makros im ARM64-Assembler helfen dir dabei, deine Programme kürzer, wartbarer und verständlicher zu schreiben. Richtig eingesetzt, erhöhen sie die Produktivität erheblich – besonders bei Hardwarezugriffen, Systemaufrufen und Wiederholungen.