Assemblersprache - Makros
Ein Makro ist ein von NASM bereitgestellter Mechanismus zur Wiederverwendung von Code, mit dem Sie Codevorlagen zur Kompilierzeit erweitern können, wodurch das wiederholte Schreiben ähnlichen Codes reduziert wird.
Was ist ein Makro
MakroEs ist ein Textersetzungsmechanismus zur Kompilierzeit, der vom Präprozessor des Assemblers vor der Kompilierung erweitert wird.
Im Gegensatz zu Prozeduren haben Makros keinen CALL/RET-Overhead – jedes Mal, wenn ein Makro verwendet wird, kopiert der Assembler den Inhalt des Makros direkt an den Verwendungsort.
Ein Makro ist kein Funktionsaufruf und hat keinen Stapelrahmen-Overhead; jedoch wird die Größe der ausführbaren Datei größer, da bei jeder Erweiterung eine Kopie des Codes erstellt wird.
Einzeiliges Makro: %define
%defineEs ist ein einzeiliges Makro mit einfacher Syntax:
Beispiel
; Eine Konstante definieren
%define MAX_SIZE 256
%define APP_NAME 'example'
; Ein Makro mit Parametern definieren (Makrofunktion)
%define mul_by_2(x) (x * 2)
%define sum3(a, b, c) ((a) + (b) + (c))
section .text
global _start
_start:
mov eax, MAX_SIZE ; eax = 256
mov eax, mul_by_2(10) ; eax = (10 * 2) = 20
mov eax, sum3(1, 2, 3) ; eax = ((1)+(2)+(3)) = 6
; Neudefinition (%define kann geändert werden)
%define MAX_SIZE 512
mov eax, MAX_SIZE ; eax = 512
; Definition aufheben
%undef MAX_SIZE
; mov eax, MAX_SIZE ; Fehler: MAX_SIZE ist nicht definiert
mov eax, 1
mov ebx, 0
int 0x80
%defineDie Makroerweiterung ist eine reine Textersetzung. Zum Beispiel,mul_by_2(2+3)wird erweitert zu(2+3 * 2)ist das Ergebnis aufgrund der Operatorenrangfolge nicht 10, sondern 8. Aus diesem Grund müssen Makroparameter in Klammern eingeschlossen werden.
Mehrzeiliges Makro: %macro / %endmacro
%macroWird verwendet, um komplexe Makros mit mehreren Anweisungen zu definieren:
Beispiel
; Mehrzeiliges Makrobeispiel
; Makrodefinition: Programm beenden
; Parameter argc: wie viele Parameter das Makro akzeptiert
%macro exit_program 1
mov eax, 1 ; sys_exit
mov ebx, %1 ; Der erste Parameter wird als Exit-Code verwendet
int 0x80
%endmacro
; Makrodefinition: Zeichenkette drucken
; Akzeptiert 2 Parameter: Zeichenkettenadresse, Länge
%macro print_string 2
push eax
push ebx
push ecx
push edx
mov eax, 4 ; sys_write
mov ebx, 1 ; stdout
mov ecx, %1 ; String-Adresse
mov edx, %2 ; String-Länge
int 0x80
pop edx
pop ecx
pop ebx
pop eax
%endmacro
section .data
msg db 'Hello, EXAMPLE!', 0xA
msg_len equ $ - msg
section .text
global _start
_start:
print_string msg, msg_len ; Verwenden Sie das Makro, um eine Nachricht auszugeben
print_string msg, msg_len ; Erneut aufrufen
exit_program 0 ; Programm beenden
Erweiterte Verwendung von Makro-Parametern
NASM-Makros unterstützen Standardparameter, Parameterzählung und bedingte Expansion:
Beispiel
; Makro mit Standardparametern (Parameterbereich 2-3)
%macro debug_print 2-3 1 ; Mindestens 2 Parameter, höchstens 3, der 3. hat den Standardwert 1
%if %3 = 1 ; Wenn der 3. Parameter = 1 ist (Debug-Modus aktiviert)
push eax
push ebx
push ecx
push edx
mov eax, 4
mov ebx, 1
mov ecx, %1
mov edx, %2
int 0x80
pop edx
pop ecx
pop ebx
pop eax
%endif
%endmacro
; Makro mit einer variablen Anzahl von Parametern
%macro push_registers 1-* ; 1 bis zu einer beliebigen Anzahl von Parametern
%rep %0 ; %0 ist die Anzahl der Parameter
push %1 ; Den 1. expandieren
%rotate 1 ; Die Parameterliste nach links rotieren
%endrep
%endmacro
section .text
global _start
_start:
; push_registers verwenden, um mehrere Register zu sichern
push_registers eax, ebx, ecx, edx
; Wird expandiert zu:
; push eax
; push ebx
; push ecx
; push edx
; Entsprechendes pop
pop edx
pop ecx
pop ebx
pop eax
mov eax, 1
mov ebx, 0
int 0x80
Lokale Labels in Makros
Verwenden Sie in Makros%%labelum lokale Labels zu definieren und Label-Konflikte bei mehrfachen Expansionen zu vermeiden:
Beispiel
; Zwei Werte vergleichen und das Minimum setzen
%macro min_val 2
mov eax, %1
mov ebx, %2
cmp eax, ebx
jle %%skip ; ★ Lokales Label, für jede Expansion wird ein eindeutiger Name generiert
mov eax, ebx
%%skip:
%endmacro
; Wenn keine lokalen Labels verwendet werden, treten nach zwei Expansionen doppelte skip-Labels auf
section .text
global _start
_start:
min_val 10, 5 ; eax = 5
min_val eax, 3 ; eax = 3
mov eax, 1
mov ebx, 0
int 0x80
Das zweimalige Expandieren eines normalen Labels in einem Makro führt zu einem Label-Namenskonfliktfehler. Lokale Labels
%%labelsorgen dafür, dass NASM für jede Expansion einen eindeutigen Labelnamen generiert (wie[email protected]), wodurch Konflikte vermieden werden.
Vergleich von Makros und Prozeduren
| Merkmal | Makro | Prozedur |
|---|---|---|
| Implementierungsmethode | Textexpansion zur Compile-Zeit | Laufzeit-CALL/RET |
| Ausführender Overhead | Kein Call-Overhead (direktes Inlining) | Hat CALL/RET-Overhead (ungefähr ein paar Taktzyklen) |
| Codegröße | Jede Expansion vergrößert die Codegröße | Eine Kopie des Codes, die mehrmals aufgerufen wird |
| Parametertypen | Beliebiger Text (Register, Konstanten, Speicher) | Laufzeitwerte |
| Debugging | Schwierig (keine Ablaufverfolgung nach der Expansion) | Einfach (hat Funktionsaufruf-Stack) |
| Anwendbare Szenarien | Kurzer, häufig aufgerufener und typflexibler Code | Funktionen mit komplexer Logik und großer Codegröße |
Bedingte Kompilierung: %if / %elif / %else / %endif
Der Makro-Präprozessor unterstützt bedingte Kompilierung, mit der basierend auf Symboldefinitionen verschiedener Code ausgewählt und generiert werden kann:
Beispiel
; Beispiel für bedingte Kompilierung: Debug- und Release-Modus
%define DEBUG 1 ; 1=Debug-Modus, 0=Release-Modus
section .data
msg db 'Program running...', 0xA
msg_len equ $ - msg
debug_msg db '[DEBUG] Entering function', 0xA
debug_len equ $ - debug_msg
section .text
global _start
_start:
%if DEBUG = 1
; Debug-Modus: Debug-Informationen ausgeben
mov eax, 4
mov ebx, 1
mov ecx, debug_msg
mov edx, debug_len
int 0x80
%endif
; Normale Geschäftslogik
mov eax, 4
mov ebx, 1
mov ecx, msg
mov edx, msg_len
int 0x80
mov eax, 1
mov ebx, 0
int 0x80
Häufige Szenarien für die bedingte Kompilierung:
| Szenario | Beispiel |
|---|---|
| Umschalten zwischen Debug und Release | %if DEBUG / %else / %endif |
| Plattformanpassung | %ifdef LINUX / %ifdef WINDOWS |
| Funktionsschalter | Steuerung von Funktionen danach, ob ein Symbol definiert ist |
%rep Wiederholungsblock
%repWird verwendet, um wiederholten Code oder Daten zu generieren:
Beispiel
section .data
; Generieren einer 256-Byte-Nachschlagetabelle
; 0, 1, 4, 9, 16, 25, ... (Quadratzahlen-Tabelle)
%assign i 0
; Hier kann %rep nicht verwendet werden, um eine Quadratzahlentabelle für komplexe Berechnungen zu generieren
; Einfaches Beispiel: ASCII-Tabelle für 0-9 generieren
digits: db '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
section .text
global _start
_start:
; %rep wiederholt Anweisungen im Code
mov eax, 0
%rep 5 ; 5 mal wiederholen
inc eax ; eax wird jedes Mal um 1 erhöht
%endrep
; eax = 5
mov ebx, eax
mov eax, 1
int 0x80
Weitere ErweiterungenDas Übertreiben mit Makros macht den Code schwer lesbar und zu debuggen. Wenn der Makrokörper 10 Zeilen überschreitet, sollten Sie einen Prozeduraufruf in Betracht ziehen. In leistungskritischen Hot-Pfaden hat der Inlining-Effekt kurzer Makros offensichtliche Vorteile.