ensamblador

137
LENGUAJE MÁQUINA Cada tipo de CPU entiende su propio lenguaje de ́ quina. Las instrucciones en lenguaje de máquina son n ́meros almacenados como bytes en memoria. Cada instrucción tiene su propio y único código llamado código de operación u opcode. Las instrucciones del procesador 80X86 varían de tamaño. El opcode está ́ siempre al inicio de la instrucción. Muchas instrucciones incluyen también datos (ver constantes o direcciones) usados por las instrucciones. El lenguaje de máquina es muy difícil de programar directamente. Descifrar el significado de las instrucciones codificadas numéricamente es te dioso para los humanos. Por ejemplo la instrucción para sumar los registros EAX y EBX y almacenar el resultado en EAX está codificada por los siguientes códigos hexadecimales: 03 C3 Esto no es obvio. Afortunadamente, un programa llamado ensamblador puede hacer este aburrido trabajo para el programador. Un programa Escrito en lenguaje ensamblador es almacenado como texto (tal como programas de alto nivel). Cada instrucción representa exactamente una instrucción de la máquina. Por ejemplo, la instrucción de suma descrita arriba podrá ser representada en lenguaje ensamblador como: Mnemonico operando(s) LENGUAJE ENSAMBLADOR

Upload: oscarhhhrq

Post on 20-Jan-2016

29 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: ensamblador

LENGUAJE MÁQUINACada tipo de CPU entiende su propio lenguaje de má́� quina. Las instrucciones en lenguaje de má́quina son n �meros almacenados como bytes en memoria. Cada instrucción tiene su propio y único código llamado código de operación u opcode. Las instrucciones del procesador 80X86 varían de tamaño. El opcode está́ � siempre al inicio de la instrucción. Muchas instrucciones incluyen también datos (ver constantes o direcciones) usados por las instrucciones.

El lenguaje de má́quina es muy difícil de programar directamente. Descifrar el significado de las instrucciones codificadas numéricamente es te dioso para los humanos. Por ejemplo la instrucción para sumar los registros EAX y EBX y almacenar el resultado en EAX está́ codificada por los siguientes códigos hexadecimales:

03 C3

Esto no es obvio. Afortunadamente, un programa llamado ensamblador puede hacer este aburrido trabajo para el programador.

Un programa Escrito en lenguaje ensamblador es almacenado como texto (tal como programas de alto nivel). Cada instrucción representa exactamente una instrucción de la má́quina. Por ejemplo, la instrucción de suma descrita arriba podrá́ ser representada en lenguaje ensamblador como:

Mnemonico operando(s)

LENGUAJE ENSAMBLADOR

Page 2: ensamblador

PROGRAMACIÓN EN ENSAMBLADOR PROGRAMACIÓN EN ENSAMBLADOR USANDO LA SINTAXIS GAS EN USANDO LA SINTAXIS GAS EN

ARQUITECTURAS x86ARQUITECTURAS x86Las instrucciones que hacen uso de la sintaxis GAS tiene la forma:

Mnemonico dato_fuente, dato_destinoPor ejemplo:

movb $0x05, %alLa cual está́ relacionada con un direccionamiento inmediato del dato 5h al registro al.Cuando se hace referencia a un registro, dicho registro requiere ser prefijado con el signo de “%”. Los tipos de datos constantes será́n antecedidos por el cará́cter “$”.Algunas características de la sintaxis de GAS se relacionan con la sintaxis del operando de direcciones:Se permiten hasta 4 pará́metros de un operando de direcciones que se presentan en la siguiente sintaxis de desplazamiento (registro base, registro de offset, multiplicador escalar). Esto es equivalente a la sintaxis de Intel [registro base + desplazamiento + registro de offset * multiplicador escalar ], las diferentes forma de implementación se ilustran a continuación:

movl -4(%ebp, %edx, 4), %eax # ejemplo completo: load *(ebp - 4 + (edx * 4)) into eaxmovl -4(%ebp), %eax # ejemplo típico: carga una variable del stack en eaxmovl (%ecx), %edx # sin offset: copiar la salida de un apuntador a un registroleal 8(,%eax,4), %eax # Aritmética: multiplicar eax por 4 y sumarle 8leal (%eax,%eax,2), %eax # Aritmética: multiplicar eax por 2 and sumarle eax (esto es multiplicar por 3)

Page 3: ensamblador

GENERANDO CÓDIGO ENSAMBLADOR A GENERANDO CÓDIGO ENSAMBLADOR A PARTIR DE CÓDIGO EN CPARTIR DE CÓDIGO EN C

Debido a que el lenguaje ensamblador se corresponde de forma directa a las operaciones que la unidad de procesamiento realiza, una rutina en ensamblador cuidadosamente escrita puede tener la capacidad de correr mucho má́s rá́pidamente que la misma rutina escrita en un lenguaje de alto nivel, tal como el lenguaje C. En contra parte, las rutinas en lenguaje ensamblador por lo regular requieren de mayor esfuerzo a ser escritas que su equivalente en lenguaje C. Una forma de conocer el funcionamiento de una rutina en ensamblador, haciendo uso del compilador de C, es escribiéndola en C y generar su similar en ensamblador haciendo uso del compilador mismo. Tal como se muestra a continuación:

#include <stdio.h>

int main(void) { printf("Hello, world!\n"); return 0;}

Dicho programa es muy simple pero nos permitirá́ demostrar su traducción a lenguaje ensamblador. Para ello, la compilación se realizará́ de la siguiente forma:

$ gcc -S hello.cLo cual deberá́ de crear un archivo de nombre “hello.s” (la extensión .s, proviene de la filosofía de los proyectos GNU). Una vez realizado lo anterior se procede a compilar dicho archivo en ensamblador para generar el archivo ejecutable.

$ gcc -o hello.exe hello.s

Page 4: ensamblador

Posterior a ello se tendrá́ que realizar la ejecución del programa correspondiente.

Lo interesante de este programa está́ en visualizar el contenido del archivo en ensamblador obtenido después de la compilación correspondiente.

Page 5: ensamblador

PROGRAMAS EN ENSAMBLADOR PROGRAMAS EN ENSAMBLADOR UTILIZANDO DDD COMO DEPURADOR, UTILIZANDO DDD COMO DEPURADOR,

ESTRUCTURA BÁSICA DE UN PROGRAMAESTRUCTURA BÁSICA DE UN PROGRAMA1. /*2. Este es un tipo de comentario3. */4. #Otro tipo de comentario5. #nombre de archivo (opcional)6. .file "Ejemplo1.s"7. #secciones8. .section .data9. # <- datos10. # <- datos11. #PROGRAMA12. .section .text13. .global _start14. _start:15. #FINALIZA PROGRAMA16. xorl %eax, %eax17. incl %eax18. xorl %ebx, %ebx19. int $0x8020 .end

Page 6: ensamblador

.text

.global _start_start: movl $len, %edx movl $msg, %ecx movl $1, %ebx movl $4, %eax int $0x80 movl $0, %ebx movl $1, %eax int $0x80.data msg: .ascii "hola mundo!\n" len = 12

.text # section declaration

# we must export the entry point to the ELF linker or.global _start # loader. They conventionally recognize _start as their

# entry point. Use ld -e foo to override the default.

_start:

# write our string to stdout

movl$len,%edx # third argument: message lengthmovl$msg,%ecx # second argument: pointer to message to writemovl$1,%ebx # first argument: file handle (stdout)movl$4,%eax # system call number (sys_write)int $0x80 # call kernel

# and exit

movl$0,%ebx # first argument: exit codemovl$1,%eax # system call number (sys_exit)int $0x80 # call kernel

.data # section declaration

msg:.ascii "Hello, world!\n" # our dear stringlen = . - msg # length of our dear str

Page 7: ensamblador

ESTRUCTURA EN ESTRUCTURA EN PSEUDOCÓDIGOPSEUDOCÓDIGO

Por má́s trivial que parezca el problema, siempre resulta útil hacer el esquema de ejecución a modo de pseudocódigo. De esta forma garantizamos, en primer lugar, una buena lógica de programa, y en segundo, reducir la labor de programación a una simple traducción de un lenguaje al cual ya estamos familiarizados. Como primer intento podríamos hablar de un pseudocódigo de alto nivel de este tipo.

Ahora bien, si tenemos en cuenta que Assembler es un lenguaje donde las operaciones son predominantemente binarias, valdrá́ la pena convertir nuestro algoritmo a uno que sólo requiera operaciones de éste tipo.

Page 8: ensamblador

VARIABLES EN LENGUAJE ENSAMBLADORVARIABLES EN LENGUAJE ENSAMBLADORAlgo importante en la programación en ensamblador es tener claras las variables que se utilizan. Para esto, se debe tener en cuenta lo siguiente:

1. Nombre de la Variable: Toda variable debe tener nombre. Debe ser fá́cil de recordar ya que el orden es muy importante en la programación en ensamblador.

2. Tamaño de la variable: Definir de que tamaño es la variable (64 bits, 32 bits, 16 bits, 8 bits o un espacio de memoria de N Bytes)

