sábado, 11 de febrero de 2012

[Emb Comp Lab] Lenguaje Ensamblador (Assembly)

"Es un lenguaje de programación de bajo nivel. Implementa una representación simbólica de los códigos de máquina binarios y otras constantes necesarias para programar una arquitectura dada de CPU y constituye la representación más directa del código máquina específico para cada arquitectura legible por un programador. 
Esta representación es usualmente definida por el fabricante de hardware, y está basada en los mnemónicos que simbolizan los pasos de procesamiento (las instrucciones), los registros del procesador, las posiciones de memoria, y otras características del lenguaje. Un lenguaje ensamblador es por lo tanto específico a cierta arquitectura de computador física (o virtual).


Mnemónico: es un dato simbólico que identifica a un comando generalmente numérico (binario, octal, hexadecimal) de una forma más sencilla que su numeración original, lo cuál facilita radicalmente la memorización de este comando para el programador.

Características

  • Es un lenguaje de bajo nivel.
  • El lenguaje ensamblador es difícilmente portable. Al cambiar a una máquina con arquitectura diferente, generalmente es necesario reescribirlo completamente.
  • Los programas en lenguaje ensamblador son generalmente mucho más rápidos y consumen menos recursos del sistema (memoria RAM y ROM) que el programa equivalente compilado desde un lenguaje de alto nivel. Al programar cuidadosamente en lenguaje ensamblador se pueden crear programas que se ejecutan más rápidamente y ocupan menos espacio que con lenguajes de alto nivel.
  • Con el lenguaje ensamblador se tiene un control muy preciso de las tareas realizadas por un microprocesador por lo que se pueden crear segmentos de código difíciles y/o muy ineficientes de programar en un lenguaje de alto nivel, ya que, entre otras cosas, en el lenguaje ensamblador se dispone de instrucciones del CPU que generalmente no están disponibles en los lenguajes de alto nivel.
  • Por lo general, cada arquitectura tiene su propia "versión" de ensamblador, es muy probable que un código escrito para cierta arquitectura no pueda ser ejecutado en otras, sino que sera necesario reescribir todo el código.


Obtener Assembly desde C

Desde la terminal tecleamos:
gcc -S nombreDelCodigo.c
Esto genera un archivo con terminacion .s que contiene el código en Assembly

Compilar Assembly

Desde la terminal tecleamos:
gcc -o nombreDelEjecutable nombreDelCodigo.c
Para ejecutar tecleamos:
./nombreDelEjecutable

Algunos conceptos


Stack



Stack Frame o en español Marco de Pila es un espacio en la memoria donde, de manera temporal, se almacena la información utilizada por una función para poder recuperarla después.

Principalmente, almacena 3 cosas:
  • Parámetros de una función
  • Variables locales
  • La dirección de retorno

Registros



os registros del procesador se emplean para controlar instrucciones en ejecución, manejar direccionamiento de memoria y proporcionar capacidad aritmética. Los registros son espacios físicos dentro del microprocesador con capacidad de 4 bits hasta 64 bits dependiendo de la arquitectura.

El registro %ebp (base pointer), apunta a la base del stack de la función que se esta ejecutando

El registro %esp (stack pointer), apunta a la cima del stack de la función que se esta ejecutando.

Registros de uso general

  • AX: Registro acumulador, dividido en AH y AL (8 bits cada uno).- Interviene en las operaciones aritméticas y lógicas, después de la operación arroja un resultado.
  • BX: Registro base, dividido en BH y BL.- Se utiliza en transferencias de datos entre la memoria y el procesador.
  • CX: Registro contador, dividido en CH y CL.- Se utiliza como contador en bucles(LOOP), en operaciones con cadenas(REP), y en desplazamientos(CL).
  • DX: Registro de datos, dividido en DH y DL.- Se utiliza en operaciones de multiplicación y división junto con Ax y en operaciones de entrada y salida de puertos, su mitad inferior DL contiene el número de puertos.

Registros de segmento

Un registro de segmento se utiliza para alinear en un limite de párrafo o dicho de otra forma codifica la dirección de inicio de cada segmento y su dirección en un registro de segmento supone cuatro bits 0 a su derecha. Un registro de segmento tiene 16 bits de longitud y facilita un área de memoria para direccionamientos conocidos como el segmento actual. Los registros de segmento son: CS (código), DS (datos), SS (pila), ES , FS y GS.

