Parameterübergabe an Unterprogramme per Stack
von Mario Rasser
Hierbei handelt es sich um eine Vortrag meines Studienkollegens Dipl. Inf. (FH) Frank Grimm zum Thema Parameterübergabe an Unterprogramme per Stack und eine Darstellung der Funktionsweise mit Beispielen.
Voraussetzung
- Es gibt ein Register, das auf eine Speicherstelle zeigt, die sich im sogenannten Stack befindet, es wird unter x86 mit esp bezeichnet.
- Der Stack in ein Stück Speicher, das sich im Speicherraum eines Prozesses befindet und dient zum Sichern von Werten, zur Wertübergabe an Unterprogramme und zum Anlegen von lokalen Variablen.
- Es gibt zwei Befehle, mit denen ein Operant auf dem Stack gesichert bzw. vom Stack geholt werden kann: push und pop.
- Jedes push/pop verändert den Stackzeiger (den Inhalt von esp).
- Auf x86 wächst der Stack von den hohen Adressen zu niedrigen Adressen auf den Heap zu.
- Mit den Befehlen push und pop kann nur nach dem “First In – Last Out”-Prinzip auf Werte im Stack zugriffen werden.
- Da esp eine Adresse auf dem Stack beinhaltet, kann aber per Zeigerarithmetik auch beliebig auf den Stack zugegriffen werden.
1. Parameter auf Stack pushen
Der letzte Parameter wird zu erst auf den Stack geschafft, die Parameter werden also von rechts nach links gesichert (gepusht) und können daher im Unterprogramm wieder von links nach rechts gelesen (pop) werden.
2. call zum Unterprogramm
Wie immer, zeigt der Befehlszeiger auf den Speicherplatz mit dem n&äuml;chsten Befehl im Hauptprogramm, dieser Wert des Befehlszeigers wird automatisch vom Befehl call auf den Stack gepusht.
3. Stack-Frame aufbauen und Unterprogramm-Befehle ausfuehren
- Wert des ebp-Registers (allgemeines Register) auf Stack sichern (pushen)
- Aktuelles esp-Register (Stackzeiger) in ebp sichern (per mov)
4. Verwendung des Stack-Frames
Falls lokale Variablen angelegt werden, wird das Stack-Register (esp) um die Grösse der gesamten lokalen Variablen erniedrigt (subtrahiert) (Stack wächst auf x86 von oben nach unten auf den Heap zu). Der Stackzeiger verweisst nun auf eine Speicheradresse, die völlig ungenutzt ist, damit nachfolgende Unterprogramm-Rufe im aktuellen Unterprogramm die aktuellen lokalen Variablen nicht verfälschen.
Der Zwischenraum auf Stack (ebp – esp) wird für lokale Variablen verwendet und die Parameter werden auf den Stack gepusht (der Stack-Zeiger verändert sich).
5. Stack-Frame-Abbau und Verlassen des Unterprogramms
- Beim Verlassen werden alle Stack-Veraenderungen rueckgaengig gemacht, was bedeutet, dass der Stackzeiger (esp) auf den Zustand vor dem Call zurückgesetzt (mit leave) wird. Das heisst: Beim Verlassen, wird der Befehlszeiger (ip) mit dem Wert geladen, auf den der Stackzeiger beim UP-Aufruf zeigte.
- Das Register ebp wird wieder mit dem Wert vor dem Sichern der Stack-Adresse auf ebp gefuellt (mit ret).
6. Der Rückgabewert eines Unterprogramms
Der Rückgabewert einer Funktion wird im allgemeinen Register eax an das Hauptprogramm zurueckgegeben.
7. Zusammenfassung
Beim Eintritt in das Unterprogramm zeigt der Stackpointer auf die Rücksprungadresse, das ist der nächste Befehl im Hauptprogramm und vor dem aktuellen Stackwert liegen die Parameter; im Unterprogramm kann der Stack veraendert werden, wird beim Verlassen aber wieder zurueckgesetzt
Beispiel
void foo() { int i = 99; } void bar(int i) { int j = i; } int add(int i, int j) { return i+j; } int main() { int i = 99; foo(); bar(i); i = add(1, 3); }
Umsetzung in GNU(-x86)-Assember (AT&T-Notation, d.h.: Befehl Quelle Ziel):
(Assembercode erzeugt mit gcc -S stack.c)
foo: pushl %ebp movl %esp,%ebp leave ret bar: pushl %ebp movl %esp,%ebp subl $24,%esp movl 8(%ebp),%eax movl %eax,-4(%ebp) leave ret add: pushl %ebp movl %esp,%ebp movl 8(%ebp),%eax movl 12(%ebp),%ecx leal (%ecx,%eax),%edx movl %edx,%eax leave ret main: pushl %ebp movl %esp,%ebp subl $24,%esp movl $99,-4(%ebp) call foo addl $-12,%esp movl -4(%ebp),%eax pushl %eax call bar addl $16,%esp addl $-8,%esp pushl $3 pushl $1 call add addl $16,%esp movl %eax,%eax movl %eax,-4(%ebp) leave ret
Copyrigth Dipl. Inf. (FH) Frank Grimm
(Archiviert aus unseren alten Tipps und Tricks Sektion.)