Según nuestro algoritmo necesitaremos 6 espacios de memoria. Cinco para los números que necesitamos sumar y un espacio para el acumulador.

El Procesador Intel posee espacios para almacenar variables, estos espacios se denominan Registros. Las ventajas que nos ofrece la utilización de registros es la velocidad de acceso, por esto es preferible que los datos que se utilicen con mayor frecuencia se alojen en registros. Los registros de Intel que podemos utilizar se organizan de esta forma:

Registros de 32 bits EAX, EBX, ECX, EDXRegistros de 16 bits Corresponden a la mitad derecha de los registros de 32 bits, y son: AX, BX, CX, DXRegistros de 8 bits Corresponden a cada una de las mitades de los registros de 16 (H para High, L para Low) y son: AL, AH, BL, BH, CL, CH, DL, DH

Page 9: ensamblador

Conociendo esto, alojaremos nuestras variables de la siguiente forma.

Las Variables en memoria se definen en el á́rea de datos así:<label>: <tama~o> <valor inicial>Aqui label sirve como el nombre de la variable, es decir, el nombre con elcual haremos referencia al valor guardado.El tamaño se define como

.long para 32 bits, .word para 16 bits y .byte para 8 bits.

Los valores iniciales equivalen a los números que tendrá́ el programa. Por defecto se leen en decimal pero pueden escribirse en binario (inicializando la cadena con “0b”), en hexadecimal (inicializando la cadena con “0x”) o en octal inicializá́ndola en “0”.Ya con esto podemos escribir nuestra sección .data para alojar las variables a las que hallaremos la sumatoria (alojaremos 100, 100, 100, 100, -100)..section .dataNumero1: .long 100Numero2: .long 0144Numero3: .long 0x64Numero4: .long 0b1100100Numero5: .long -100

Page 10: ensamblador

PROGRAMA EN ENSAMBLADOR DE PROGRAMA EN ENSAMBLADOR DE EJEMPLOEJEMPLO

.file "Sumatoria.s"

.section .dataNumero1: .long 100Numero2: .long 0144Numero3: .long 0x64Numero4: .long 0b1100100Numero5: .long -100

.global _start

.text_start:

clrl %eaxaddl Numero1,%eaxaddl Numero2,%eaxaddl Numero3,%eaxaddl Numero4,%eaxaddl Numero5,%eaxxorl %eax, %eaxincl %eaxxorl %ebx, %ebxint $0x80

.end

Page 11: ensamblador

FORMA DE FORMA DE COMPILACIÓN COMPILACIÓN

DEL PROGRAMADEL PROGRAMA

Page 12: ensamblador

GAS (Gnu ASsembler) utiliza la sintaxis de AT&T, que tiene pequeñas diferencias con respecto a la sintaxis está́ndar de Intel (usada en NASM, TASM, MASM, etc). Las principales diferencias se detallan a continuación:

En AT&T, el destino se coloca a la derecha y el fuente a la izquierda (en Intel es al revés). Las siguientes instrucciones cargan en ebx el valor de eax

AT&T: movl %eax, %ebxINTEL: mov ebx, eax

En AT&T, a los valores inmediatos se les añade el prefijo $ en el siguiente ejemplo, la primera instrucción carga la dirección de la variable en eax; la segunda carga el valor 0F02h en ebx

AT&T: INTEL:movl $var, %eax mov eax, offset varmovl $0xf02, %ebx mov ebx, 0f02h

Page 13: ensamblador

En AT&T, el tamaño del resultado se especifica con sufijos (b, w o l) en las instrucciones (en Intel cuando hay ambigüedad se utiliza byte ptr, word ptr o dword ptr). Si lo omitimos, GAS intentará́ “adivinar” el tamaño, y es algo que no queremos que haga...

AT&T: INTEL: movb var, %ah mov ah, byte ptr var movw %bx, %ax mov ax, bx

AT&T: INTEL: movb %bl,%al mov al,blmovw %bx, %ax mov ax, bxmovl %ebx,%eax mov eax,ebxmovl (%ebx),%eax mov eax, dword ptr [ebx]

Direccionamiento a memoria:Es uno de los aspectos que má́s cambian. Veamos la sintaxis de Intel para hacer un direccionamiento a base, con índice y desplazamiento:

[ base + índice*escala + desplazamiento ]

en la sintaxis AT&T esto queda como sigue:

desplazamiento ( base , índice , escala )

Page 14: ensamblador

Veamos dos ejemplos:

AT&T: movl array (, %eax, 4), %edx INTEL: mov edx, array[eax*4]

AT&T: INTEL: movl (%ebx) , %eax mov eax , [ebx]movl 3(%ebx) , %eax mov eax,[ebx+3]

Salto lejano AT&T: INTEL: lcall $sección, $offset call far sección:offsetljmp $sección, $offset jmp far sección:offsetlret $V ret far V

Nemotécnico. Varían los nemotécnicos de algunas instrucciones AT&T:AT&T INTEL

movswl %ax, %ecx movsx ecx, axmovzbw %ah, %cx movzx cx, ahcbtw cbwcwtl cwdecwtd cwdcltd cdq

Page 15: ensamblador

Directivas del compilador.

Como vimos, los programas ensamblador, ademá́s de las instrucciones que componen el programa, contienen órdenes al compilador que le servirá́n para definir las secciones del programa, definir los tipos de datos, macros, etc.

Como comentamos má́s arriba, hay diferencias en cuanto a algunas directivas al programar con el ensamblador GAS o NASM. En ambos ensambladores hay que definir las secciones de datos y código utilizando los mismos nombres (.data .bss .text). Sin embargo, la directiva utilizada para definir las secciones difiere de un ensamblador a otro:

Page 16: ensamblador

En ambos ensambladores, la etiqueta de entrada al programa ensamblador suele ser _start. Sin embargo, la directiva utilizada difiere de un ensamblador a otro:

La definición de datos constantes se lleva a cabo utilizando de la misma forma, pero utilizando palabras reservadas diferentes:

Page 17: ensamblador

USO DE MACROS EN GASPodemos hacer uso de macros para facilitar la programación. Para ello, debemos utilizar la directiva .macro de la siguiente forma:

.macro nombreMacroinstrucciones

.endm

En el ejemplo definiremos una macro para terminar el programa, otra para mostrar una cadena por salida está́ndar, y otra para leer cadenas de texto desde entrada está́ndar.

La forma de llamar a una macro es parecida a como se llama a una función de C/C++ (por la forma en que le pasaremos los valores):

# COMPILAR:# as -o m.o m.s# ls -o m m.o

Page 18: ensamblador

.dataretorno: .byte 0x0Amensaje1: .ascii "\n Introduce una cadena: "longitud1 = . - mensaje1buffer: .ascii " "

.text

.globl _start

_start:escribir_cadena $mensaje1 $longitud1leer_cadena $buffer $10escribir_cadena $retorno $1escribir_cadena $buffer $10escribir_cadena $retorno $1terminar

.macro terminarmovl $1,%eaxmovl $0,%ebxint $0x80

.endm

#espera ECX=cadena ; EDX=longitud

.macro escribir_cadena cadena longitudmovl $4,%eaxmovl $1,%ebx #stdoutmovl \cadena,%ecxmovl \longitud,%edx

int $0x80.endm

#espera ECX=cadena ; EDX=longitud

.macro leer_cadena cadena longitudmovl $3,%eaxmovl $0,%ebx #stdinmovl \cadena,%ecxmovl \longitud,%edxint $0x80

.endm

Page 19: ensamblador

Podemos hacer uso de funciones para facilitar la programación. Dentro de la sección .text (y antes del punto de entrada al programa) podemos definir las diferentes funciones (subrutinas), utilizando una etiqueta que indiquen el inicio de la función y cuidando siempre terminar la ejecución de ésta con la instrucción ret. Una función tendrá́ el siguiente aspecto:

nombreFuncion:instrucciones

ret

Veamos el ejemplo anterior (el de las macros) utilizando tres subrutinas. Como se verá́ en el programa principal, el paso de pará́metros a la función hay que hacerlo a través de la pila o en los registros o variables globales del programa (según como se haya programado la subrutina):

Page 20: ensamblador

funcion_leer_cadena:movl $3,%eaxmovl $0,%ebx #stdinint $0x80ret

_start:#los pará́metros se pasan en los registrosmovl $mensaje1,%ecxmovl $longitud1,%edxcall funcion_escribir_cadena

movl $buffer,%ecxmovl $10,%edxcall funcion_leer_cadena

movl $retorno,%ecxmovl $1,%edxcall funcion_escribir_cadena

movl $buffer,%ecxmovl $10,%edxcall funcion_escribir_cadena

movl $retorno,%ecxmovl $1,%edxcall funcion_escribir_cadena#esta última no necesita ningún pará́metrocall funcion_terminar

.dataretorno: .byte 0x0Amensaje1: .ascii "\n Introduce una cadena: "longitud1 = . - mensaje1buffer: .ascii " "

.text

.globl _start

