Funktionen und Stack: Unterschied zwischen den Versionen
KKeine Bearbeitungszusammenfassung |
|||
| (Eine dazwischenliegende Version desselben Benutzers wird nicht angezeigt) | |||
| Zeile 1: | Zeile 1: | ||
In diesem Kapitel werden wir uns mit der Erstellung und Verwendung von Funktionen im ARM64-Assembler beschäftigen. Insbesondere werden wir uns mit den folgenden Themen befassen: | In diesem Kapitel werden wir uns mit der Erstellung und Verwendung von Funktionen im ARM64-Assembler beschäftigen. Insbesondere werden wir uns mit den folgenden Themen befassen: | ||
== Funktion aufrufen und Rückkehr == | == Funktion aufrufen und Rückkehr == | ||
| Zeile 48: | Zeile 43: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
== Verwendung des Stacks für lokale Variablen und Funktionsaufrufe == | == Verwendung des Stacks für lokale Variablen und Funktionsaufrufe == | ||
In vielen Programmiersprachen einschließlich C, werden Parameter häufig über den Stack übergeben, besonders wenn viele Parameter übergeben werden oder wenn die Parameter größer sind als Register (z.B. Strukturen). Im Fall von ARM64-Assembler wird jedoch oft eine Kombination verwendet: Die ersten paar Parameter werden über Register übergeben und zusätzliche Parameter über den Stack. | |||
Wie dies vereinfacht funktioniert, wird hier ein Beispiel gezeigt, das die Übergabe der Parameter über den Stack zeigt und dann die Berechnung innerhalb der Funktion durchführt: | |||
<syntaxhighlight lang="asm"> | <syntaxhighlight lang="asm"> | ||
.global _start | .global _start | ||
_start: | _start: | ||
mov x0, #10 | mov x0, #10 // Parameter 1: 10 | ||
mov x1, #20 | mov x1, #20 // Parameter 2: 20 | ||
str x0, [sp, #-8]! // Speicher Parameter 1 (10) auf dem Stack; Stack-Pointer adjustiert | |||
str x1, [sp, #-8]! // Speicher Parameter 2 (20) auf dem Stack; Stack-Pointer adjustiert | |||
bl add_numbers_with_stack // Rufe add_numbers_with_stack auf | bl add_numbers_with_stack // Rufe add_numbers_with_stack auf | ||
// Das Ergebnis (30) ist jetzt in x0 | // Das Ergebnis (30) ist jetzt in x0 | ||
mov x8, #93 | add sp, sp, #16 // Stack-Pointer zurücksetzen | ||
svc 0 | mov x8, #93 // exit system call | ||
svc 0 // system call | |||
add_numbers_with_stack: | add_numbers_with_stack: | ||
stp x29, x30, [sp, #-16]! // Speichere x29 (FP) und x30 (LR) auf dem Stack | stp x29, x30, [sp, #-16]! // Speichere x29 (FP) und x30 (LR) auf dem Stack; Stack-Pointer adjustiert | ||
mov x29, sp | mov x29, sp // Setze den Frame-Pointer | ||
add x0, x0, x1 | ldr x0, [x29, #16] // Lade Parameter 1 (10) vom Stack | ||
ldp x29, x30, [sp], #16 // Lade x29 und x30 vom Stack zurück | ldr x1, [x29, #8] // Lade Parameter 2 (20) vom Stack | ||
ret | add x0, x0, x1 // Addiere x0 (10) und x1 (20), Ergebnis in x0 (30) | ||
ldp x29, x30, [sp], #16 // Lade x29 und x30 vom Stack zurück; Stack-Pointer adjustiert | |||
ret // Kehre zurück | |||
</syntaxhighlight> | |||
=== Schritt für Schritt Erklärung === | |||
* Parameter vorbereiten und auf den Stack legen: | |||
<syntaxhighlight lang="asm"> | |||
mov x0, #10 // Parameter 1: 10 in Register x0 | |||
mov x1, #20 // Parameter 2: 20 in Register x1 | |||
str x0, [sp, #-8]! // Speicher x0 (10) auf den Stack und adjustiere den Stack-Pointer | |||
str x1, [sp, #-8]! // Speicher x1 (20) auf den Stack und adjustiere den Stack-Pointer | |||
</syntaxhighlight> | |||
Hier werden die Werte 10 und 20 in die Register x0 und x1 geladen und dann auf den Stack gelegt. | |||
* Funktionsaufruf: | |||
<syntaxhighlight lang="asm"> | |||
bl add_numbers_with_stack // Rufe add_numbers_with_stack auf | |||
</syntaxhighlight> | |||
* Speichern von Frame-Pointer und Link-Register: | |||
<syntaxhighlight lang="asm"> | |||
stp x29, x30, [sp, #-16]! // Speichere x29 (FP) und x30 (LR) auf den Stack und adjustiere den Stack-Pointer | |||
mov x29, sp // Setze den Frame-Pointer (x29) | |||
</syntaxhighlight> | |||
stp speichert die Register x29 und x30 auf dem Stack und adjustiert den Stack-Pointer. Danach wird der Frame-Pointer aktualisiert. | |||
* Laden der Parameter und Berechnung: | |||
<syntaxhighlight lang="asm"> | |||
ldr x0, [x29, #16] // Lade Parameter 1 (10) vom Stack | |||
ldr x1, [x29, #8] // Lade Parameter 2 (20) vom Stack | |||
add x0, x0, x1 // Addiere x0 (10) und x1 (20), Ergebnis in x0 (30) | |||
</syntaxhighlight> | |||
Die Parameter werden vom Stack in die Register x0 und x1 geladen und die Addition wird durchgeführt. Das Ergebnis wird in x0 gespeichert. | |||
* Wiederherstellung des Kontextes und Funktionsrückkehr: | |||
<syntaxhighlight lang="asm"> | |||
ldp x29, x30, [sp], #16 // Lade x29 und x30 vom Stack zurück und adjustiere den Stack-Pointer | |||
ret // Kehre zurück | |||
</syntaxhighlight> | </syntaxhighlight> | ||
ldp lädt die Register x29 und x30 vom Stack und stellt den Stack-Pointer wieder her. Der ret-Befehl kehrt zur aufrufenden Funktion zurück. | |||
* Stack-Pointer zurücksetzen und Beenden: | |||
<syntaxhighlight lang="asm"> | |||
add sp, sp, #16 // Stack-Pointer zurücksetzen, um zuvor gespeicherte Parameter zu entfernen | |||
mov x8, #93 // exit system call | |||
svc 0 // system call | |||
</syntaxhighlight> | |||
Der Stack-Pointer wird auf seinen ursprünglichen Zustand zurückgesetzt, bevor das Programm beendet wird. | |||
=== Wichtige Informationen === | |||
Parameter Übergabe über den Stack: Beweg die Register auf den Stack, um sie an die Funktion zu übergeben. | |||
* Frame-Pointer (x29): Verwendet zur Verwaltung des Stack-Frames innerhalb der Funktion. | |||
* Link-Register (x30): Hält die Rücksprungadresse für die Rückkehr zur aufrufenden Funktion. | |||
* Stack-Pointer: Wird angepasst, um den Stack für lokale Variablen und Parameter zu nutzen. | |||
Diese Methode stellt sicher, dass bei verschachtelten Funktionsaufrufen und bei der Verwendung vieler Parameter der Kontext korrekt gesichert und wiederhergestellt wird. | |||
=== Problem des "LR" bei verschachtelten Funktionsaufrufen === | === Problem des "LR" bei verschachtelten Funktionsaufrufen === | ||
| Zeile 99: | Zeile 157: | ||
ret | ret | ||
</syntaxhighlight> | </syntaxhighlight> | ||
== Regeln für das Aufrufen von Funktionen (AAPCS) == | == Regeln für das Aufrufen von Funktionen (AAPCS) == | ||
Aktuelle Version vom 8. April 2025, 13:21 Uhr
In diesem Kapitel werden wir uns mit der Erstellung und Verwendung von Funktionen im ARM64-Assembler beschäftigen. Insbesondere werden wir uns mit den folgenden Themen befassen:
Funktion aufrufen und Rückkehr
Eine Funktion im ARM64-Assembler wird durch das Setzen eines Labels deklariert und kann mit dem Befehl bl (Branch with Link) aufgerufen werden. Der bl-Befehl speichert die Rücksprungadresse in das Register lr (Link Register, auch bekannt als x30), wodurch die Funktion nach ihrem Abschluss dorthin zurückkehren kann.
Beispiel:
.global _start
_start:
mov x0, #10 // Setze x0 auf 10
bl my_function // Rufe my_function auf
// Weiterer Code nach der Rückkehr von my_function
mov x8, #93 // exit system call
svc 0 // system call
my_function:
// Funktion beginnt hier
mov x0, #0 // Setze x0 auf 0, als Beispiel
ret // Kehre zur Rücksprungadresse zurück
Funktionsparameter und Rückgabewerte
In ARM64-Assembler werden Funktionsparameter in den Registern x0 bis x7 übergeben. Rückgabewerte werden üblicherweise in x0 zurückgegeben.
Beispiel:
.global _start
_start:
mov x0, #10 // Parameter 1: 10
mov x1, #20 // Parameter 2: 20
bl add_numbers // Rufe add_numbers auf
// Das Ergebnis (30) ist jetzt in x0
mov x8, #93 // exit system call
svc 0 // system call
add_numbers:
add x0, x0, x1 // Addiere x0 (10) und x1 (20), Ergebnis in x0 (30)
ret // Kehre zurück
Verwendung des Stacks für lokale Variablen und Funktionsaufrufe
In vielen Programmiersprachen einschließlich C, werden Parameter häufig über den Stack übergeben, besonders wenn viele Parameter übergeben werden oder wenn die Parameter größer sind als Register (z.B. Strukturen). Im Fall von ARM64-Assembler wird jedoch oft eine Kombination verwendet: Die ersten paar Parameter werden über Register übergeben und zusätzliche Parameter über den Stack.
Wie dies vereinfacht funktioniert, wird hier ein Beispiel gezeigt, das die Übergabe der Parameter über den Stack zeigt und dann die Berechnung innerhalb der Funktion durchführt:
.global _start
_start:
mov x0, #10 // Parameter 1: 10
mov x1, #20 // Parameter 2: 20
str x0, [sp, #-8]! // Speicher Parameter 1 (10) auf dem Stack; Stack-Pointer adjustiert
str x1, [sp, #-8]! // Speicher Parameter 2 (20) auf dem Stack; Stack-Pointer adjustiert
bl add_numbers_with_stack // Rufe add_numbers_with_stack auf
// Das Ergebnis (30) ist jetzt in x0
add sp, sp, #16 // Stack-Pointer zurücksetzen
mov x8, #93 // exit system call
svc 0 // system call
add_numbers_with_stack:
stp x29, x30, [sp, #-16]! // Speichere x29 (FP) und x30 (LR) auf dem Stack; Stack-Pointer adjustiert
mov x29, sp // Setze den Frame-Pointer
ldr x0, [x29, #16] // Lade Parameter 1 (10) vom Stack
ldr x1, [x29, #8] // Lade Parameter 2 (20) vom Stack
add x0, x0, x1 // Addiere x0 (10) und x1 (20), Ergebnis in x0 (30)
ldp x29, x30, [sp], #16 // Lade x29 und x30 vom Stack zurück; Stack-Pointer adjustiert
ret // Kehre zurück
Schritt für Schritt Erklärung
- Parameter vorbereiten und auf den Stack legen:
mov x0, #10 // Parameter 1: 10 in Register x0
mov x1, #20 // Parameter 2: 20 in Register x1
str x0, [sp, #-8]! // Speicher x0 (10) auf den Stack und adjustiere den Stack-Pointer
str x1, [sp, #-8]! // Speicher x1 (20) auf den Stack und adjustiere den Stack-Pointer
Hier werden die Werte 10 und 20 in die Register x0 und x1 geladen und dann auf den Stack gelegt.
- Funktionsaufruf:
bl add_numbers_with_stack // Rufe add_numbers_with_stack auf
- Speichern von Frame-Pointer und Link-Register:
stp x29, x30, [sp, #-16]! // Speichere x29 (FP) und x30 (LR) auf den Stack und adjustiere den Stack-Pointer
mov x29, sp // Setze den Frame-Pointer (x29)
stp speichert die Register x29 und x30 auf dem Stack und adjustiert den Stack-Pointer. Danach wird der Frame-Pointer aktualisiert.
- Laden der Parameter und Berechnung:
ldr x0, [x29, #16] // Lade Parameter 1 (10) vom Stack
ldr x1, [x29, #8] // Lade Parameter 2 (20) vom Stack
add x0, x0, x1 // Addiere x0 (10) und x1 (20), Ergebnis in x0 (30)
Die Parameter werden vom Stack in die Register x0 und x1 geladen und die Addition wird durchgeführt. Das Ergebnis wird in x0 gespeichert.
- Wiederherstellung des Kontextes und Funktionsrückkehr:
ldp x29, x30, [sp], #16 // Lade x29 und x30 vom Stack zurück und adjustiere den Stack-Pointer
ret // Kehre zurück
ldp lädt die Register x29 und x30 vom Stack und stellt den Stack-Pointer wieder her. Der ret-Befehl kehrt zur aufrufenden Funktion zurück.
- Stack-Pointer zurücksetzen und Beenden:
add sp, sp, #16 // Stack-Pointer zurücksetzen, um zuvor gespeicherte Parameter zu entfernen
mov x8, #93 // exit system call
svc 0 // system call
Der Stack-Pointer wird auf seinen ursprünglichen Zustand zurückgesetzt, bevor das Programm beendet wird.
Wichtige Informationen
Parameter Übergabe über den Stack: Beweg die Register auf den Stack, um sie an die Funktion zu übergeben.
- Frame-Pointer (x29): Verwendet zur Verwaltung des Stack-Frames innerhalb der Funktion.
- Link-Register (x30): Hält die Rücksprungadresse für die Rückkehr zur aufrufenden Funktion.
- Stack-Pointer: Wird angepasst, um den Stack für lokale Variablen und Parameter zu nutzen.
Diese Methode stellt sicher, dass bei verschachtelten Funktionsaufrufen und bei der Verwendung vieler Parameter der Kontext korrekt gesichert und wiederhergestellt wird.
Problem des "LR" bei verschachtelten Funktionsaufrufen
Bei verschachtelten Funktionsaufrufen (eine Funktion ruft eine andere auf) muss das lr-Register gesichert werden, um sicherzustellen, dass die Rücksprungadresse korrekt erhalten bleibt. Dies geschieht typischerweise durch das Speichern von lr auf dem Stack bei jedem Funktionsaufruf.
Beispiel:
.global _start
_start:
mov x0, #10
mov x1, #20
bl outer_function
mov x8, #93
svc 0
outer_function:
stp x29, x30, [sp, #-16]!
mov x29, sp
bl inner_function
ldp x29, x30, [sp], #16
ret
inner_function:
stp x29, x30, [sp, #-16]!
mov x29, sp
add x0, x0, x1
ldp x29, x30, [sp], #16
ret
Regeln für das Aufrufen von Funktionen (AAPCS)
Die ARM Architecture Procedure Call Standard (AAPCS) legt Regeln für das Aufrufen von Funktionen fest, um die Interoperabilität zwischen verschiedenen Code-Modulen sicherzustellen.
Wichtige Regeln des AAPCS
- Parameterübergabe: Die ersten acht Parameter werden in den Registern x0 bis x7 übergeben. Weitere Parameter werden auf dem Stack übergeben.
- Rückgabewerte: Rückgabewerte werden in x0 und x1 zurückgegeben, abhängig von der Größe des Rückgabewerts.
- Callee-saved Register: Register x19 bis x29 müssen von der aufgerufenen Funktion gesichert und wiederhergestellt werden. Register x0 bis x18 und x30 (LR) können von der Funktion frei verwendet werden.
- Stack Alignment: Der Stack muss immer auf eine 16-Byte-Grenze ausgerichtet sein.
Beispiel für eine Funktion, die sich an AAPCS hält:
global _start
_start:
mov x0, #10
mov x1, #20
bl add_numbers // Aufruf der Funktion
mov x8, #93 // exit system call
svc 0 // system call
add_numbers:
stp x29, x30, [sp, #-16]! // Speichere x29 (FP) und x30 (LR) auf dem Stack
mov x29, sp // Setze den Frame-Pointer
stp x19, x20, [sp, #-16]! // Speichere x19 und x20 (callee-saved Register)
mov x19, x0 // Benutze x19 als temporäres Register
mov x20, x1 // Benutze x20 als temporäres Register
add x0, x19, x20 // Addiere x0 und x1 und speichere das Ergebnis in x0
ldp x19, x20, [sp], #16 // Lade x19 und x20 vom Stack zurück
ldp x29, x30, [sp], #16 // Lade x29 und x30 vom Stack zurück
ret // Kehre zur Rücksprungadresse zurück