Dein Problem ist, dass du den Stackframe deiner Funktion falsch erstellst bzw. zerstörst.
Jede Funktion hat eine Startadresse, an der ihr Speicher beginnt. Wenn du eine Funktion aufrufst, erwartet der Aufrufer, dass du diese Startadresse unverändert lässt. Willst du sie also ändern, musst du sie innerhalb deiner Funktion speichern und am Ende wieder auf den ursprünglichen Wert zurücksetzen. Diese Basis-Adresse befindet sich immer im Register %ebp (bzw. %rbp auf 64 bit Systemen).
Du schreibst allerdings ohne jede Vorbereitung auf folgende Adressen:
Code: Alles auswählen
movl %eax, -4(%ebp)
movl %eax, -8(%ebp)
movl %eax, -12(%ebp)
hast aber dein %ebp davor nicht für deine Funktion initialisiert. Du schreibst also praktisch in den Speicherbereich deines Aufrufers, also main!
So, nun ist klar, dass du dein %ebp richtig setzen musst. Nur welchen Wert soll %ebp bekommen? Du schreibst alle deine Werte auf den Stack, also sollte auch deine Startadresse dort hin zeigen. Deine Funktion besteht grob gesagt aus den folgenden Schritten:
* 1: %ebp des Aufrufers speichern
* 2: %ebp auf den Speicherbereich deiner Funktion setzen. Die Obergrenze deines Stacks befindet sich in %esp, also können wir das einfach kopieren.
* 3: %esp um den benötigten Speicherbereich vermindern.
* 4: blabla (Die eigentliche Funktionalität deiner Funktion)
* 5: %esp um den benötigten Speicherbereich erhöhen.
* 6: %ebp des Aufrufers wiederherstellen.
* 7: Kontrolle an den Aufrufer zurückliefern.
In deinen Code gegossen schaut dies so aus:
Code: Alles auswählen
.text
.globl prog
prog:
push %ebp /* 1 */
mov %esp, %ebp /* 2 */
sub $12, %esp /* 3 */
/* 4 */
mov $4, %eax
mov %eax, 0(%esp)
mov $8, %eax
mov %eax, 4(%esp)
mov $12, %eax
mov %eax, 8(%esp)
add $12, %esp /* 5 */
pop %ebp /* 6 */
ret /* 7 */
Ich hab dein Programm noch etwas erweitert, um die Funktionalität des Stackframes zu testen. Ich rufe dazu eine (C-)Funktion auf, die drei ints als Parameter bekommt und auf die Kommandozeile schreibt, sowie eine andere Funktion innerhalb von main um den korrekten Abbau des Stackframes zu prüfen:
rt.c:
Code: Alles auswählen
#include <stdio.h>
extern void prog();
void print_int(int n);
void print_3_ints(int a, int b, int c);
int main() {
prog();
print_int(42);
}
void print_int(int n) {
printf("%d\n", n);
}
void print_3_ints(int a, int b, int c) {
printf("3 ints: %d %d %d\n", a, b, c);
}
out.S:
Code: Alles auswählen
.text
.extern print_3_ints /* Es gibt "irgendwo" diese Funktion, sie wird erst vom Linker genau spezifziert. */
.globl prog
prog:
push %ebp
mov %esp, %ebp
sub $12, %esp
mov $4, %eax
mov %eax, 0(%esp)
mov $8, %eax
mov %eax, 4(%esp)
mov $12, %eax
mov %eax, 8(%esp)
call print_3_ints /* Aufruf an die neue C-Funktion */
add $12, %esp
pop %ebp
ret
Ausgabe:
Man sieht hier auch sehr schön, dass man die Parameter in umgekehrter Reihenfolge auf den Stack legen muss. Das oberste Element wird logischerweise als erstes von der aufgerufene Funktion vom Stack genommen.
Für jemanden der das ganze selbst bauen und ausführen will:
Ich arbeite hier auf einem 64bit Linux-System. 64bit Register haben ein 'r'-Präfix anstatt 'e'. Das heißt statt eax, esp, ebp etc. verwendet man rax, rsp und rbp. Die kleineren Register existieren nur mehr als genauere Adressierung der größeren (rax hat ein eax, eax hat ein ax, ax hat ein al und ah usw). Um Probleme mit dem gegebenen Code zu umgehen, sage ich dem Assembler/Compiler/Linker explizit mir 32bit Binarys zu erstellen.
* Assembler-Code assemblieren:
* C-Code compilieren:
* Executable linken (du hast oben .exe stehen, das verwirrte mich etwas?!):
* Ausführen:
Das wars, noch Fragen?
Ich hab mir auch erlaubt den Titel des Threads ein wenig ausführlicher zu gestalten.