funcion_terminar:movl $1,%eaxmovl $0,%ebxint $0x80ret

#pará́metros ECX=cadena ; EDX=longitud

funcion_escribir_cadena:movl $4,%eaxmovl $1,%ebx #stdoutint $0x80ret

#pará́metros ECX=cadena ; EDX=longitud

Page 21: ensamblador

NASMUna directiva es un artificio del ensamblador no de la CPU. Ellas se usan generalmente para decirle al ensamblador que haga alguna cosa o informarle al ensamblador de algo. Ellas no se traducen en código de má́quina. Los usos comunes de las directivas son:

• Definir constantes• Definir memoria para almacenar datos en ella• Definir la memoria para almacenar datos en ella• Agrupar la memoria en segmentos• Incluir código fuente condicionalmente• Incluir otros archivos

El código de NASM pasa a través de un preprocesador tal como C.

Tiene muchas de las órdenes del preprocesador tal como C. Sin embargo las directivas del preprocesador de NASM comienzan con un como en C.

directiva equ

La directiva equ se puede usar para definir un símbolo. Los símbolos son constantes con nombre que se pueden emplear en el programa ensamblador.

El formato es:símbolo equ valorLos valores de los símbolos no se pueden redefinir posteriormente.

Page 22: ensamblador

La directiva %define

Esta directiva es parecida a la #define de C. Se usa normalmente para definir macros tal como en C.

%define SIZE 100

mov eax, SIZE

El código de arriba define un macro llamado size y muestra su uso en una instrucción MOV. Los macros son má́s flexibles que los símbolos de dos maneras. Los macros se pueden redefinir y pueden ser má́s que simples constantes numéricas.

Page 23: ensamblador

Las directivas de datos son usadas en segmentos de datos para definir espacios de memoria. Hay dos formas en que la memoria puede ser reservada. La primera es solo definir el espacio para los datos; la segunda manera define el espacio y el valor inicial. El primer método usa una de las directivas RESX. La X se reemplaza con una letra que determina el tamaño del objeto (u objetos) que será́n almacenados. La tabla 1.3 muestra los valores posibles.El segundo método (que define un valor inicial también) usa una de las directivas DX. Las X son las mismas que las de la directiva RESX.

Page 24: ensamblador

Las comillas dobles o simples se interpretan igual. Las definiciones consecutivas de datos se almacenan secuencialmente en memoria. Esto es, la palabra L2 se almacena inmediatamente después que la L1. Se pueden definir también secuencias de memoria.

L9 db 0, 1, 2, 3 ; define 4 bytesL10 db "w", "o", "r", ’d’, 0 ; define una cadena tipo C = "word"L11 db ’word’, 0 ; igual que L10

Page 25: ensamblador

Veamos un ejemplo de lectura de los argumentos de línea de comando, programado para el ensamblador GAS. En este ejemplo se hace uso de todo lo descrito en “Acceso a la pila en un programa ensamblador en Linux”:

funcion_pintar_cadena: #definicion de una funcionmovl %eax,%ecx#el parametro ha sido pasado en EAXxorl %edx,%edx

contar:movb (%ecx, %edx, 1),%altestb %al,%al #comprobar el caracter de la cadenajz fin_contarincl %edx #vamos incrementando el calculo en EDXjmp contar

fin_contar:movl $4,%eax #una vez calculada la longitud,semovl $1,%ebxint $0x80

movl $4,%eax #mostramos el RETORNO_CARROmovl $retorno,%ecxmovl $1,%edx #es un solo caracterint $0x80

ret

.dataretorno: .byte 0x0A

.text

.globl _start_start:

pop %eax #extraer de la pila el ARGCrepetir: #bucle para recorrer todos los argumentospop %eax #extraer el ARGV[i]testl %eax,%eax #comprobar si es NULLjz terminarcall funcion_pintar_cadena #llamada a la funcionjmp repetir

terminar:movl $1, %eax #funcion del sistema para terminarmovl $0, %ebxint $0x80

Para cada pará́metro llamamos a una función que lo muestre por salida está́ndar. Para ello debe calcular la longitud de la cadena (argumento actual), contando uno por uno cada cará́cter que la forma. Tras cada argumento impreso, se hace un retorno de carro (es una cadena de caracteres de longitud 1). El programa muestra también el nombre del ejecutable como primer argumento (sería casi inmediato hacer que sólo muestre los argumentos reales).

Page 26: ensamblador

El siguiente ejemplo lee los 1024 primeros bytes de un fichero que le pasemos como primer argumento por la línea de comandos y los muestra por salida está́ndar. En este ejemplo, la sintaxis utilizada ha sido la de Intel (NASM).

section .datamensaje db 0xA,"---vamos a probar esto---",0xAlongitud equ $ - mensajemensaje2 db 0xA,"---hemos terminado---",0xAlongitud2 equ $ - mensaje2

tamano equ 1024

section .bssbuffer: resb1024

section .textglobal _start ;definimos el punto de entrada

_start:

mov edx,longitud ;EDX=long. de la cadenamov ecx,mensaje ;ECX=cadena a imprimirmov ebx,1 ;EBX=manejador de fichero (STDOUT)mov eax,4 ;EAX=función sys_write() del kernelint 0x80 ;interrupc. 80 (llamada al kernel)

pop ebx ;extraer "argc"pop ebx ;extraer argv[0] (nombre del ejecutable)

Page 27: ensamblador

pop ebx ;extraer el primer arg real (puntero a cadena)mov eax,5 ;función para sys_open()mov ecx,0 ;O_RDONLY (definido en fcntl.h)int 0x80 ;interrupc. 80 (llamada al kernel)

test eax,eax ;comprobar si dev. error o el descriptorjns leer_del_fichero

hubo_error:mov ebx,eax ;terminar, devolviendo el código de errormov eax,1int 0x80

leer_del_fichero:mov ebx,eax ;no hay error=>devuelve descriptorpush ebxmov eax,3 ;función para sys_read()mov ecx,buffer ;variable donde guardamos lo leidomov edx,tamano;tamaño de lecturaint 0x80js hubo_error

mostrar_por_pantalla:mov edx,eax ;longitud de la cadena a escribirmov eax,4 ;función sys_write()mov ebx,1 ;descriptor de STDOUTint 0x80

Page 28: ensamblador

cerrar_fichero:pop ebxmov eax,6 ;función para cerrar un ficheroint 0x80

mov edx,longitud2 ;EDX=long. de la cadenamov ecx,mensaje2 ;ECX=cadena a imprimirmov ebx,1 ;EBX=manejador de fichero (STDOUT)mov eax,4 ;EAX=función sys_write() del kernelint 0x80 ;interrupc. 80 (llamada al kernel)

mov ebx,0 ;EBX=código de salida al SOmov eax,1 ;EAX=función sys_exit() del kernelint 0x80 ;interrupc. 80 (llamada al kernel)

$nasm -f elf acceso_a_fich.asm$ ld -s -o acceso_a_fich acceso_a_fich.o

Page 29: ensamblador

Llamar a funciones externas definidas en un módulo de C/C++ desde ensamblador

Vamos a suponer que tenemos un módulo escrito en C/C++ que define varias funciones muy útiles que luego querremos llamar desde nuestro programa ensamblador.El módulo en C/C++ de este ejemplo tendrá́ dos funciones, una que recibe dos pará́metros, y otra que no recibe ninguno. Ambas, para este ejemplo, sólo van a mostrar por salida está́ndar un mensaje y los pará́metros recibidos. El código fuente podría ser el siguiente:

En este caso, nos vemos obligados a definir como punto de entrada del programa ensamblador, en lugar de “_start”, la etiqueta “main” (como en C/C++).Ya dentro del programa, antes de llamar a ninguna función, nuestro programa ensamblador (sintaxis GAS) debe inicializar la pila. Luego, para pasar los pará́metros (si son necesarios), debe insertarlos en la pila en el orden inverso (primero el último, etc.), hacer la llamada a la función, y por último, deshacer las inserciones hechas antes de la llamada (ajustar el valor del registro puntero de pila, SP):

#include <stdio.h>#include <stdlib.h>void func_con_parametros(int x, int y);void func_sin_parametros();void func_con_parametros(int x, int y) {

printf("Llamada con dos parametros x=%d y=%d \n", x , y );}void func_sin_parametros() {

printf("Llamada SIN parametros \n");}

Page 30: ensamblador

.datamensaje: .ascii " --mensaje desde ASM directamente--\n"mensaje_SIZE = . - mensaje

.text

.globl mainmain:

# inicializar la pila

pushl %ebpmovl %esp, %ebp

# llamar a la función que recibe 2 argumentos de tipo entero

pushl $25 #segundo parametro (4 bytes)pushl $76 #primer parametro (4 bytes)call func_con_parametros #func_con_parametros(76,25)addl $8, %esp #quitar de la pila 8 bytes

#(dos num. enteros)

# mostrar texto directamente con la INT 80hmovl $4, %eaxmovl $1, %ebxmovl $mensaje, %ecxmovl $mensaje_SIZE, %edxint $0x80