Registro Apuntador de instrucciones (IP)

El registro apuntador de instrucciones (IP) de 16 bits contiene el desplazamiento de dirección de la siguiente instrucción que se ejecuta.

Registro índice

Los registros SI y DI están disponibles para direccionamientos indexados y para sumas y restas. Que son las operaciones de punta.

Registro de bandera (EFLAGS)

Los registros de banderas sirven parar indicar el estado actual de la maquina y el resultado del procesamiento, Cuando algunas instrucciones piden comparaciones o cálculos aritméticos cambian el estado de las banderas.

Ejecución del Código


1. Push Parameters: Los parámetros de la función que fue llamada son empujados al stack, al mismo tiempo se lleva un registro de los bytes ocupados por los parámetros y se almacena el offset en donde se encuentran. Esto sirve para las labores de limpieza posteriores.

2. Call the function: El procesador empuja el registro %EIP (Instruction Pointer) dentro del stack, el cual apunta al primer byte después de la llamada a la función (instrucción CALL). Después de que termina la ejecución de la función, esta pierde el control y lo pasa a la función desde la cual fue llamada.

3. Guardar y actualizar el registro %ebp: Ahora estamos preparados para la nueva función, necesitamos reservar el stack local de la misma; lo anterior se logra guardando el %ebp que pertenece a la función anterior y que el apuntador %esp apunte al tope del nuevo stack. A esto se le llama prologo, sus instrucciones son:
push ebp mov ebp, esp
4. Guardar los registros del CPU: : Si la función necesita utilizar registros del CPU, se deben guardar los anteriores. Se empujan uno a uno a la pila. El compilador debe recordar este paso para poder recuperarlos después.

5. Reservar el espacio para las variables locales: A esto se le conoce como "enderezar la pila o alinear la pila". La función realiza una suma del espacio que utilizaran las variables (caracteres = 1byte, enteros = 4bytes), se suma su tamaño en bytes y la función decrementa el apuntador %esp en grupos de 4bytes hasta alcanzar el tamaño calculado. Entonces, las variables locales se alojan entre el registro %ebp y el registro %esp.

6. Ejecutar la tarea de la función: En este punto el stack ya esta construido, mas o menos asi:

La función es libre de utilizar todo registro dentro del stack, con ellos puede realizar todas las operaciones para las cuales fue creada.

7. Liberar el espacio temporal: Una vez que la función termina su ejecución primero debe liberar el espacio alojado por sus variables. Esto de logra con algunas operaciones POP.

8. Restaurar los registros guardados: Deben ser restaurados en el mismo orden en el cual fueron guardados, para ello se utilizan algunas operaciones POP.

9. Restaurar el puntero al registro base anterior: En el stack hemos guardado el puntero al registro %ebp del anterior stack, esto es hacer que %esp apunte a %ebp y despues hacemos POP a %ebp. Con ello destruimos el stack de la función recién concluida y restauramos el stack de la función anterior. A esto se le llama epílogo y las instrucciones son:
mov esp, ebp pop ebp
Que es equivalente a la instrucción
LEAVE
10. Regresar el control a la función anterior: La instrucción RET saca (POP) el registro %eip del stack y entonces la ejecución salta a esa ubicación, regresando el control a la función padre.

11. Limpiar los parámetros introducidos al inicio: Con algunas instrucciones POP, se sacan los parámetros introducidos en el stack. La función recuerda el offset de inicio y final para sacarlos a todos rápidamente.

Para entender un poco mejor estos conceptos les dejo el siguiente código en C. El código en ensamblador tiene comentarios para su mejor análisis.
Referencias

2 comentarios:

  1. Hola, me agrado bastante la entrada esta muy bien explicada.
    Checa la parte "Compilar Assembly" porque lo que estas haciendo ahi es compilar el .c
    La forma para compilar el Assembly es con

    >gcc -o NombreSalida -masm Programa.s

    Saludos.

    ResponderEliminar
  2. Van 8 para el lab para JC y uno para el mismo lab para Ever por la corrección.

    ResponderEliminar