Content |
Call und Ret
Am Tag 2 haben wir einige Assembler Befehle kennen und verstehen gelernt. Ein paar Dinge müssen wir aber noch genauer anschauen, um sie wirklich zu verstehen:
-
Was tun die Assembler Befehle call und ret ganz genau?
-
Was tut push und pop und wieso werden sie hier benötigt?
Zweites Beispiel
void
f()
{
a = a + a;
}
void
g()
{
f();
f();
}
Mit
$shell> gcc-4.8 -S -fno-asynchronous-unwind-tables second.c
erzeugen wir den Assembler Code
.data
.align 2
_a:
.long 6
.text
.globl _f
_f:
pushq %rbp
movq %rsp, %rbp
movl _a(%rip), %edx
movl _a(%rip), %eax
addl %edx, %eax
movl %eax, _a(%rip)
popq %rbp
ret
.globl _g
_g:
pushq %rbp
movq %rsp, %rbp
movl $0, %eax
call _f
movl $0, %eax
call _f
popq %rbp
ret
.subsections_via_symbols
Was tut Call und Ret?
Beide Assembler Befehle ändern das %rip Register. Damit wird als nicht der im Text-Segment folgende Maschinenbefehl ausgeführt sondern es wird gesprungen. Stellt sich also die Frage wieso man nicht einfach den jmp Befehl benutzt.
Den call Befehl könnte man tatsächlich mit einer jmp Anweisung ersetzten. Die Adresse des ersten Befehls einer Funktion kennt man ja nach dem Linken. Wenn die Funktion abgearbeitet ist muss man aber zurückspringen und zwar zu dem Maschinenbefehl der dem Funktionsaufruf folgt. Da die Funktion mehrfach aufgerufen werden kann ist diese Adresse aber nicht fix. Für jeden Funktionsaufruf muss man also eine Rücksprungadresse speichern.
Stellt sich die Frage wo man die Rücksprungadresse speichern soll. Man kann einen festen Platz im Speicher ausmachen. Das geht gut solange nur eine Funktion aufgerufen wird. Ein zweiter Funktionsaufruf dürfte nur erfolgen, wenn man mit dem ersten Aufruf “fertig” ist, d.h. die Funktion abgearbeitet ist und zurückgesprungen wurde.
Für jeden Funktionsaufruf muss man also einen neuen Platz für die Rücksprung Adresse festlegen. Wenn viele Funktionsaufrufe stattfinden, dann stapelt sich da was auf. Und da sind wir schon bei der Lösung. Die Rücksprungadressen werden einfach auf einem Stack gespeichert. Mit jedem Funktionsaufruf wird der Stack größer. Ganz oben auf dem Stack ist dann die Rücksprung Adresse für den letzten Funktionsaufruf. Darunter die für den vorletzten usw.