Page 31: ensamblador

# llamar a la función que NO recibe argumentos

call func_sin_parametros

# restaurar el valor del EBP# (dejar la pila como esta al principio del programa)

popl %ebp

# terminar y salir al sistema operativo

movl $1, %eaxmovl $0, %ebxint $0x80

$gcc -Wall -O2 -o progr.exe funciones.c progr.s

Page 32: ensamblador

Depuración de código usando gdb

En Linux podemos hacer uso del gdb para depurar el código que hemos escrito (trazarpaso a paso, comprobar el valor de ciertos registros en cada momento, etc).Para ello, debemos ensamblar nuestros programas con una opción especial del as :

as –a –-gstabs –o prog.o prog.s

la opción –a nos mostrará́ un listado de memoria durante el proceso de ensamblaje,donde podremos ver la localización de las variables y código respecto al principio de lossegmentos de código y datos. La opción ––gstabs introduce información de depuraciónen el fichero binario, que luego usará́ gdb.

Primer paso de la depuración: llamar a gdb indicá́ndole el ejecutable a depurar

Page 33: ensamblador

La orden l muestra el texto del programa de 10 en 10 líneas:

Page 34: ensamblador

Antes de ejecutar el programa debemos establecer dos puntos de ruptura (break): unocorrespondiente a la etiqueta de comienzo del programa (_start) y otro en la líneasiguiente (en el primero no para, pero es necesario ponerlo...). Vemos que al poner elprimer punto, nos indica un número de línea. Nosotros debemos poner otro punto en lalínea cuyo número es el siguiente al que nos acaba de indicar. Una vez hecho esto, yapodemos ejecutar el programa (run):

Podemos ir viendo los valores de los registros mediante info registers o bien con p/x

Page 35: ensamblador
Page 36: ensamblador

La traza paso a paso del programa la haremos con la orden step . A cada paso nos vamostrando la instrucción a ejecutar a continuación; mediante las órdenes anteriorespodremos ir viendo cómo cambia el contenido de los registros:

Page 37: ensamblador

ALGUNAS SEMEJANZAS ENTRE COMANDOS ALGUNAS SEMEJANZAS ENTRE COMANDOS DE C/C++ Y ENSAMBLADORDE C/C++ Y ENSAMBLADOR

Page 38: ensamblador

INSTRUCCIONES (move y extend) movz y INSTRUCCIONES (move y extend) movz y movzxmovzx

.databyteval: .byte 204.text .global _start_start: movzbw byteval, %ax # %eax is now 204 movzwl %ax, %ebx # %ebx is now 204 movzbl byteval, %esi # %esi is now 204# Linux sys_exit mov $1, %eax xorl %ebx, %ebx int $0x80

Page 39: ensamblador

.databyteval: .byte 204.text .global _start_start: movzbw byteval, %ax # %eax is now 204 movzwl %ax, %ebx # %ebx is now 204 movzbl byteval, %esi # %esi is now 204# Linux sys_exit mov $1, %eax xorl %ebx, %ebx int $0x80

Page 40: ensamblador

.file "MayorenArreglo.s"

.section .dataTam: .long 5Arreglo: .int 0,2,4,8,10prueba: .long 4

.text

.global _start _start:

movl Tam, %ecxloop1:

cmpl %eax, Arreglo(,%ecx,4)JLE elsemovl Arreglo(,%ecx,4), %eaxjmp fin

else:#En este caso no hay Elsefin:

loop loop1xorl %eax, %eaxincl %eaxxorl %ebx, %ebxint $0x80

.end

Page 41: ensamblador
Page 42: ensamblador

CONTROL DE FLUJO, LOOPCONTROL DE FLUJO, LOOP

El control de flujo en los programas de alto nivel se realiza con estructuras if, while, for. En ensamblador dichas estructuras no existen, en cambio, existen los saltos condicionales, una especie de goto condicionados, con los cuales se pueden escribir cada una de las rutinas que utilizan los lenguajes de alto nivel. Las instrucciones del ensamblador de Intel que nos permiten hacer esto.

La instrucción Loop es muy útil para algoritmos de la forma:

El Loop funciona de la siguiente manera:

1. Se hace dec %ECX2. Si ECX es igual a cero, pasa a la siguiente instrucción. Si no, salta a la etiqueta que se le pasa por pará́metro.

Siendo así, una secuencia loop será́ de la forma:1. mov n, %ecx2. bucle:3. ##SECUENCIA4. loop bucle

Page 43: ensamblador

LA SENTENCIA if-then-elseLA SENTENCIA if-then-else

CMP: Sirve para comparar. Internamente es una resta entre los 2 operandos que se comparanJE: Este es un salto que se ejecuta cuando la comparació� n anterior dio como resultado que los 2 operandos son iguales.JNE: Salta si no es igual.JG: Salta si es mayor.JGE: Salta si es mayor o igual.JL: Salta si es menor.JLE: Salta si es menor o igual.JMP: Salta siempre.

1. cmp A,B2. <Salto> else3. #SECUENCIA DE PASOS SI SE CUMPLE LA CONDICIÓN4. jmp fin5. else:6. #SECUENCIA DE PASOS SI NO SE CUMPLE LA CONDICIÓN7. fin:

Page 44: ensamblador

LA SENTENCIA WHILELA SENTENCIA WHILE

1. while:2. cmp a,b3. <Salto> salir4. #SECUENCIA DE PASOS DENTRO DEL WHILE5. jmp while6. salir:

Algoritmo de un número Mayor en Arreglo

Page 45: ensamblador

FUNCIONES Y MODULARIDADFUNCIONES Y MODULARIDADA medida que un programa aumenta de complejidad, se detectan ciertas porciones de código que realizan lo mismo basados en 0 o mas pará́metros. A estos fragmentos, podemos agruparlos en funciones. Independiente de la arquitectura utilizada, una función está́ constituida por dos componentes: El Llamador y el Llamado

El Llamador (Caller ) y el Llamado (Called)El Llamador1 es la parte del código que tiene estos objetivos:

Colocar Los pará́metros en el sitio adecuadoGuardar la dirección de retornoInvocar el Llamado

El Llamado, es la parte que se encarga de:

Garantizar que todo quede como estaba.Localizar (si los hay) los pará́metros.Definir (si las hay) las variables locales.Recuperar la dirección de retornoRetornar el control al llamador.

Page 46: ensamblador

Una Aproximación al Llamador y el Llamado desde asm

1. .section .data2.3. a: .long 44. b: .long 55.6. .section .text7. .global sumar8. sumar:9. pushl %ebp10. movl %esp, %ebp11. movl 8(%ebp), %ebx12. movl 12(%ebp), %eax13. addl %ebx, %eax14. #leave15. ret

17. .global _start18. _start:19. # "Main"20. pushl a21. pushl b22. call sumar23. popl %ebx24. popl %ebx25. # Finalizo el programa26. xorl %eax, %eax27. incl %eax28. xorl %ebx, %ebx29. int $0x8030. end

Page 47: ensamblador

Programación en Lenguaje Ensamblador

Programación en Lenguaje Ensamblador

Este material se refiere al compilador gcc (GNU

compiler collection) el cual corre bajo ambiente

Linux. El ensamblador de gcc se llama as y por

formar parte de gcc comúnmente se conoce

como gas.

Este material se refiere al compilador gcc (GNU

compiler collection) el cual corre bajo ambiente

Linux. El ensamblador de gcc se llama as y por

formar parte de gcc comúnmente se conoce

como gas.

Page 48: ensamblador

Programación en Lenguaje Ensamblador

Plataforma: IA-32IA-32 es la arquitectura de microprocesadores de 32 bits de Intel (Intel Architecture 32). Son los microprocesadores má́s usados en los ordenadores personales (PC).

Esta gama de microprocesadores comenzó con el Intel 80386 en 1985, conocido luego popularmente como 386 o x86 para denominar a toda la gama. Los procesadores de Intel que siguieron y mantuvieron la compatibilidad son el 486, Pentium, Pentium II, Pentium III, Pentium 4, y la línea Intel Core.

La novedad de estos procesadores con respecto a sus predecesores es que incluyen gestión de memoria avanzada (segmentación, paginación, soporte de memoria virtual), unidad de punto flotante, y a partir del Pentium MMX, soporte para operaciones matriciales complejas, muy usadas en aplicaciones grá́ficas y multimedia.

Page 49: ensamblador

Plataforma: IA-32

Tipos de datos

La información se puede accesar de diversas maneras. Se puede leer un sólo byte (8 bits) o un conjunto de bytes, en esta má́quina en particular se denomina palabra a dos bytes y doble palabra a 4 bytes. La notación puede ser en decimal o en hexadecimal.

Page 50: ensamblador

Plataforma: IA-32

Tamaños de los datos:

Page 51: ensamblador

Plataforma: IA-32

Orden de los datos en memoria:

En gas las instrucciones utilizan un sufijo para indicar el tamaño de los datos sobre los cuales operan.

