miércoles, 15 de febrero de 2012

¿Cómo funciona el lenguaje ensamblador? [Lab]

Un programa empieza en la función principal (main), que después hace algunas llamadas a otras funciones y estas funciones a otras, etc., cuando codificamos debemos saber que una función obtiene tres tipos de datos los cuales son:
  •   Las variables globales, las cuales sabemos que están en el segmento de datos de un proceso, esto es que siempre están hasta que terminamos el programa. 
  •   El argumento de la función y las variables locales, están en una pila, aparecen cuando se llama a una función y desaparecen cuando esta función tiene un return 

Por lo tanto, los parámetros y variables locales en memoria de un proceso de una función se unen en un bloque de memoria llamado marco de pila, esto es por ejemplo, si f1 llama a f2, el marco de pila de f2 se monta sobre el marco de pila de f1, luego cuando en f2 aparece un return, su marco de pila desaparece, la composición del marco de pila es la siguiente:

Marco de pila

Se utilizan las variables locales y los parámetros de la función, con un modo de direccionamiento relativo de una dirección en un registro del procesador (para mas información de este modo aquí),  es importante recalcar los siguientes registros.


Registros





8 32-bit Registros de proposito general
Registro Función terminación 16-bit 8-bit
eax Acumulador ax ah, al
ebx (base index) bx bh, bl
ecx (contador) cx ch, cl
edx (datos) dx dh, dl
edi (destination index) do
esi (source index) si
ebp Frame pointer bp
esp Stack top pointer sp


Pare este caso, yo tengo una arquitectura Intel, la cual tiene un registro denominado base ebp, este apunta a la ubicación del marco de pila que almacena el registro ebp utilizado por la función la cual es llamada.

Por lo tanto, esto nos sirve cuando una función retorna, es mas sencillo restaurar el puntero de la función principal y seguir con sus instrucciones. 

Cuando una función llama a otra, es como si se fuera preparando poniendo en la pila los parámetros que necesita para hacer llamar a la siguiente función, entonces construye esa parte del marco.

El compilador, es el que tiene la responsabilidad de hacer que esto suceda, entonces cuando llama una función lo que se hace primeramente no es el realizar el código que nosotros escribimos, sino hacer lo que se le llama "labores de gestión", los cuales son los siguiente: 

1. Guardar el puntero de marco de una función, que está en ebp, en la pila. Ahora, ya teniendo ese enlace; 
2. Reescribir en el registro base ebp con su correspondiente puntero de marco. 
3. Organizar en la pila el espacio para sus variables locales. A estas tres tareas se le llama prólogo de la función, después de esto la siguiente función que fué llamada esta preparada a iniciar, cuando finalice, guardará el valor de return en el registro eax para que ahí lo encuentre la función que la llamó. 

Por lo tanto, devolver el dominio de la función que llamó a otra mediante el return, se necesita otra tarea de gestión anterior, la cual consta en restaurar el registro ebp al puntero del marco de la función. A esta tarea se le llama epílogo de la función, después de este epílogo, se ejecuta la instrucción ret


Referencias.

  • Sistemas de tiempo real - Universidad de Extremadura. 
  • x86 Assembly Programming - University of Science and Technology of China. 
  • C Internals - Avabodh. 
  • Stackoverflow - question.
  • 1 comentario: