Assemblersprache - Prozeduren (Unterprogramme)
Eine Prozedur ist ein Mechanismus in der Assemblersprache zur Implementierung der Wiederverwendung von Funktionen und Unterprogrammen, wodurch Code strukturiert und wiederverwendbar wird. Das Verständnis der Beziehung zwischen Prozeduren und dem Stack ist ein wichtiger Meilenstein in der Assemblerprogrammierung.
Was ist eine Prozedur
Eine Prozedurist ein Codeblock, der mehrfach aufgerufen werden kann, ähnlich wie Funktionen oder Unterprogramme in höheren Programmiersprachen.
In Assembler wird eine Prozedur aufgerufen über dieCALLCALL-Anweisung und kehrt zurück über dieRETRET-Anweisung.CALLSie legt die Rücksprungadresse auf dem Stack ab,RETund sie holt die Rücksprungadresse vom Stack und springt zurück.
Beispiel
; Einfachstes Beispiel für einen Prozeduraufruf
section .data
msg db 'Hello, example!', 0xA
len equ $ - msg
section .text
global _start
_start:
call print_message ; Prozedur aufrufen (Adresse der nächsten Anweisung auf den Stack legen)
call print_message ; Erneut aufrufen
mov eax, 1
mov ebx, 0
int 0x80
; Prozedur: Nachricht ausgeben
print_message:
push eax ; Zu modifizierende Register speichern
push ebx
push ecx
push edx
mov eax, 4
mov ebx, 1
mov ecx, msg
mov edx, len
int 0x80
pop edx ; Register wiederherstellen (in umgekehrter Reihenfolge)
pop ecx
pop ebx
pop eax
ret ; Zum Aufrufer zurückkehren (Rücksprungadresse vom Stack nehmen)
Innerhalb einer Prozedur sollten Sie alle modifizierten Register speichern und wiederherstellen (außer EAX, wenn es den Rückgabewert enthält). Dies ist grundlegende Etikette in der Assemblerprogrammierung. Wenn Sie dies nicht tun, werden die Registerwerte des Aufrufers unerwartet verändert.
CALL- und RET-Prinzipien
CALLundRETSie arbeiten paarweise und nutzen den Stack zur Verwaltung von Rücksprungadressen:
| Anweisung | Tatsächlich ausgeführte Operation |
|---|---|
CALL label | push eip(Adresse der nächsten Anweisung speichern) +jmp label |
RET | pop eip(Adresse vom Stack nehmen und springen) |
Übergabe von Parametern über Register
Bevor eine Prozedur aufgerufen wird, werden die Parameter in vereinbarten Registern abgelegt:
Beispiel
; Parameterübergabe über Register
section .data
newline db 0xA
section .text
global _start
_start:
; Prozedur add_two aufrufen: 10 + 20 berechnen
mov eax, 10 ; Ersten Parameter in eax ablegen
mov ebx, 20 ; Zweiten Parameter in ebx ablegen
call add_two ; Prozedur aufrufen
; Rückgabewert befindet sich in eax = 30
mov ebx, eax ; Exit-Code = 30
mov eax, 1
int 0x80
; Prozedur: eax + ebx berechnen, Ergebnis in eax zurückgeben
add_two:
add eax, ebx ; eax = eax + ebx
ret
Übergabe von Parametern über den Stack
Die Übergabe von Parametern über den Stack ist flexibler und die Standardmethode für höhere Programmiersprachen wie C:
Beispiel
; Parameterübergabe über den Stack (im cdecl-Stil)
section .data
msg db 'Sum is: '
msg_len equ $ - msg
newline db 0xA
section .bss
result_buf resb 4
section .text
global _start
_start:
; Prozedur sum aufrufen: 100 + 200 berechnen
push dword 200 ; Zweiten Parameter auf den Stack legen
push dword 100 ; Ersten Parameter auf den Stack legen
call sum ; Prozedur aufrufen
add esp, 8 ; ★ Aufrufer bereinigt den Stack (cdecl-Konvention)
; Rückgabewert befindet sich in eax = 300
mov ebx, eax
mov eax, 1
int 0x80
; Prozedur: sum(a, b) = a + b
sum:
push ebp ; ★ Alten Basiszeiger sichern
mov ebp, esp ; ★ Neue Stack-Frame-Basis einrichten
; Stack-Layout (von hoher Adresse zu niedriger Adresse):
; [ebp+12] = Parameter b (200)
; [ebp+8] = Parameter a (100)
; [ebp+4] = Rücksprungadresse
; [ebp] = alter ebp
; [ebp-4] = Speicher für lokale Variablen
mov eax, [ebp + 8] ; Ersten Parameter (a) abrufen
add eax, [ebp + 12] ; Zweiten Parameter (b) addieren
pop ebp ; ★ Alten Basiszeiger wiederherstellen
ret ; Zurückkehren
Diagramm der Stack-Frame-Struktur:
Das Folgende ist die entsprechende Textdarstellung:
高地址 +------------------+ | 参数2 (200) | <-- [ebp + 12] +------------------+ | 参数1 (100) | <-- [ebp + 8] +------------------+ | 返回地址 | <-- [ebp + 4] +------------------+ | 旧的 EBP | <-- [ebp] (当前 EBP 指向这里) +------------------+ | 局部变量区 | <-- [ebp - 4], [ebp - 8], ... +------------------+ <-- ESP 指向这里 低地址
Funktions-Prologue
push ebp; mov ebp, espund Epiloguepop ebp; ret(oderleave; ret) ist die Standardvorlage für die Stack-Frame-Verwaltung; fast alle Assemblerfunktionen beginnen und enden mit dieser Struktur.
Rückgabewerte von Prozeduren
Rückgabewerte werden normalerweise im EAX-Register (32-Bit-Wert) oder in EDX:EAX (64-Bit-Wert) gespeichert:
Beispiel
; int zurückgeben
call get_answer
; eax = 42
; 64-Bit-Wert zurückgeben (z. B. long long)
call get_big_value
; edx:eax = 64-Bit-Ergebnis
get_answer:
mov eax, 42 ; Rückgabewert in eax abgelegt
ret
get_big_value:
mov eax, 0x00000001 ; Niedere 32 Bit
mov edx, 0x00000000 ; Höhere 32 Bit
ret
Lokale Variablen
Lokale Variablen verwenden Stack-Speicher und werden im Prozedur-Prolog durch Verringern von ESP zugewiesen:
Beispiel
; Lokale Variablen in einer Prozedur verwenden
section .text
global _start
_start:
push dword 10
push dword 20
call multiply_add
add esp, 8
; eax = 10 * 20 + 10 + 20 = 230
mov ebx, eax
mov eax, 1
int 0x80
; Prozedur: multiply_add(x, y) = x*y + x + y
multiply_add:
push ebp
mov ebp, esp
sub esp, 8 ; ★ 8 Bytes Speicher für lokale Variablen auf dem Stack reservieren
; Nun sind [ebp-4] und [ebp-8] verfügbare lokale Variablen
mov eax, [ebp + 8] ; x
mov ebx, [ebp + 12] ; y
; [ebp-4] = x * y
imul eax, ebx
mov [ebp - 4], eax ; Lokale Variable 1 = x * y
; [ebp-8] = x + y
mov eax, [ebp + 8]
add eax, [ebp + 12]
mov [ebp - 8], eax ; Lokale Variable 2 = x + y
; Rückgabewert = [ebp-4] + [ebp-8]
mov eax, [ebp - 4]
add eax, [ebp - 8]
; ★ Speicher für lokale Variablen bereinigen und wiederherstellen
mov esp, ebp ; Äquivalent zu add esp, 8
pop ebp
ret
Lokale Variablen werden im Stack-Frame zugewiesen und automatisch freigegeben, wenn die Prozedur zurückkehrt. Verwendung von
leaveDie Anweisung kann ersetzenmov esp, ebp; pop ebp, mit äquivalentem Effekt.
Vergleich von Prozedur-Aufrufkonventionen
| Konvention | Parameterübergabe | Stack-Bereinigung | Registererhaltung | Rückgabewert |
|---|---|---|---|---|
| cdecl | Stack (von rechts nach links gepusht) | Aufrufer | EAX, ECX, EDX werden vom Aufrufer gesichert | EAX |
| stdcall | Stack (von rechts nach links gepusht) | Aufgerufene Funktion | EAX, ECX, EDX werden vom Aufrufer gesichert | EAX |
| fastcall | ECX, EDX + Stack | Aufgerufene Funktion | EAX, ECX, EDX werden vom Aufrufer gesichert | EAX |