El sistema guarda los datos en memoria en secuencia inversa de bytes (little endian) lo cual trae como consecuencia que el byte menos significativo se ubica en la posición de menor orden y el byte más significativo en la posición de memoria de mayor orden. Por ejemplo si se transfiere el dato 0x457A a las posiciones consecutivas de memoria 0x100 y 0x101 se ubica el byte 7A en la posición 0x100 y el byte 45 en la posición 0x101.

Page 52: ensamblador

Plataforma: IA-32

Registros de propósito general

Los registros de propósito general se utilizan para almacenar

datos temporalmente, debido a que estos registros han

evolucionado desde una má́quina de 8 bits (el 8080) un grupo

de registros aún se puede acceder de 8 bits para mantener

compatibilidad con toda la línea de procesadores.

Aún cuando estos registros pueden mantener cualquier tipo

de datos, algunos tienen cierta funcionalidad específica o son

usados de manera especial por algunas instrucciones.

Page 53: ensamblador

Plataforma: IA-32

Registros de propósito general

Page 54: ensamblador

Plataforma: IA-32

Registros de propósito general

En gas los registros se denotan usando el símbolo de porcentaje antes del nombre del registro. Los registros %eax, %ebx, %ecx y %edx pueden ser accesados con tamaños de 8, 16 o 32 bits cambiando su nomenclatura de acuerdo al tamaño. Ejemplo para %eax:

Page 55: ensamblador

Plataforma: IA-32

Registros de propósito general

Los registros %edi, %esi, %ebp y %esp se pueden accesar como registros de 16 o 32 bits.

Ejemplo para %edi:

Page 56: ensamblador

Plataforma: IA-32

Registro de instrucción: El registro de instrucción o

contador de programa contiene la dirección de la próxima

instrucción a ejecutarse.

Registros de punto flotante: Son 8 registros los

cuales son tratados como una pila. Se nombran %st(0),

%st(1), %st(2), etc. %st(0) se ubica en el tope de la pila.

Banderas: Proveen una manera de obtener información

acerca del estado actual de la máquina y el resultado de

procesamiento de una instrucción. La plataforma IA-32 utiliza

un registro de 32 bits llamado EFLAGS que contiene las

banderas.

Page 57: ensamblador

Plataforma: IA-32

Banderas:

Estas son las banderas más comunes:

Page 58: ensamblador

Plataforma: IA-32Banderas:

• La bandera de acarreo se activa cuando se produce

acarreo en una operación matemática entre números sin signo.

• La bandera de paridad se usa para indicar si el resultado, en un registro, de una operación matemática es válido.

• La bandera de ajuste se utiliza en operaciones matemáticas con números decimales codificados en binario (BCD). Se activa si hay acarreo presente.

• La bandera de cero se activa si el resultado de una operación es cero.

• La bandera de signo muestra el bit más significativo del resultado de una operación, el cual denota el signo del número.

Page 59: ensamblador

Plataforma: IA-32Banderas:

• La bandera de dirección controla la selección de

autoincremento o autodecremento de los registros %edi

o %esi durante las operaciones con cadenas de

caracteres.

• La bandera de dirección sólo se utiliza con las

instrucciones para el manejo de cadenas de caracteres.

• La bandera de desbordamiento se utiliza en la

aritmética de enteros con signo cuando un número

sobrepasa la capacidad de representación del registro.

Page 60: ensamblador

Programación en ensambladorUn programa en lenguaje ensamblador está orientado a

líneas y cada enunciado especifica una operación

sencilla.

Conceptos básicos de los programas en lenguaje

ensamblador:

Espacio: Un espacio es equivalente a cualquier

número de espacios o tabuladores. Los espacios no

pueden aparecer en medio de un número o identificador.

Comentario: Texto que aparece después de un

carácter de inicio de comentario, el ensamblador ignora

los comentarios. Los comentarios comienzan con el

símbolo #.

Page 61: ensamblador

Programación en ensambladorConceptos básicos de los programas en lenguaje

ensamblador:

Identificador: Es una letra seguida de cualquier

cantidad de letras o dígitos (y en algunos casos

caracteres especiales).

Etiqueta: identificador seguido de dos puntos.

Instrucción: es una operación seguida de una lista

de operandos.

Debe haber un espacio entre el nombre de la operación

y la lista de operandos. Los operandos se separan por

comas, el número de operandos en la lista depende de

la operación.

Page 62: ensamblador

Programación en ensamblador

Conceptos básicos de los programas en lenguaje ensamblador:

Directriz: consiste en un nombre de directriz seguido de una lista de parámetros. Los nombres de la directrices comienzan con un punto.

Con las directrices se especifica la forma en que el ensamblador traduce las instrucciones, es decir, las directrices dirigen el proceso de traducción.

Page 63: ensamblador

Programación en ensambladorEl programa escrito en lenguaje ensamblador se

compone de varias secciones. Las secciones má́s

comunes son:

• sección de texto

• sección de datos

• sección bss.

En la sección de texto se escriben las instrucciones, en

la sección de datos los datos inicializados y en la

sección bss las variables sin inicializar.

Cada una de las secciones se declara por medio de una

directiva.

Page 64: ensamblador

Programación en ensamblador

Para declarar las secciones mencionadas se usan las siguientes directivas:

.section .text para la sección de texto

.section .data para la sección de datos

.section .bss para la sección bss

Comúnmente las secciones se colocan en la siguiente secuencia:

.section .data

.section .bss

.section .text

bss son las siglas correspondientes a "block storage start", que significa inicio de bloque de almacenaje.

Page 65: ensamblador

Programación en ensamblador

Punto de inicio de programa: se define por medio de la

declaración de una etiqueta: _start la cual indica a partir

de qué instrucción se comienza a ejecutar el código.

Esta etiqueta debe ser declarada como global, es decir,

que esté disponible para aplicaciones externas; esto se

logra utilizando la directiva .globl.

Page 66: ensamblador

Programación en ensamblador

Finalización del programa: Gas no provee una instrucción de fin de ejecución, esto se logra mediante una llamada al sistema. Para realizar esta llamada se pasa dos pará́metros:

El valor 1 en el registro %eax indica el código de llamada a exit (salida).

El valor 0 en el registro %ebx indica la salida normal del programa.

Page 67: ensamblador

Programación en ensamblador

Estructura generalEn general la estructura de un programa en lenguaje ensamblador tiene la siguiente forma:

Page 68: ensamblador

Programación en ensamblador

Los datos se definen en las secciones .data y .bss.Para definir datos en la sección .data se pueden utilizar las siguientes directivas:

Page 69: ensamblador

Programación en ensamblador

El formato para estas directivas es el siguiente:

etiqueta: directiva valor

Ejemplo: declaración de variables inicializadas

Se pueden definir múltiples valores en la misma línea. Cada uno de ellos será́ guardado en memoria en el orden en el cual fueron declarados.

Page 70: ensamblador

Programación en ensamblador

Declaración de múltiples valores con una misma etiqueta

En este caso cuando se lee la variable var arroja el valor 10, para poder leer el siguiente valor se debe incrementar la dirección de var en 4 bytes (debido a que la variable está́ declarada como long, es decir de 32 bits) de esta manera se usa la etiqueta var como la dirección inicial de estos valores y su tratamiento es el de un arreglo donde cada acceso se realiza tomando var como posición inicial lo cual sería equivalente a decir var[0] y las posiciones siguientes como un desplazamiento de 4 bytes cada uno. Para leer por ejemplo el valor 30 se accesaría var+8.

Page 71: ensamblador

Programación en ensambladorCuando se definen las variables, el sistema las guarda en

forma consecutiva en memoria. Por ejemplo si se definen

variables de 16 bits y luego se leen usando instrucciones de

32 bits el sistema no produce un mensaje de error y accesa

los bytes consecutivos leyendo datos no vá́lidos.

Aún cuando la sección .data se utiliza principalmente para

definir variables también puede ser usada para definir

constantes. Esto se hace usando la directiva .equ, el formato

para esta directiva es:

directiva símbolo, valor

Ejemplo: definición de constantes

Page 72: ensamblador

Programación en ensamblador

Para definir datos en la sección bss se usan dos

directivas:

La directiva .lcomm se usa para datos locales, que no será́n usados fuera del código local.El formato, para ambas directivas es el siguiente:

directiva símbolo, tamaño en bytesEjemplo : declaración de un á́rea de memoria sin

inicializar

Se declara una variable llamada area de 100 bytes de tamaño.

Page 73: ensamblador

Programación en ensamblador

La ventaja de declarar variables en la sección .bss es

que esos datos no se incluyen en el programa ejecutable

y por lo tanto el tamaño total del programa es menor al

tamaño generado por la declaración equivalente en la

sección .data.

Page 74: ensamblador

Programación en ensamblador

InstruccionesLas instrucciones en gas tienen un sufijo que indica el tamaño del dato sobre el cual actúa la instrucción.

Las instrucciones pueden no tener operandos, tener un sólo operando o dos operandos; dependiendo de la instrucción en particular.En general las instrucciones tienen la forma:

instrucción operando fuente, operando destino

Page 75: ensamblador

