_cero: push ebp mov ebp, esp sub esp, 0 push dword 0 pop eax mov esp, ebp pop ebp ret
begin
Una subrutina reentrante [deMiguel01] es aqulla que puede ser llamada dessde
varios puntos simultneamente sin modificar su comportamiento Una subrutina recursiva es aqulla que puede llamarse a s misma, por lo tanto, una subrutina recursiva necesariamente tiene que ser reentrante. Las condiciones para que una subrutina sea recursiva y reentrante son las mismas. Las condiciones necesarias para que una subrutina sea recursiva y reentrante tiene que ver con que los datos utilizados por ellas no estn contenidos en posiciones fijas de memoria. Por lo tanto es suficiente con utilizar la pila para almacenar datos globales, locales y parmetros.
Para llamar a una subrutina se cumple los que se conoce como convenio de
llamadas que debe ser especificado explcitamente al disear el compilador.
function int doble(int arg) begin int auxArg; auxArg := arg; return 2*arg end input z; output doble(z) end
begin int z; function int doble(int arg) begin int auxArg; auxArg := arg; return 2*arg end input z; output doble(z) end
segment .data _z dd 0 segment .text global main extern lee_entero, imprime_entero main:
begin int z; function int doble(int arg) begin int auxArg; auxArg := arg; return 2*arg end input z; output doble(z) end
begin int z; function int doble(int arg) begin int auxArg; auxArg := arg; return 2*arg end input z; output doble(z) end
begin int z; function int doble(int arg) begin int auxArg; auxArg := arg; return 2*arg end input z; output doble(z) end
Y limpia la pila. Esta instruccin es equivalente a pop <registro> pero no necesita modificar ningn registro
10
begin int z; function int doble(int arg) begin int auxArg; auxArg := arg; return 2*arg end input z; output doble(z) end
begin int z;
De la misma manera hay que hacer con una llamada a una funcin definida por el programador
function int doble(int arg) begin int auxArg; auxArg := arg; return 2*arg end input z; output doble(z) end
push dword [_z] call _doble add esp, 4 push dword eax El retorno de la funcin se encuentra en eax
11
12
begin int z; function int doble(int arg) begin int auxArg; auxArg := arg; return 2*arg end input z; output doble(z) end
begin int z; function int doble(int arg) begin int auxArg; auxArg := arg; return 2*arg end input z; output doble(z) end
push dword [_z] call _doble add esp, 4 push dword eax call imprime_entero add esp, 4
push dword [_z] call _doble add esp, 4 push dword eax call imprime_entero add esp, 4 ret
13
14
Para comprender el cdigo que hay que generar para la funcin imaginemos cmo
est la pila (donde se tiene que almacenar toda la informacin) en una llamada si z contiene el valor 3 begin int z; function int doble(int arg) begin int auxArg; auxArg := arg; return 2*arg end input z; output doble(z) end
main: push dword _z call lee_entero add esp, 4
push dword [_z] call _doble add esp, 4 push dword eax call imprime_entero add esp, 4 ret
push dword [_z] call _doble add esp, 4 push dword eax call imprime_entero add esp, 4 ret
esp
15
16
Esta es la pila que se encuentra la funcin cuando comienza su ejecucin Est claro dnde se encuentra el argumento de la funcin (arg) Se tiene que utilizar tambin la pila para localizar las variables locales (auxArg) Debemos conocer qu expresin, en funcin de los registros de gestin de la pila, sirve para localizar en ella argumentos y variables locales. Obsrvese que el compilador tendr que generar el cdigo necesario para el cuerpo de la funcin. Por las decisiones de diseo tomadas, se utilizar la pila, por lo tanto, el valor del registro esp cambiar. NASM proporciona otro registro ebp especficamente para poder solucionar esta circunstancia. Para ello hay que hacer lo siguiente: Guardar el valor de ebp (tambin en la pila) Utilizar ebp para mantener una copia del valor de esp en el momento de la entrada Utilizar ebp para localizar los parmetros y las variables locales Recuperar los valores de los dos registros antes de salir de la funcin
18
push dword [_z] call _doble add esp, 4 push dword eax call imprime_entero add esp, 4 ret
17
begin int z; function int doble(int arg) begin int auxArg; auxArg := arg; return 2*arg end input z; output doble(z) end
begin int z; function int doble(int arg) begin int auxArg; auxArg := arg; return 2*arg end input z; output doble(z) end
19
20
Ahora slo queda localizar (reservar espacio en la pila y saber como acceder
posteriormente) las variables locales (auxArg). Para ello se utiliza las posiciones libres bajo la cima de la pila (como con push, restamos a esp lo necesario)
_doble: push ebp mov ebp, esp sub esp, 4 3 dir ret. ebp ebp esp ebp+8
begin int z; function int doble(int arg) begin int auxArg; auxArg := arg; return 2*arg end input z; output doble(z) end
begin int z; function int doble(int arg) begin int auxArg; auxArg := arg; return 2*arg end input z; output doble(z) end
21
22
Por lo tanto, auxArg se encontrar siempre 4 bytes por debajo de ebp es decir,
begin int z; function int doble(int arg) begin int auxArg; auxArg := arg; return 2*arg end input z; output doble(z) end
begin int z; function int doble(int arg) begin int auxArg; auxArg := arg; return 2*arg end input z; output doble(z) end
23
24
Antes de salir de la funcin se debe recuperar los valores iniciales de ebp y esp.
begin int z; function int doble(int arg) begin int auxArg; auxArg := arg; return 2*arg end input z; output doble(z) end
begin int z; function int doble(int arg) begin int auxArg; auxArg := arg; return 2*arg end input z; output doble(z) end
ebp-4
dword eax, [ebp+8] dword [ebp-4], eax dword edx, 2 edx Se copia en esp su valor que est guardado en ebp. Obsrvese que es como si se hicieran los pop necesarios para eliminar las variables locales
25
26
Antes de salir de la funcin se debe recuperar los valores iniciales de ebp y esp.
Antes de salir de la funcin se debe recuperar los valores iniciales de ebp y esp.
begin int z; function int doble(int arg) begin int auxArg; auxArg := arg; return 2*arg end input z; output doble(z) end
begin int z; function int doble(int arg) begin int auxArg; auxArg := arg; return 2*arg end input z; output doble(z) end
3 dir ret.
esp
dword eax, [ebp+8] dword [ebp-4], eax dword edx, 2 edx Se recupera (desde la pila) el antiguo valor de ebp. Obsrvese que, tras modificar en la instruccin anterior esp la cima de la pila apunta ahora a la posicin que guarda la copia de ebp
dword eax, [ebp+8] dword [ebp-4], eax dword edx, 2 edx Y ya se puede devolver el control al programa llamante mediante la instruccin ret. Esta instruccin elimina de la pila la direccin de retorno
27
28
Retornamos, por lo tanto, al cdigo del programa principal que se encuentra la pila
como la tena antes de la ejecucin de la instruccin call. Obsrvese que la pila todava contiene los argumentos de la funcin, el programa debe limpiar la pila.
main: push dword _z call lee_entero add esp, 4
Retornamos, por lo tanto, al cdigo del programa principal que se encuentra la pila
como la tena antes de la ejecucin de la instruccin call. Obsrvese que la pila todava contiene los argumentos de la funcin, el programa debe limpiar la pila.
main: push dword _z call lee_entero add esp, 4
begin int z; function int doble(int arg) begin int auxArg; auxArg := arg; return 2*arg end input z; output doble(z) end
esp push dword [_z] call _doble add esp, 4 push dword eax call imprime_entero add esp, 4 ret 3 Se vio previamente la posibilidad de simula los pop necesarios para limpiar la pila sumando directamente en el puntero de la cima (esp)
push dword [_z] call _doble add esp, 4 push dword eax call imprime_entero add esp, 4 ret
29
30
Las primeras sentencias NASM del cuerpo de cualquier funcin deben ser las
siguientes: _<nombre_funcion>: push ebp mov ebp, esp sub esp, <4*n var locales>
Y limpiar la pila:
add esp, <4 * n argumentos de la funcin> Antes de continuar con su cdigo sabiendo que eax contiene el retorno de la funcin (el cdigo escrito a continuacin imprime el entero devuelto por la funcin llamada) push eax call imprime_entero add esp, 4
Prcticas de compiladores 2004-2005 31
32
Por ejemplo:
El valor de la primera variable local ser [ebp-4]. El valor de la segunda variable local ser [ebp-8]. El valor de la i-esima variable local ser [ebp-<4*i>].
33
34
Donde
<registro>, es el nombre de un registro (por ejemplo eax) <expresin>, puede ser, por ejemplo ebp+12.
[_z]
Pero, la expresin para la direccin es
_z
35
36
_z [_z]
Si es el argumento i-simo de una funcin, lo siguiente deja en el registro eax la direccin
[ebp+<4+4*i>]
Si es la variable local i-sima de una funcin
[ebp-<4*i>]
37
38
39
40