Funktionen und Stack

Aus C und Assembler mit Raspberry
Version vom 3. April 2025, 12:35 Uhr von Satyria (Diskussion | Beiträge) (Die Seite wurde neu angelegt: „Funktionen und Stack 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 Funktionsparameter und Rückgabewerte Verwendung des Stacks für lokale Variablen und Funktionsaufrufe Regeln für das Aufrufen von Funktionen (AAPCS) Funktion aufrufen und Rückkehr Eine Funktion im ARM64-Assembler wird dur…“)
(Unterschied) ← Nächstältere Version | Aktuelle Version (Unterschied) | Nächstjüngere Version → (Unterschied)

Funktionen und Stack

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 Funktionsparameter und Rückgabewerte Verwendung des Stacks für lokale Variablen und Funktionsaufrufe Regeln für das Aufrufen von Funktionen (AAPCS) 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

Der Stack wird verwendet, um lokale Variablen zu speichern und den aktuellen Kontext bei verschachtelten Funktionsaufrufen zu sichern. Dies geschieht mit den Befehlen stp (store pair) und ldp (load pair), die Paare von Registern auf dem Stack speichern und wiederherstellen können.

Beispiel:

.global _start

_start:

   mov x0, #10           // Parameter 1: 10
   mov x1, #20           // Parameter 2: 20
   bl add_numbers_with_stack // Rufe add_numbers_with_stack auf
   // Das Ergebnis (30) ist jetzt in x0
   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
   mov x29, sp                // Setze den Frame-Pointer
   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
   ret                        // Kehre zurück

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


Mit diesen Informationen können Anfänger Funktionen erstellen, Parameter übergeben, Rückgabewerte verarbeiten und den Stack für lokale Variablen und verschachtelte Funktionsaufrufe verwenden, während sie den AAPCS-Regeln folgen.