Programación en ensamblador

Los operandos se pueden clasificar en tres tipos:

• Inmediato: para valores constantes.

• Registro: denota el contenido de uno de los registros.

• Referencia a memoria: denota el contenido de una

posición de memoria.

Page 76: ensamblador

Programación en ensambladorHay varias maneras de obtener la información las cuales se pueden resumir en la siguiente tabla:

inm denota un inmediatoreg denota un registro, regb un registro base y regi un registro índicee es la escala la cual puede ser 1, 2, 4 ó 8R[reg] significa el contenido del registro regM[x] significa el contenido de la posición de memoria con dirección x

Page 77: ensamblador

Programación en ensamblador

Ejemplo: valores para cada modo de direccionamiento Asumiendo los contenidos de: %eax= 0x100 y %ecx= 0x10

Page 78: ensamblador

Programación en ensamblador

El valor inmediato se puede expresar en decimal o en hexadecimal como se puede observar en el siguiente ejemplo:Asumiendo el contenido de %eax=0x100

Page 79: ensamblador

Programación en ensamblador

La instrucción mov

La instrucción mov permite el movimiento de datos, ya que gas utiliza un prefijo para señalar el tamaño de los datos podemos tener tres opciones al momento de realizar una transferencia de datos:

movb mueve un byte

movw mueve una palabra (2 bytes)

movl mueve dos palabras (4bytes)

Usaremos la nomenclatura F para denotar el operando fuente y D para denotar el operando destino.

Page 80: ensamblador

Programación en ensambladorLa instrucción mov

La instrucción mov permite el movimiento de datos, ya que gas utiliza un prefijo para señalar el tamaño de los datos podemos tener tres opciones al momento de realizar una transferencia de datos:

movb mueve un byte

movw mueve una palabra (2 bytes)

movl mueve dos palabras (4bytes)

Usaremos la nomenclatura F para denotar el operando fuente y D para denotar el operando destino.

Page 81: ensamblador

Programación en ensambladorMovimiento de datos inmediatos a registro o a memoriaLos datos inmediatos se especifican directamente en la instrucción. Deben estar precedidos por el símbolo dólar para indicar que son datos inmediatos. Pueden estar expresados en decimal o hexadecimal.Ejemplo:

Page 82: ensamblador

Programación en ensambladorMovimiento de datos entre registrosEsta es la transferencia de datos que toma menor tiempo dentro del sistema es buena prá́ctica de programación utilizar este tipo de transferencia en vez de accesos a memoria ya que ello redunda en una mayor eficiencia.Ejemplo:

Page 83: ensamblador

Programación en ensambladorMovimiento de datos entre memoria y registros

Las direcciones de memoria usualmente se expresan con etiquetas, cuando por ejemplo se escribe:

a debe haber sido declarado en la sección de datos.

Como se está́n transfiriendo 4 bytes éstos será́n guardados en memoria de manera consecutiva a partir de la posición a.

Page 84: ensamblador

Programación en ensamblador

Movimiento de datos entre memoria y registros

Para mover la información de memoria a un registro se escribe:

Page 85: ensamblador

Programación en ensambladorPara leer o escribir en arreglos se utiliza direccionamiento indexado. Hay que tomar en cuenta la dirección inicial del arreglo y utilizar un índice para ir recorriendo cada uno de sus elementos. El tamaño de los datos representa el desplazamiento entre dos posiciones del arreglo.

La forma general de accesar un arreglo se puede expresar como:

dirección inicial (desplazamiento, índice, escala)

La dirección se calcula como:

dirección inicial + desplazamiento + índice *escala

Donde escala refleja el tamaño del tipo de dato del arreglo.

Page 86: ensamblador

Programación en ensambladorEjemplo de declaración y lectura de un arreglo de enteros:

Page 87: ensamblador

Programación en ensambladorMovimiento de datos con extensiónHay dos instrucciones adicionales que permiten mover datos extendiéndolos.

Page 88: ensamblador

Programación en ensambladorMovimiento de datos con extensiónHay dos instrucciones adicionales que permiten mover datos extendiéndolos.

La instrucción movsbl toma un operando fuente de 1 byte, ejecuta una extensión de signo a 32 bits y lo copia al destino de 32 bits.La instrucción movzbl hace un procedimiento similar pero extiende con ceros.Ejemplo de movsbl:

Ejemplo de movzbl:

Page 89: ensamblador

Programación en ensambladorUso de la pilaLa pila es un á́rea de memoria que crece desde una dirección inicial hacia direcciones menores. El último elemento colocado en la pila es el que está́ disponible para ser retirado.Las instrucciones para el manejo de la pila son dos, una para apilar un operando fuente y una para desapilar el valor que está́ en el tope de la pila y colocarlo en un operando destino.

Page 90: ensamblador

Programación en ensambladorUso de la pilaEjemplo de uso de las instrucciones pushl y popl: Dados los valores iniciales:

%esp=0x108 %ebx=0xCD%eax=0xAB %ecx=0xFE

Al ejecutarse las instrucciones:

Ocurre lo siguiente:

Page 91: ensamblador

Programación en ensamblador

Page 92: ensamblador

Programación en ensamblador

Uso de la pila

Carga dirección efectiva (instrucción leal)La instrucción leal (load effective address) permite obtener la dirección de un operando en vez de su valor.

Page 93: ensamblador

Programación en ensamblador

Carga dirección efectiva (instrucción leal)La instrucción leal (load effective address) permite obtener la dirección de un operando en vez de su valor.

Ejemplo:Dados los siguientes valores en los registros:

%eax=0x100 %ebx=0x10

Page 94: ensamblador

Programación en ensambladorInstrucciones aritméticas y lógicasPara estas instrucciones usaremos el sufijo l para los ejemplos aunque también se pueden usar con b o w.

Page 95: ensamblador

Programación en ensambladorInstrucciones aritméticas y lógicas

Page 96: ensamblador

Programación en ensambladorOperaciones aritméticas especiales

Page 97: ensamblador

Programación en ensambladorEjemplo de un programa que realiza algunas operaciones aritméticas:

Page 98: ensamblador

Programación en ensamblador

Control de flujo

Los códigos de condición describen los atributos de las operaciones aritméticas y lógicas má́s recientes. Por lo tanto la ejecución de las instrucciones aritméticas y lógicas afectan las banderas. La instrucción leal no afecta las banderas.

Las banderas má́s comunes son las siguientes:

Bandera de Acarreo (CF), bandera de Cero (ZF), bandera de Signo (SF) y bandera de desbordamiento (OF).

Las instrucciones cmpl y testl actualizan las banderas sin afectar los operandos. También existen las versiones con los sufijos b y w.

Page 99: ensamblador

Programación en ensamblador

Control de flujo

Cuando se usa cmpl se compara el operando 1 con el operando 2Ejemplo:

testl generalmente se usa repitiendo el operando para saber si es cero, positivo o negativo.Ejemplo:

Page 100: ensamblador

Programación en ensambladorActualización de un registro con valor de una banderaEs posible transferir el contenido de una bandera o una condición que dependa de la combinación de varias banderas a un registro. Esto es útil para poder usar la misma condición en varias partes del programa.Estas instrucciones sólo mueven un byte, las má́s usadas son:

Page 101: ensamblador

Programación en ensambladorActualización de un registro con valor de una bandera

Ejemplo:

Page 102: ensamblador

Programación en ensambladorLas instrucciones de saltoUna instrucción de salto produce un cambio en la ejecución del programa pasando a una nueva posición, no secuencial. La nueva dirección se representa con una etiqueta.

Page 103: ensamblador

Programación en ensamblador

Las instrucciones de salto

La instrucción jmp salta de manera incondicional, es decir, no

chequea ningún código de condición. El salto puede ser directo

a la dirección representada por la etiqueta o indirecto donde la

dirección de destino se lee de un registro o de una posición de

memoria.

Ejemplo de salto incondicional directo:

En este caso la tercera instrucción no se ejecuta ya que al

ejecutarse el salto el programa pasa a ejecutar la instrucción 4.

Page 104: ensamblador

Programación en ensamblador

Las instrucciones de salto

Ejemplo de salto incondicional indirecto:

Dados los siguientes valores: %eax = 0x120 y el contenido

de la posición de memoria 0x120 = 0x180

Page 105: ensamblador

Programación en ensamblador

Las instrucciones de salto

Las otras instrucciones de salto son condicionales lo cual

significa que la má́quina revisa los códigos de condición antes

de realizar el salto, si la condición se cumple realiza el salto a la

etiqueta especificada, si la condición no se cumple continúa la

ejecución de manera secuencial.

Ejemplo de salto condicional:

Page 106: ensamblador

Programación en ensamblador

Las instrucciones de salto

Podemos observar en el ejemplo anterior que si se cumple la

condición de comparación valor1<valor2 el programa salta a la

instrucción 7 sin pasar por las instrucciones 5 y 6. En caso de

no cumplirse la condición, es decir valor1>=valor2, entonces

continúa la ejecución en la instrucción siguiente, en este caso, la

instrucción 5. Es importante destacar la necesidad de introducir

la instrucción 6, un salto incondicional a etiq2 ya que de no estar

presente la má́quina seguiría el orden secuencial y luego de

realizar la resta ejecutaría la suma lo cual arrojaría un resultado

erróneo. En este programa sólo se ejecuta una de las dos

operaciones aritméticas, la resta en caso de cumplirse la

condición o la suma en caso de que no se cumpla.

Page 107: ensamblador

Programación en ensambladorTraducción de estructuras condicionalesPara traducir expresiones de la forma:

if (condición) instruccioneselse instrucciones

En lenguaje ensamblador se utilizan los saltos condicionales.Por ejemplo para realizar un extracto de programa que haga lo siguiente:

if (x>y)result=x-y;

elseresult=y-x;

Page 108: ensamblador

Programación en ensamblador

Page 109: ensamblador

Programación en ensamblador

Ciclos

En lenguaje ensamblador se implementan los ciclos

usando comparaciones de condiciones y saltos.

Do while

While

For

Page 110: ensamblador

Do whileConsideremos el siguiente programa en lenguaje C para encontrar el valor del elemento n en la serie de Fibonacci.La serie se define como F1=1; F2=1; Fn=Fn-1+Fn-2 para n>=3. Por ejemplo los primeros 7 elementos de la serie son: 1,1,2,3,5,8,13

int fib_dw(int n){

int i=0;int val=0;int nval=1;int t=0;do{

t=val+nval;val=nval;nval=t;i++;

}while (i<n);return val;

}

Page 111: ensamblador

Do whileAsumamos que el pará́metro n está́ en el registro %esi.Usaremos los registros %ecx, %ebx, %edx y %eax para representar las variables i, val, nval y t respectivamente.

El resultado estará́ en la variable val, en este caso en %ebx

Page 112: ensamblador

Do while

Page 113: ensamblador

WhileCuando se utiliza while se comprueba la condición antes de entrar en el ciclo.Se puede reescribir el ejemplo anterior utilizando un while de la siguiente manera:

int fib_w(int n){

int i=1;int val=1;int nval=1;int t=0;while (i<n){

t=val+nval;val=nval;nval=t;i++;

}return val;

}

Page 114: ensamblador

WhileDe nuevo asumimos que n está́ en %esi y usamos los mismos registros anteriores:

Page 115: ensamblador

While

Page 116: ensamblador

ForEn este caso también se revisa la condición antes de entrar al ciclo. El ejemplo anterior escrito con un for:

int fib_f(int n){

int i;int val=1;int nval=1;int t=0;for(i=1; i<n; i++){

t=val+nval;val=nval;nval=t;

}return val;

}

Page 117: ensamblador

ForDe nuevo usamos las mismas variables:

Esta solución es casi idéntica a la del while.

Page 118: ensamblador

Arreglos

Cuando se declara un arreglo en C se escribe una expresión con la siguiente estructura:

T A[N]

Esta declaración designa el nombre del arreglo como A de

N posiciones del tipo de dato T. Esto produce la asignación

de un espacio de memoria de N*T, es decir el número de

elementos del arreglo por el número de bytes del tipo de

dato. Los elementos del arreglo se acceden con un índice

cuyo rango es { 0, N-1}.

Page 119: ensamblador

Arreglos

Ejemplo de declaración y pará́metros de arreglos en C:Al declarar los arreglos:char A[12];int B[8];double C[6];

Sus pará́metros será́n los siguientes:

Page 120: ensamblador

Arreglos

En lenguaje ensamblador se utiliza el tipo de direccionamiento escalado para acceder a las posiciones de un arreglo.

Por ejemplo si se declara el arreglo a como:

Los elementos del arreglo se guardan en memoria en posiciones consecutivas y el identificador a apunta a la primera posición del arreglo, es decir la posición cuyo índice es cero.

Page 121: ensamblador

Arreglos

Para leer un elemento de a se puede utilizar la siguiente instrucción:

donde a es la dirección inicial del arreglo y se utiliza un registro, en este caso %ebx como índice. La escala (4) viene dada por el tamaño del tipo de dato del arreglo, en este caso es 4 porque a está́ declarado como "long".

Page 122: ensamblador

Arreglos

Ejemplo: dado un arreglo de 10 posiciones, inicializado, sumar los valores del arreglo y guardar el resultado en la variable "result"

Page 123: ensamblador

Arreglos

Cuando se declara un arreglo de dos dimensiones como por ejemplo el arreglo A declarado en C como: int A[4][3];Se reserva el espacio de memoria para 12 elementos (4*3) y se ordenan de la siguiente manera:

En general para un arreglo de dos dimensiones A[R][C] el elemento A[i][j] está́ en la dirección XA + número de bytes del tipo de dato por (C * i + j). Para el ejemplo anterior el elemento A[2][2] está́ en la dirección XA+4(3*2+2)=XA+32.

Page 124: ensamblador

Arreglos

Ejemplo en ensamblador:Declaración del arreglo a de dos dimensiones a [3][2] con los siguientes contenidos:

Page 125: ensamblador

Arreglos

Lectura del elemento a [2][0]:La dirección de a [2][0] es: a +4*(2*2+0)= a+16

Page 126: ensamblador

Los primeros procesadores Intel no tenían soporte de hardware para las operaciones de punto flotante. Esto no significa que ellos no podían efectuar operaciones de punto flotante. Esto sólo significa que ellas se realizaban por procedimientos compuestos de muchas instrucciones que no son de punto flotante. Para estos primeros sistemas, Intel suministraba un circuito integrado adicional llamado coprocesador matemá́tico. Un coprocesador matemá́tico tiene instrucciones de má́quina que realizan instrucciones de punto flotante mucho má́s rá́pido que usando procedimientos de software (en los primeros procesadores se realizaban al menos 10 veces má́s rá́pido). El coprocesador para el 8086/8088 fue llamado 8087. Para el 80286 era el 80287 y para el 80386 un 80387. El procesador 80486DX integrá́ el coprocesador matemá́tico en el 80486 en sí mismo. Desde el Pentium, todas las generaciones del 80X86 tienen un coprocesador matemá́tico interno; sin embargo todavía se programa como si fuera una unidad separada. Aún los primeros sistemas sin un coprocesador pueden instalar un software que emula el coprocesador matemá́tico. Estos emuladores se activan automá́ticamente cuando un programa ejecuta una instrucción del coprocesador y corre un procedimiento que produce los mismos resultados que el coprocesador (mucho má́s lento claro está́. El coprocesador numérico tiene ocho registros de punto flotante. Cada registro almacena 80 bits. Los números de punto flotante se almacenan en estos registros siempre como números de 80 bits de precisión extendida.

Los registros se llaman STO, ST1, . . . ST7. Los registros de punto flotante se usan diferentes que los registros enteros en la CPU. Los registros de punto flotante está́n organizados como una pila. Recuerde que una pila es una lista LIFO (Last In Firist Out). STO siempre se refiere al valoren el tope de la pila. Todos los números nuevos se añaden al tope de la pila. Los números existentes se empujan en la pila para dejarle espacio al nuevo número. Hay también un registro de estado en el coprocesador numérico. Tiene varias banderas. Sólo las 4 banderas usadas en comparaciones será́n estudiadas. C0 , C1 , C2 and C3 . El uso de ellas se discutirá́ luego.

Page 127: ensamblador

LA UNIDAD DE PUNTO FLOTANTELA UNIDAD DE PUNTO FLOTANTEEn decimal, los dígitos a la derecha del punto decimal tienen asociados potencias de 10 negativas.

No es sorprendente que los nú�meros binarios trabajen parecido.

Conversión a binario del número decimal 0.5625

Conversión a binario del número decimal 0.85

Page 128: ensamblador

REPRESENTACIÓN DE NÚMEROS CON REPRESENTACIÓN DE NÚMEROS CON EXPONENTESEXPONENTES

Un número de punto flotante a normalizado tiene la forma:1,sssssssssssx2eeeeeeeeeeeeeeeee

Donde 1, sssssssssssss es la mantisa y eeeeeeee es el exponente.

El IEEE (Institute of Electerical and Electronic Engineer) es una organización internacional que ha diseñado formatos binarios específicos para almacenar números de punto flotante. Este formato se usa en la mayoría de (pero no en todas) las computadoras hechas hoy día. A menudo es soportado por el hardware de la computadora en sí mismo. Por ejemplo el coprocesador numérico (o matemá́tico) de Intel (que está́ insertado en todas las CPU que hacen uso de este procesador desde el Pentium) lo usa. El IEEE define dos formatos con precisión diferentes: precisión simple y doble, la precisión simple es usada por las variables del tipo float en C y la precisión doble es usada por la variable double.

El coprocesador matemá́tico de Intel utiliza un tercer nivel de mayor precisión llamado precisión extendida. De hecho, todos los datos en el coprocesador en sí mismos está́n en esta precisión. Cuando se almacenan en la memoria desde el coprocesador se convierte a precisión simple o doble automá́ticamente. La precisión extendida usa un formato general ligeramente

Page 129: ensamblador

PRECISIÓN SIMPLE IEEEPRECISIÓN SIMPLE IEEEEl punto flotante de precisión simple usa 32 bits para codificar el número. Normalmente son exactos los primeros 7 dígitos decimales. Los números de punto flotante son almacenados en una forma mucho má́s complicada que los enteros. La siguiente figura muestra la forma bá́sica del formato de precisión simple del IEEE. Hay varias peculiaridades del formato. Los números de punto flotante no usan la representación en complemento a 2 para los números negativos. Ellos usan la representación de magnitud y signo. El bit 31 determina el signo del número como se muestra.

El exponente binario no se almacena directamente. En su lugar, se almacena la suma del exponente y 7F en los bits 23 al 30. Este exponente polarizado siempre es no negativo.La parte fraccionaria se asume que es normalizada (en la forma 1.sssssssss).Ya que el primer bit es siempre uno, éste uno no se almacena. Esto permite el almacenamiento de un bit adicional al final y así se incrementa un poco la precisión. Esta idea se conoce como la representación oculta del uno.

¿Cómo se podría almacenar 23.85? Primero es positivo, así el bit de signo es 0, ahora el exponente verdadero es 4, así que el exponente es 7F + 4 = 8316 . Finalmente la fracción es (recuerde el uno de adelante está́ oculto).

Colocando todo esto unido (para ayudar a aclarar las diferentes secciones del formato del punto flotante, el bit de signo y la fracción han sido subrayados y los bits se han agrupado en nibles):

Page 130: ensamblador
Page 131: ensamblador

La doble precisión IEEE usa 64 bits para representar números y normalmente son exactos los 15 primeros dígitos decimales significativos. Como muestra la Figura 6.4, el formato bá́sico es muy similar a la precisión simple. Se usan má́s bits para el exponente (ll) y la fracción (52) que para la precisión simple.El gran rango para el exponente tiene dos consecuencias. La primera es que se calcula como la suma del exponente real y 3FF (1023) (no 7F como para la precisión simple). Segundo, se permite un gran rango de exponentes verdaderos (y así usa un gran rango de magnitudes). Las magnitudes de precisión doble van de 10−308 hasta 10308 aproximadamente.En el campo de la fracción el responsable del incremento en el número dígitos significativos para los valores dobles.Como un ejemplo, considere nuevamente 23.85 otra vez. El exponente polarizado será́ 4 + 3FF = 403 en hexadecimal. As � la representación doble sería:

0 100 0000 0011 0111 1101 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1010O 40 37 D9 99 99 99 99 9A en hexadecimal. Si uno convierte esto a decimal uno encuentra 23.8500000000000014 (¡hay 12 ceros!) que es una aproximación mucho mejor de 23.85.

La precisión doble tiene los mismos valores especiales que la precisión simple. 3 Los números no normalizados son muy similares también. La principal diferencia es que los números dobles sin normalizados usan 2−1023 en lugar de 2−127 .

Page 132: ensamblador

ARITMÉTICA DE PUNTO FLOTANTELa aritmética de punto flotante en un computador diferente que en la matemá́tica continua. En las matemá́ticas, todos los números pueden ser considerados exactos. Como se muestra en la sección anterior, en un computador muchos números no se pueden representar exactamente con un número finito de bits. Todos los cá́lculos se realizan con una precisión limitada. En los ejemplos de esta sección, se usará́n números con una mantisa de 8 bits por simplicidad.

Para sumar dos números de punto flotante, los exponentes deben ser iguales. Si ellos, no son iguales, entonces ellos se deben hacer iguales, desplazando la mantisa del número con el exponente má́s pequeño Por ejemplo, considere 10,375 + 6,34375 = 16,71875 o en binario:

1,0100110 × 23

+ 1,1001011 × 22

Estos dos números no tienen el mismo exponente así que se desplaza la mantisa para hacer iguales los exponentes y entonces sumar:

1,0100110 × 23

+ 0,1100110 × 23

10,0001100 × 23

Page 133: ensamblador

Observe que el desplazamiento de 1,1001011 × 22 pierde el uno delantero y luego de redondear el resultado se convierte en 0,1100110 × 23 . El resultado de la suma, 10,0001100×23 (o 1,00001100×24 ) es igual a 10000,1102 o 16.75. Esto no es igual a la respuesta exacta (16.71875) Es só� lo una aproximación debido al error del redondeo del proceso de la suma.

Es importante tener en cuenta que la aritmética de punto flotante en un computador (o calculadora) es siempre una aproximación. Las leyes de las matemá́ticas no siempre funcionan con números de punto flotante en un computador. Las matemá́ticas asumen una precisión infinita que un computador no puede alcanzar. Por ejemplo, las matemá́ticas enseñan que (a+b)−b = a; sin embargo, esto puede ser exactamente cierto en un computador.

La resta trabaja muy similar y tiene los mismos problemas que la suma. Como un ejemplo considere 16,75 − 15,9375 = 0,8125:

1,0000110 × 24

− 1,1111111 × 23

Desplazando 1,1111111 × 23 da (redondeando arriba) 1,0000000 × 24

1,0000110 × 24

− 1,0000000 × 24

0,0000110 × 24

0,0000110 × 24 = 0,112 = 0,75 que no es exacto.

Page 134: ensamblador

Los primeros procesadores Intel no tenían soporte de hardware para las operaciones de punto flotante. Esto no significa que ellos no podían efectuar operaciones de punto flotante. Esto sólo significa que ellas se realizaban por procedimientos compuestos de muchas instrucciones que no son de punto flotante. Para estos primeros sistemas, Intel suministraba un circuito integrado adicional llamado coprocesador matemá́tico. Un coprocesador matemá́tico tiene instrucciones de má́quina que realizan instrucciones de punto flotante mucho má́s rá́pido que usando procedimientos de software (en los primeros procesadores se realizaban al menos 10 veces má́s rá́pido). El coprocesador para el 8086/8088 fue llamado 8087. Para el 80286 era el 80287 y para el 80386 un 80387. El procesador 80486DX integró el coprocesador matemá́tico en el 80486 en sí mismo 5 . Desde el Pentium, todas las generaciones del 80X86 tienen un coprocesador matemá́tico empotrado; sin embargo todavía se programa como si fuera una unidad separada. Aún los primeros sistemas sin un coprocesador pueden instalar un software que emula el coprocesador matemá́tico. Estos emuladores se activan automá́ticamente cuando un programa ejecuta una instrucción del coprocesador y corre un procedimiento que produce los mismos resultados que el coprocesador (mucho má́s lento claro está́).

Page 135: ensamblador

section .datavalor: dd 0 ;argc suma: dd 0 ; sumatoria de los enteros formato: db '%.3f', 10, 0 ;Formato de impresión de resultadoerror: db 'No se introducieron valores', 10, 0 ;mal forma de uso

global mainextern printfextern atoi

section .textmain: mov ecx, [esp+4] ; dec ecx ; Checar que se hayan introducido enteros. jz nada mov [valor], ecx ; guardar el numero introducido argc o argumentos de enteros. mov edx, [esp+8] ; agregar: push ecx ; insertar dato en la pila push edx ; insertar dato en la pila push dword [edx+ecx*4] ; argv[ecx] call atoi ; convertir el arg de entrada a entero add esp, 4 ; aumentar el apuntador de la pila pop edx ; sacar registros usados de los registros pop ecx add [suma], eax ; guardar dato en sumatoria dec ecx jnz agregar ; si hay mas argumentos continua el ciclo.

Page 136: ensamblador

average: fild dword [suma] ; cargar en st0 la sumatoria de enteros en la pila fild dword [valor] ; cargar en st0 numero de enteros (argc) en la pila fdivp st1, st0 ; promedio = (suma / valor) st1 / st0 sub esp, 8 ; mover el apuntador para posicinarlo en el promedio (resultado) fstp qword [esp] ; almacena el promedio y saca de la pila push formato ; alistar de impresion call printf ; llamar a la libreria de C para imprimir add esp, 12 ; avanzar el apuntador para sacar los resultados ret

nada: push error ; mensaje de error call printf add esp, 4 ret

Page 137: ensamblador

Para la multiplicación, las mantisas son multiplicadas y los exponentes son sumados. Considere 10,375 × 2,5 = 25,9375:

1,0100110 × 23

× 1,0100000 × 21

10100110 + 10100110 1,10011111000000 × 24

Claro está́, el resultado real podría ser redondeado a 8 bits para dar:

La división es má́s complicada, pero tiene problemas similares con errores de redondeo.

El principal punto de esta sección es que los cá́lculos de punto flotante no son exactos. El programador necesita tener cuidado con esto. Un error común que el programador hace con números de punto flotante es compararlos asumiendo que el cá́lculo es exacto. Por ejemplo considere una función llamada f (x) que hace un cá́lculo complejo y un programa está́ tratando de encontrar las raíces de la función. 4 . Uno podría intentar usar la siguiente instrucción para mirar si x es una raíz:

if ( f (x) == 0.0 )