LENGUAJE MÁQUINA

Cada 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.

LENGUAJE ENSAMBLADOR
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)

PROGRAMACIÓN EN ENSAMBLADOR USANDO LA SINTAXIS GAS EN ARQUITECTURAS x86
Las instrucciones que hacen uso de la sintaxis GAS tiene la forma: Mnemonico Por ejemplo: movb $0x05, %al La 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 movl movl leal leal -4(%ebp, %edx, 4), %eax -4(%ebp), %eax (%ecx), %edx 8(,%eax,4), %eax (%eax,%eax,2), %eax # ejemplo completo: load *(ebp - 4 + (edx * 4)) into eax # ejemplo típico: carga una variable del stack en eax # sin offset: copiar la salida de un apuntador a un registro # Aritmética: multiplicar eax por 4 y sumarle 8 # Aritmética: multiplicar eax por 2 and sumarle eax (esto es multiplicar por 3)

dato_fuente, dato_destino

GENERANDO CÓDIGO ENSAMBLADOR A PARTIR 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.c Lo 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

. Lo interesante de este programa está en visualizar el contenido del archivo en ensamblador obtenido después de la compilación correspondiente.Posterior a ello se tendrá que realizar la ejecución del programa correspondiente.

#secciones 8. _start: 15.global _start 14. xorl %ebx. #Otro tipo de comentario 5.text 13. %ebx 19. xorl %eax. #FINALIZA PROGRAMA 16.s" 7.end . */ 4. #nombre de archivo (opcional) 6.datos 10. . #PROGRAMA 12.PROGRAMAS EN ENSAMBLADOR UTILIZANDO DDD COMO DEPURADOR. .section .section .data 9. incl %eax 18.datos 11. /* 2. int $0x80 20 . %eax 17.file "Ejemplo1. # <. . . # <. Este es un tipo de comentario 3. ESTRUCTURA BÁSICA DE UN PROGRAMA 1.

They conventionally recognize _start as their # entry point. %ebx movl $4.msg # length of our dear str .%edx movl$msg.%ebx movl$4.%ecx movl$1.ascii "hola mundo!\n" len = 12 # write our string to stdout movl$len.%ebx movl$1.%eax int $0x80 # and exit movl$0. %eax int $0x80 movl $0. %edx movl $msg.global _start _start: movl $len.text . Use ld -e foo to override the default. _start: .ascii "Hello. %eax int $0x80 ..data # first argument: exit code # system call number (sys_exit) # call kernel # section declaration # third argument: message length # second argument: pointer to message to write # first argument: file handle (stdout) # system call number (sys_write) # call kernel msg: . world!\n" # our dear string len = .text # section declaration # we must export the entry point to the ELF linker or .global _start # loader. .data msg: . %ecx movl $1.%eax int $0x80 . %ebx movl $1.

ESTRUCTURA EN PSEUDOCÓDIGO Por más trivial que parezca el problema. si tenemos en cuenta que Assembler es un lenguaje donde las operaciones son predominantemente binarias. reducir la labor de programación a una simple traducción de un lenguaje al cual ya estamos familiarizados. siempre resulta útil hacer el esquema de ejecución a modo de pseudocódigo. Ahora bien. valdrá la pena convertir nuestro algoritmo a uno que sólo requiera operaciones de éste tipo. . Como primer intento podríamos hablar de un pseudocódigo de alto nivel de este tipo. De esta forma garantizamos. una buena lógica de programa. y en segundo. en primer lugar.

EDX Registros de 16 bits Corresponden a la mitad derecha de los registros de 32 bits. El Procesador Intel posee espacios para almacenar variables. 32 bits. ECX.VARIABLES EN LENGUAJE ENSAMBLADOR Algo importante en la programación en ensamblador es tener claras las variables que se utilizan. Las ventajas que nos ofrece la utilización de registros es la velocidad de acceso. DL. DX Registros de 8 bits Corresponden a cada una de las mitades de los registros de 16 (H para High. CX. EBX. estos espacios se denominan Registros. 2. BH. L para Low) y son: AL. CH. Para esto. se debe tener en cuenta lo siguiente: 1. 8 bits o un espacio de memoria de N Bytes) Según nuestro algoritmo necesitaremos 6 espacios de memoria. BX. 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. 16 bits. BL. Cinco para los números que necesitamos sumar y un espacio para el acumulador. CL. Debe ser fácil de recordar ya que el orden es muy importante en la programación en ensamblador. AH. Tamaño de la variable: Definir de que tamaño es la variable (64 bits. Nombre de la Variable: Toda variable debe tener nombre. DH . y son: AX.

el nombre con el cual haremos referencia al valor guardado. Los valores iniciales equivalen a los números que tendrá el programa.word para 16 bits y .section .long -100 .long 0x64 Numero4: . . 100. es decir.Conociendo esto. 100. 100. 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. en hexadecimal (inicializando la cadena con “0x”) o en octal inicializándola en “0”. Por defecto se leen en decimal pero pueden escribirse en binario (inicializando la cadena con “0b”). alojaremos nuestras variables de la siguiente forma. . El tamaño se define como .long 100 Numero2: . -100).long 0144 Numero3: .data Numero1: .long 0b1100100 Numero5: . Ya con esto podemos escribir nuestra sección .data para alojar las variables a las que hallaremos la sumatoria (alojaremos 100.long para 32 bits.byte para 8 bits.

%eax addl Numero2.end .long 0b1100100 Numero5: .%eax addl Numero4.long 0144 Numero3: .%eax addl Numero5.global _start . %eax incl %eax xorl %ebx.section .data Numero1: .s" .%eax addl Numero3.long 0x64 Numero4: .PROGRAMA EN ENSAMBLADOR DE EJEMPLO .text _start: clrl %eax addl Numero1.long -100 .long 100 Numero2: .%eax xorl %eax.file "Sumatoria. %ebx int $0x80 .

FORMA DE COMPILACIÓN DEL PROGRAMA .

el destino se coloca a la derecha y el fuente a la izquierda (en Intel es al revés). etc). eax En AT&T. 0f02h . %ebx INTEL: mov ebx. Las siguientes instrucciones cargan en ebx el valor de eax AT&T: movl %eax. Las principales diferencias se detallan a continuación: En AT&T. TASM. offset var mov ebx. la primera instrucción carga la dirección de la variable en eax. que tiene pequeñas diferencias con respecto a la sintaxis estándar de Intel (usada en NASM.GAS (Gnu ASsembler) utiliza la sintaxis de AT&T. %eax movl $0xf02. MASM. %ebx INTEL: mov eax. a los valores inmediatos se les añade el prefijo $ en el siguiente ejemplo. la segunda carga el valor 0F02h en ebx AT&T: movl $var.

. el tamaño del resultado se especifica con sufijos (b.%eax (%ebx). Si lo omitimos. %ah movw %bx. byte ptr var mov ax. y es algo que no queremos que haga. bx AT&T: %bl. AT&T: movb var.%al %bx.. GAS intentará “adivinar” el tamaño.%eax INTEL: mov al.bl mov ax. índice .ebx mov eax. bx mov eax. %ax movb movw movl movl INTEL: mov ah. dword ptr [ebx] Direccionamiento a memoria: Es uno de los aspectos que más cambian. escala ) . w o l) en las instrucciones (en Intel cuando hay ambigüedad se utiliza byte ptr. con índice y desplazamiento: [ base + índice*escala + desplazamiento ] en la sintaxis AT&T esto queda como sigue: desplazamiento ( base . %ax %ebx. Veamos la sintaxis de Intel para hacer un direccionamiento a base. word ptr o dword ptr).En AT&T.

%eax Salto lejano AT&T: lcall $sección. $offset lret $V INTEL: mov eax . %eax movl 3(%ebx) . %ecx movsx ecx. array[eax*4] AT&T: movl (%ebx) . 4). Varían los nemotécnicos de algunas instrucciones AT&T: AT&T INTEL movswl %ax.[ebx+3] INTEL: call far sección:offset jmp far sección:offset ret far V Nemotécnico. %eax. ah cbtw cbw cwtl cwde cwtd cwd cltd cdq . %cx movzx cx. $offset ljmp $sección.Veamos dos ejemplos: AT&T: movl array (. [ebx] mov eax. %edx INTEL: mov edx. ax movzbw %ah.

hay diferencias en cuanto a algunas directivas al programar con el ensamblador GAS o NASM.data . Como comentamos más arriba. definir los tipos de datos.text). Como vimos. los programas ensamblador. macros. En ambos ensambladores hay que definir las secciones de datos y código utilizando los mismos nombres (.bss . la directiva utilizada para definir las secciones difiere de un ensamblador a otro: . contienen órdenes al compilador que le servirán para definir las secciones del programa. además de las instrucciones que componen el programa. etc. Sin embargo.Directivas del compilador.

la etiqueta de entrada al programa ensamblador suele ser _start. Sin embargo. pero utilizando palabras reservadas diferentes: .En ambos ensambladores. la directiva utilizada difiere de un ensamblador a otro: La definición de datos constantes se lleva a cabo utilizando de la misma forma.

endm En el ejemplo definiremos una macro para terminar el programa.macro de la siguiente forma: . Para ello. debemos utilizar la directiva .USO DE MACROS EN GAS Podemos hacer uso de macros para facilitar la programación. y otra para leer cadenas de texto desde entrada estándar.s ls -o m m.o m. 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 . otra para mostrar una cadena por salida estándar.macro nombreMacro instrucciones .

EDX=longitud .mensaje1 buffer: .%ebx #stdout movl \cadena.macro escribir_cadena cadena longitud movl $4. EDX=longitud .%ebx #stdin movl \cadena.ascii " " .%ecx movl \longitud.endm #espera ECX=cadena .%eax movl $1.ascii "\n Introduce una cadena: " longitud1 = .%edx int $0x80 .text ..macro terminar movl $1.macro leer_cadena cadena longitud movl $3.%ebx int $0x80 .globl _start _start: escribir_cadena $mensaje1 $longitud1 leer_cadena $buffer $10 escribir_cadena $retorno $1 escribir_cadena $buffer $10 escribir_cadena $retorno $1 terminar .%edx int $0x80 .%ecx movl \longitud.byte 0x0A mensaje1: . .data retorno: .%eax movl $0.%eax movl $0.endm #espera ECX=cadena .

Dentro de la sección . 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.text (y antes del punto de entrada al programa) podemos definir las diferentes funciones (subrutinas). Como se verá en el programa principal. Una función tendrá el siguiente aspecto: nombreFuncion: instrucciones ret Veamos el ejemplo anterior (el de las macros) utilizando tres subrutinas. 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): .Podemos hacer uso de funciones para facilitar la programación.

%ecx movl $1..%eax movl $0. EDX=longitud funcion_escribir_cadena: movl $4.%ecx movl $1.%ebx int $0x80 ret #parámetros ECX=cadena .mensaje1 buffer: .%ecx movl $longitud1.%edx call funcion_escribir_cadena #esta última no necesita ningún parámetro call funcion_terminar .byte 0x0A mensaje1: .text . .%eax movl $0.%ecx movl $10.%edx call funcion_escribir_cadena movl $retorno.%ecx movl $10.%ebx #stdin int $0x80 ret _start: #los parámetros se pasan en los registros movl $mensaje1.data retorno: .%ebx #stdout int $0x80 ret #parámetros ECX=cadena .%edx call funcion_escribir_cadena movl $buffer.%eax movl $1. EDX=longitud funcion_leer_cadena: movl $3.ascii "\n Introduce una cadena: " longitud1 = .%edx call funcion_leer_cadena movl $retorno.%edx call funcion_escribir_cadena movl $buffer.globl _start funcion_terminar: movl $1.ascii " " .

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. Ellas se usan generalmente para decirle al ensamblador que haga alguna cosa o informarle al ensamblador de algo. .NASM Una directiva es un artificio del ensamblador no de la CPU. Ellas no se traducen en código de máquina. Tiene muchas de las órdenes del preprocesador tal como C. El formato es: símbolo equ valor Los valores de los símbolos no se pueden redefinir posteriormente. Sin embargo las directivas del preprocesador de NASM comienzan con un como en C. 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.

Los macros son más flexibles que los símbolos de dos maneras.La directiva %define Esta directiva es parecida a la #define de C. SIZE El código de arriba define un macro llamado size y muestra su uso en una instrucción MOV. . %define SIZE 100 mov eax. Se usa normalmente para definir macros tal como en C. Los macros se pueden redefinir y pueden ser más que simples constantes numéricas.

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

Las comillas dobles o simples se interpretan igual. Esto es. "o". 2. "r". 3 "w". 0 ’word’. define una cadena tipo C = "word" . Se pueden definir también secuencias de memoria. Las definiciones consecutivas de datos se almacenan secuencialmente en memoria. 0 . L9 L10 L11 db db db 0. la palabra L2 se almacena inmediatamente después que la L1. ’d’. 1. define 4 bytes . igual que L10 .

%ecx movl $1.se movl $1.%edx #es un solo caracter int $0x80 ret .%al testb %al. Tras cada argumento impreso. El programa muestra también el nombre del ejecutable como primer argumento (sería casi inmediato hacer que sólo muestre los argumentos reales). se hace un retorno de carro (es una cadena de caracteres de longitud 1).%ebx int $0x80 movl $4.%eax #mostramos el RETORNO_CARRO movl $retorno.Veamos un ejemplo de lectura de los argumentos de línea de comando. 1). %eax movl $0.%eax #comprobar si es NULL jz terminar call funcion_pintar_cadena #llamada a la funcion jmp repetir terminar: movl $1.%ecx #el parametro ha sido pasado en EAX xorl %edx.globl _start _start: pop %eax #extraer de la pila el ARGC repetir: #bucle para recorrer todos los argumentos pop %eax #extraer el ARGV[i] testl %eax. En este ejemplo se hace uso de todo lo descrito en “Acceso a la pila en un programa ensamblador en Linux”: . contando uno por uno cada carácter que la forma.%eax #una vez calculada la longitud.%edx contar: movb (%ecx. %edx. programado para el ensamblador GAS. %ebx int $0x80 #funcion del sistema para terminar 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).data retorno: .text .%al #comprobar el caracter de la cadena jz fin_contar incl %edx #vamos incrementando el calculo en EDX jmp contar fin_contar: movl $4.byte 0x0A funcion_pintar_cadena: #definicion de una funcion movl %eax. .

section . de la cadena mov ecx.data mensaje db 0xA.extraer "argc" .bss buffer: resb1024 section .longitud .text global _start .1 .4 . 80 (llamada al kernel) pop ebx pop ebx ."---hemos terminado---".0xA longitud2 equ $ .extraer argv[0] (nombre del ejecutable) .mensaje2 tamano equ 1024 section ."---vamos a probar esto---".0xA longitud equ $ .EDX=long. la sintaxis utilizada ha sido la de Intel (NASM).mensaje .definimos el punto de entrada _start: mov edx.EBX=manejador de fichero (STDOUT) mov eax. En este ejemplo.mensaje mensaje2 db 0xA.ECX=cadena a imprimir mov ebx.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.EAX=función sys_write() del kernel int 0x80 .interrupc.

error o el descriptor jns leer_del_fichero hubo_error: mov ebx.h) int 0x80 .buffer .tamano .4 .terminar.3 .extraer el primer arg real (puntero a cadena) mov eax.no hay error=>devuelve descriptor push ebx mov eax.función para sys_read() mov ecx.O_RDONLY (definido en fcntl.función sys_write() mov ebx.interrupc.variable donde guardamos lo leido mov edx.comprobar si dev.0 . devolviendo el código de error mov eax.eax .eax .tamaño de lectura int 0x80 js hubo_error mostrar_por_pantalla: mov edx.función para sys_open() mov ecx.eax .longitud de la cadena a escribir mov eax.eax .pop ebx .descriptor de STDOUT int 0x80 .1 int 0x80 leer_del_fichero: mov ebx.1 . 80 (llamada al kernel) test eax.5 .

EAX=función sys_write() del kernel int 0x80 .EAX=función sys_exit() del kernel int 0x80 .EBX=manejador de fichero (STDOUT) mov eax.1 .longitud2 .4 . 80 (llamada al kernel) mov ebx.EBX=código de salida al SO mov eax. 80 (llamada al kernel) $nasm -f elf acceso_a_fich.asm $ ld -s -o acceso_a_fich acceso_a_fich.mensaje2 .interrupc.0 .interrupc.función para cerrar un fichero int 0x80 mov edx.o .EDX=long.ECX=cadena a imprimir mov ebx.cerrar_fichero: pop ebx mov eax.1 .6 . de la cadena mov ecx.

h> void func_con_parametros(int x. debe insertarlos en la pila en el orden inverso (primero el último. la etiqueta “main” (como en C/C++). nos vemos obligados a definir como punto de entrada del programa ensamblador. Ambas. SP): .).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. Luego. void func_con_parametros(int x. void func_sin_parametros(). El código fuente podría ser el siguiente: #include <stdio. una que recibe dos parámetros. antes de llamar a ninguna función. nuestro programa ensamblador (sintaxis GAS) debe inicializar la pila. hacer la llamada a la función. deshacer las inserciones hechas antes de la llamada (ajustar el valor del registro puntero de pila. etc. x . para pasar los parámetros (si son necesarios). sólo van a mostrar por salida estándar un mensaje y los parámetros recibidos. } void func_sin_parametros() { printf("Llamada SIN parametros \n"). } En este caso.h> #include <stdlib. int y) { printf("Llamada con dos parametros x=%d y=%d \n". y otra que no recibe ninguno. Ya dentro del programa. El módulo en C/C++ de este ejemplo tendrá dos funciones. para este ejemplo. en lugar de “_start”. int y). y ). y por último.

. %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.globl main main: # inicializar la pila pushl %ebp movl %esp.text . %ebx movl $mensaje. %ecx movl $mensaje_SIZE.25) addl $8.mensaje . enteros) # mostrar texto directamente con la INT 80h movl $4.data mensaje: . %edx int $0x80 . %eax movl $1. . %esp #quitar de la pila 8 bytes #(dos num.ascii " --mensaje desde ASM directamente--\n" mensaje_SIZE = .

exe funciones. %eax movl $0.c progr.# 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. %ebx int $0x80 $gcc -Wall -O2 -o progr.s .

que luego usará gdb.s la opción –a nos mostrará un listado de memoria durante el proceso de ensamblaje.Depuración de código usando gdb En Linux podemos hacer uso del gdb para depurar el código que hemos escrito (trazar paso a paso. Para ello. donde podremos ver la localización de las variables y código respecto al principio de los segmentos de código y datos. comprobar el valor de ciertos registros en cada momento. La opción ––gstabs introduce información de depuración en el fichero binario.o prog. debemos ensamblar nuestros programas con una opción especial del as : as –a –-gstabs –o prog. Primer paso de la depuración: llamar a gdb indicándole el ejecutable a depurar . etc).

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

Antes de ejecutar el programa debemos establecer dos puntos de ruptura (break): uno correspondiente a la etiqueta de comienzo del programa (_start) y otro en la línea siguiente (en el primero no para.). ya podemos ejecutar el programa (run): Podemos ir viendo los valores de los registros mediante info registers o bien con p/x . Una vez hecho esto. pero es necesario ponerlo. Nosotros debemos poner otro punto en la línea cuyo número es el siguiente al que nos acaba de indicar. Vemos que al poner el primer punto. nos indica un número de línea...

.

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

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

%ebx # %ebx is now 204 movzbl byteval.byte 204 . %ax # %eax is now 204 movzwl %ax.INSTRUCCIONES (move y extend) movz y movzx .data byteval: . %eax xorl %ebx. %esi # %esi is now 204 # Linux sys_exit mov $1.global _start _start: movzbw byteval.text . %ebx int $0x80 .

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

%ebx int $0x80 . %eax incl %eax xorl %ebx.%ecx.long 4 .file "MayorenArreglo.text .s" .section .data Tam: .4) JLE else movl Arreglo(. %ecx loop1: cmpl %eax.10 prueba: .long 5 Arreglo: .4.end .global _start _start: movl Tam.8. %eax jmp fin else: #En este caso no hay Else fin: loop loop1 xorl %eax.4).. Arreglo(.2.%ecx.int 0.

.

CONTROL 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 %ECX
2. 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, %ecx 2. bucle: 3. ##SECUENCIA 4. loop bucle

LA SENTENCIA if-then-else
CMP: Sirve para comparar. Internamente es una resta entre los 2 operandos que se comparan JE: 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,B 2. <Salto> else 3. #SECUENCIA DE PASOS SI SE CUMPLE LA CONDICIÓN 4. jmp fin 5. else: 6. #SECUENCIA DE PASOS SI NO SE CUMPLE LA CONDICIÓN 7. fin:

LA SENTENCIA WHILE

1. while: 2. cmp a,b 3. <Salto> salir 4. #SECUENCIA DE PASOS DENTRO DEL WHILE 5. jmp while 6. salir: Algoritmo de un número Mayor en Arreglo

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 adecuado Guardar la dirección de retorno Invocar el Llamado El Llamado.FUNCIONES Y MODULARIDAD A medida que un programa aumenta de complejidad. podemos agruparlos en funciones. A estos fragmentos. Localizar (si los hay) los parámetros. se detectan ciertas porciones de código que realizan lo mismo basados en 0 o mas parámetros. Independiente de la arquitectura utilizada. . es la parte que se encarga de:      Garantizar que todo quede como estaba. Definir (si las hay) las variables locales. Recuperar la dirección de retorno Retornar el control al llamador.

xorl %ebx. %eax 27. xorl %eax.long 5 5. addl %ebx.Una Aproximación al Llamador y el Llamado desde asm 1. %eax 13.section .text 7. %ebp 11. # Finalizo el programa 26. pushl %ebp 10. pushl b 22. %ebx 12. . . call sumar 23.section . popl %ebx 24. . movl 8(%ebp).global sumar 8. .global _start 18. popl %ebx 25. _start: 19. 3. %eax 14. movl %esp. int $0x80 30. b: .long 4 4. movl 12(%ebp). a: . incl %eax 28. #leave 15. end . sumar: 9. ret 17.data 2. # "Main" 20. %ebx 29. pushl a 21. 6.

.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.

conocido luego popularmente como 386 o x86 para denominar a toda la gama. Pentium II. Son los microprocesadores más usados en los ordenadores personales (PC). paginación. Pentium. La novedad de estos procesadores con respecto a sus predecesores es que incluyen gestión de memoria avanzada (segmentación. Los procesadores de Intel que siguieron y mantuvieron la compatibilidad son el 486. soporte de memoria virtual). muy usadas en aplicaciones gráficas y multimedia. y a partir del Pentium MMX. unidad de punto flotante. y la línea Intel Core.Programación en Lenguaje Ensamblador Plataforma: IA-32 IA-32 es la arquitectura de microprocesadores de 32 bits de Intel (Intel Architecture 32). Pentium 4. . Pentium III. Esta gama de microprocesadores comenzó con el Intel 80386 en 1985. soporte para operaciones matriciales complejas.

Se puede leer un sólo byte (8 bits) o un conjunto de bytes. .Plataforma: IA-32 Tipos de datos La información se puede accesar de diversas maneras. 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.

Plataforma: IA-32 Tamaños de los datos: .

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.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.

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. algunos tienen cierta funcionalidad específica o son usados de manera especial por algunas instrucciones. . Aún cuando estos registros pueden mantener cualquier tipo de datos.

Plataforma: IA-32 Registros de propósito general .

16 o 32 bits cambiando su nomenclatura de acuerdo al tamaño. %ecx y %edx pueden ser accesados con tamaños de 8.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. Ejemplo para %eax: .

Ejemplo para %edi: .Plataforma: IA-32 Registros de propósito general Los registros %edi. %ebp y %esp se pueden accesar como registros de 16 o 32 bits. %esi.

%st(0) se ubica en el tope de la pila. Se nombran %st(0). etc. 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.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. . %st(1). Registros de punto flotante: Son 8 registros los cuales son tratados como una pila. %st(2).

Plataforma: IA-32 Banderas: Estas son las banderas más comunes: .

en un registro. • La bandera de paridad se usa para indicar si el resultado. de una operación matemática es válido. • La bandera de signo muestra el bit más significativo del resultado de una operación. • La bandera de ajuste se utiliza en operaciones matemáticas con números decimales codificados en binario (BCD). el cual denota el signo del número.Plataforma: IA-32 Banderas: • La bandera de acarreo se activa cuando se produce acarreo en una operación matemática entre números sin signo. . • La bandera de cero se activa si el resultado de una operación es cero. Se activa si hay acarreo presente.

.Plataforma: IA-32 Banderas: • 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.

Conceptos básicos de los programas en lenguaje ensamblador: Espacio: Un espacio es equivalente a cualquier número de espacios o tabuladores.  Comentario: Texto que aparece después de un carácter de inicio de comentario.Programación en ensamblador Un programa en lenguaje ensamblador está orientado a líneas y cada enunciado especifica una operación sencilla. Los espacios no pueden aparecer en medio de un número o identificador. el ensamblador ignora los comentarios. Los comentarios comienzan con el símbolo #.  .

 Debe haber un espacio entre el nombre de la operación y la lista de operandos. .Programación en ensamblador Conceptos 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). el número de operandos en la lista depende de la operación. Los operandos se separan por comas.   Etiqueta: identificador seguido de dos puntos. Instrucción: es una operación seguida de una lista de operandos.

es decir.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. . las directrices dirigen el proceso de traducción.  Con las directrices se especifica la forma en que el ensamblador traduce las instrucciones. Los nombres de la directrices comienzan con un punto.

En la sección de texto se escriben las instrucciones. Cada una de las secciones se declara por medio de una directiva. Las secciones más comunes son: • sección de texto • sección de datos • sección bss. en la sección de datos los datos inicializados y en la sección bss las variables sin inicializar.Programación en ensamblador El programa escrito en lenguaje ensamblador se compone de varias secciones. .

data para la sección de datos .text para la sección de texto .section .section . .data .Programación en ensamblador Para declarar las secciones mencionadas se usan las siguientes directivas: .text bss son las siglas correspondientes a "block storage start".section .bss .section .bss para la sección bss Comúnmente las secciones se colocan en la siguiente secuencia: .section .section . que significa inicio de bloque de almacenaje.

. es decir. esto se logra utilizando la directiva .globl.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. que esté disponible para aplicaciones externas. Esta etiqueta debe ser declarada como global.

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). . esto se logra mediante una llamada al sistema.Programación en ensamblador Finalización del programa: Gas no provee una instrucción de fin de ejecución. El valor 0 en el registro %ebx indica la salida normal del programa.

Programación en ensamblador Estructura general En general la estructura de un programa en lenguaje ensamblador tiene la siguiente forma: .

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

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. .

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.

el sistema las guarda en forma consecutiva en memoria.Programación en ensamblador Cuando se definen las variables. valor Ejemplo: definición de constantes .data se utiliza principalmente para definir variables también puede ser usada para definir constantes.equ. el formato para esta directiva es: directiva símbolo. 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. Esto se hace usando la directiva . Aún cuando la sección .

lcomm se usa para datos locales. que no serán usados fuera del código local. El formato.Programación en ensamblador Para definir datos en la sección bss se usan dos directivas: La directiva . para ambas directivas es el siguiente: directiva símbolo. tamaño en bytes Ejemplo : declaración de un área de memoria sin inicializar Se declara una variable llamada area de 100 bytes de tamaño. .

.data.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 .

tener un sólo operando o dos operandos. Las instrucciones pueden no tener operandos. En general las instrucciones tienen la forma: instrucción operando fuente.Programación en ensamblador Instrucciones Las instrucciones en gas tienen un sufijo que indica el tamaño del dato sobre el cual actúa la instrucción. dependiendo de la instrucción en particular. operando destino .

• Referencia a memoria: denota el contenido de una posición de memoria. • Registro: denota el contenido de uno de los registros.Programación en ensamblador Los operandos se pueden clasificar en tres tipos: • Inmediato: para valores constantes. .

2.Programación en ensamblador Hay varias maneras de obtener la información las cuales se pueden resumir en la siguiente tabla: inm denota un inmediato reg denota un registro. 4 ó 8 R[reg] significa el contenido del registro reg M[x] significa el contenido de la posición de memoria con dirección x . regb un registro base y regi un registro índice e es la escala la cual puede ser 1.

Programación en ensamblador Ejemplo: valores para cada modo de direccionamiento Asumiendo los contenidos de: %eax= 0x100 y %ecx= 0x10 .

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 .

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 movw movl mueve un byte mueve una palabra (2 bytes) mueve dos palabras (4bytes)

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

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 movw movl mueve un byte mueve una palabra (2 bytes) mueve dos palabras (4bytes)

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

Programación en ensamblador
Movimiento de datos inmediatos a registro o a memoria Los 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:

Programación en ensamblador
Movimiento de datos entre registros Esta 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:

Programación en ensamblador
Movimiento 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.

Programación en ensamblador Movimiento de datos entre memoria y registros Para mover la información de memoria a un registro se escribe: .

. La forma general de accesar un arreglo se puede expresar como: dirección inicial (desplazamiento. índice. El tamaño de los datos representa el desplazamiento entre dos posiciones del arreglo. Hay que tomar en cuenta la dirección inicial del arreglo y utilizar un índice para ir recorriendo cada uno de sus elementos. 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.Programación en ensamblador Para leer o escribir en arreglos se utiliza direccionamiento indexado.

Programación en ensamblador Ejemplo de declaración y lectura de un arreglo de enteros: .

.Programación en ensamblador Movimiento de datos con extensión Hay dos instrucciones adicionales que permiten mover datos extendiéndolos.

Programación en ensamblador Movimiento de datos con extensión Hay 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. Ejemplo de movsbl: Ejemplo de movzbl: . La instrucción movzbl hace un procedimiento similar pero extiende con ceros.

Las instrucciones para el manejo de la pila son dos.Programación en ensamblador Uso de la pila La pila es un área de memoria que crece desde una dirección inicial hacia direcciones menores. . 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. El último elemento colocado en la pila es el que está disponible para ser retirado.

Programación en ensamblador Uso de la pila Ejemplo 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: .

Programación en 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.

Ejemplo: Dados los siguientes valores en los registros: %eax=0x100 %ebx=0x10 .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.

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

Programación en ensamblador Instrucciones aritméticas y lógicas .

Programación en ensamblador Operaciones aritméticas especiales .

Programación en ensamblador Ejemplo de un programa que realiza algunas operaciones aritméticas: .

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. . bandera de Signo (SF) y bandera de desbordamiento (OF). Las instrucciones cmpl y testl actualizan las banderas sin afectar los operandos. Las banderas más comunes son las siguientes: Bandera de Acarreo (CF). La instrucción leal no afecta las banderas. También existen las versiones con los sufijos b y w. Por lo tanto la ejecución de las instrucciones aritméticas y lógicas afectan las banderas. bandera de Cero (ZF).

Programación en ensamblador Control de flujo Cuando se usa cmpl se compara el operando 1 con el operando 2 Ejemplo: testl generalmente se usa repitiendo el operando para saber si es cero. positivo o negativo. Ejemplo: .

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

Programación en ensamblador Actualización de un registro con valor de una bandera Ejemplo: .

no secuencial. . La nueva dirección se representa con una etiqueta.Programación en ensamblador Las instrucciones de salto Una instrucción de salto produce un cambio en la ejecución del programa pasando a una nueva posició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. no chequea ningún código de condición.Programación en ensamblador Las instrucciones de salto La instrucción jmp salta de manera incondicional. 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. es decir. .

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 .

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. Ejemplo de salto condicional: . si la condición no se cumple continúa la ejecución de manera secuencial.

es decir valor1>=valor2.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 este caso. la resta en caso de cumplirse la condición o la suma en caso de que no se cumpla. 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. entonces continúa la ejecución en la instrucción siguiente. . la instrucción 5. Es importante destacar la necesidad de introducir la instrucción 6. En caso de no cumplirse la condición.

. Por ejemplo para realizar un extracto de programa que haga lo siguiente: if (x>y) result=x-y. else result=y-x.Programación en ensamblador Traducción de estructuras condicionales Para traducir expresiones de la forma: if (condición) instrucciones else instrucciones En lenguaje ensamblador se utilizan los saltos condicionales.

Programación en ensamblador .

Programación en ensamblador Ciclos En lenguaje ensamblador se implementan los ciclos usando comparaciones de condiciones y saltos.  Do while  While For  .

i++. Por ejemplo los primeros 7 elementos de la serie son: 1.1. Fn=Fn-1+Fn-2 para n>=3.13 int fib_dw(int n) { int i=0.5. return val. nval=t. int t=0. val=nval. F2=1.3. } while (i<n). La serie se define como F1=1. int val=0.2. int nval=1. } . do { t=val+nval.8.Do while Consideremos el siguiente programa en lenguaje C para encontrar el valor del elemento n en la serie de Fibonacci.

Do while Asumamos que el parámetro n está en el registro %esi. Usaremos los registros %ecx. %edx y %eax para representar las variables i. nval y t respectivamente. El resultado estará en la variable val. en este caso en %ebx . %ebx. val.

Do while .

Se puede reescribir el ejemplo anterior utilizando un while de la siguiente manera: int fib_w(int n) { int i=1. } . } return val. while (i<n) { t=val+nval. i++. val=nval. nval=t.While Cuando se utiliza while se comprueba la condición antes de entrar en el ciclo. int t=0. int val=1. int nval=1.

While De nuevo asumimos que n está en %esi y usamos los mismos registros anteriores: .

While .

} return val. i<n. i++) { t=val+nval. int t=0. val=nval. for(i=1. nval=t. El ejemplo anterior escrito con un for: int fib_f(int n) { int i. int nval=1.For En este caso también se revisa la condición antes de entrar al ciclo. } . int val=1.

For De nuevo usamos las mismas variables: Esta solución es casi idéntica a la del while. .

. Esto produce la asignación de un espacio de memoria de N*T. N-1}. Los elementos del arreglo se acceden con un índice cuyo rango es { 0. es decir el número de elementos del arreglo por el número de bytes del tipo de dato.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.

int B[8]. Sus parámetros serán los siguientes: . double C[6].Arreglos Ejemplo de declaración y parámetros de arreglos en C: Al declarar los arreglos: char A[12].

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. .

en este caso es 4 porque a está declarado como "long". .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.

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

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).Arreglos Cuando se declara un arreglo de dos dimensiones como por ejemplo el arreglo A declarado en C como: int A[4][3]. Para el ejemplo anterior el elemento A[2][2] está en la dirección XA+4(3*2+2)=XA+32. .

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

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

Sólo las 4 banderas usadas en comparaciones serán estudiadas. El uso de ellas . sin embargo todavía se programa como si fuera una unidad separada. C1 . C0 . Aún los primeros sistemas sin un coprocesador pueden instalar un software que emula el coprocesador matemático. Esto sólo significa que ellas se realizaban por procedimientos compuestos de muchas instrucciones que no son de punto flotante. 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). Esto no significa que ellos no podían efectuar operaciones de punto flotante. Cada registro almacena 80 bits.Los primeros procesadores Intel no tenían soporte de hardware para las operaciones de punto flotante. STO siempre se refiere al valoren el tope de la pila. El coprocesador para el 8086/8088 fue llamado 8087. Para estos primeros sistemas. todas las generaciones del 80X86 tienen un coprocesador matemático interno. El procesador 80486DX integrá el coprocesador matemático en el 80486 en sí mismo. . Hay también un registro de estado en el coprocesador numérico. Desde el Pentium. Los registros de punto flotante están organizados como una pila. El coprocesador numérico tiene ocho registros de punto flotante. . . C2 and C3 . Los registros se llaman STO. Intel suministraba un circuito integrado adicional llamado coprocesador matemático. Todos los números nuevos se añaden al tope de la pila. 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á. ST7. Para el 80286 era el 80287 y para el 80386 un 80387. Los números existentes se empujan en la pila para dejarle espacio al nuevo número. ST1. Recuerde que una pila es una lista LIFO (Last In Firist Out). Los números de punto flotante se almacenan en estos registros siempre como números de 80 bits de precisión extendida. Los registros de punto flotante se usan diferentes que los registros enteros en la CPU. Tiene varias banderas.

los dígitos a la derecha del punto decimal tienen asociados potencias de 10 negativas.LA UNIDAD DE PUNTO FLOTANTE En decimal. Conversión a binario del número decimal 0. No es sorprendente que los nú́ meros binarios trabajen parecido.85 .5625 Conversión a binario del número decimal 0.

A menudo es soportado por el hardware de la computadora en sí mismo. 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. El coprocesador matemático de Intel utiliza un tercer nivel de mayor precisión llamado precisión extendida. todos los datos en el coprocesador en sí mismos están en esta precisión. De hecho. 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. La precisión extendida usa un formato general ligeramente .sssssssssssx2eeeeeeeeeeeeeeeee Donde 1. Cuando se almacenan en la memoria desde el coprocesador se convierte a precisión simple o doble automáticamente. 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 IEEE define dos formatos con precisión diferentes: precisión simple y doble. Este formato se usa en la mayoría de (pero no en todas) las computadoras hechas hoy día. sssssssssssss es la mantisa y eeeeeeee es el exponente.REPRESENTACIÓN DE NÚMEROS CON EXPONENTES Un número de punto flotante a normalizado tiene la forma: 1.

Los números de punto flotante no usan la representación en complemento a 2 para los números negativos. La parte fraccionaria se asume que es normalizada (en la forma 1. Este exponente polarizado siempre es no negativo. 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. Los números de punto flotante son almacenados en una forma mucho más complicada que los enteros. así el bit de signo es 0. Hay varias peculiaridades del formato. El exponente binario no se almacena directamente. ¿Cómo se podría almacenar 23. el bit de signo y la fracción han sido subrayados y los bits se han agrupado en nibles): .85? Primero es positivo. Ya que el primer bit es siempre uno.sssssssss).PRECISIÓN SIMPLE IEEE El punto flotante de precisión simple usa 32 bits para codificar el número. El bit 31 determina el signo del número como se muestra. éste uno no se almacena. ahora el exponente verdadero es 4. se almacena la suma del exponente y 7F en los bits 23 al 30. Normalmente son exactos los primeros 7 dígitos decimales. Esta idea se conoce como la representación oculta del uno. Esto permite el almacenamiento de un bit adicional al final y así se incrementa un poco la precisión. La siguiente figura muestra la forma básica del formato de precisión simple del IEEE. así que el exponente es 7F + 4 = 8316 . Ellos usan la representación de magnitud y signo. En su lugar.

.

Como muestra la Figura 6. considere nuevamente 23. Como un ejemplo.La doble precisión IEEE usa 64 bits para representar números y normalmente son exactos los 15 primeros dígitos decimales significativos. El gran rango para el exponente tiene dos consecuencias. En el campo de la fracción el responsable del incremento en el número dígitos significativos para los valores dobles. Se usan más bits para el exponente (ll) y la fracción (52) que para la precisión simple. Las magnitudes de precisión doble van de 10−308 hasta 10308 aproximadamente. Si uno convierte esto a decimal uno encuentra 23.85. La primera es que se calcula como la suma del exponente real y 3FF (1023) (no 7F como para la precisión simple). 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 1010 O 40 37 D9 99 99 99 99 9A en hexadecimal. La precisión doble tiene los mismos valores especiales que la precisión simple. el formato básico es muy similar a la precisión simple. Segundo.85 otra vez.8500000000000014 (¡hay 12 ceros!) que es una aproximación mucho mejor de 23. . 3 Los números no normalizados son muy similares también.4. La principal diferencia es que los números dobles sin normalizados usan 2−1023 en lugar de 2−127 . se permite un gran rango de exponentes verdaderos (y así usa un gran rango de magnitudes).

en un computador muchos números no se pueden representar exactamente con un número finito de bits. considere 10. En las matemáticas.0100110 × 23 1. se usarán números con una mantisa de 8 bits por simplicidad. los exponentes deben ser iguales. En los ejemplos de esta sección.0001100 × 23 . desplazando la mantisa del número con el exponente más pequeño Por ejemplo. no son iguales. entonces ellos se deben hacer iguales.0100110 × 23 + 0.34375 = 16. Si ellos.ARITMÉTICA DE PUNTO FLOTANTE La aritmética de punto flotante en un computador diferente que en la matemática continua.1100110 × 23 10.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.375 + 6.71875 o en binario: 1. Como se muestra en la sección anterior. Para sumar dos números de punto flotante. todos los números pueden ser considerados exactos. Todos los cálculos se realizan con una precisión limitada.

Es importante tener en cuenta que la aritmética de punto flotante en un computador (o calculadora) es siempre una aproximación.75 que no es exacto. 10. Esto no es igual a la respuesta exacta (16.112 = 0. sin embargo. Las matemáticas asumen una precisión infinita que un computador no puede alcanzar.8125: 1. Por ejemplo.0000110 × 24 1.0000000 × 24 − 1.0000110 × 24 1.0000110 × 24 = 0. esto puede ser exactamente cierto en un computador.0001100×23 (o 1.1001011 × 22 pierde el uno delantero y luego de redondear el resultado se convierte en 0.1100110 × 23 .1111111 × 23 − Desplazando 1.75. La resta trabaja muy similar y tiene los mismos problemas que la suma. Las leyes de las matemáticas no siempre funcionan con números de punto flotante en un computador. El resultado de la suma.0000000 × 24 0. las matemáticas enseñan que (a+b)−b = a. Como un ejemplo considere 16.71875) Es só́ lo una aproximación debido al error del redondeo del proceso de la suma.9375 = 0. .1102 o 16.0000110 × 24 0.1111111 × 23 da (redondeando arriba) 1.75 − 15.Observe que el desplazamiento de 1.00001100×24 ) es igual a 10000.

Esto sólo significa que ellas se realizaban por procedimientos compuestos de muchas instrucciones que no son de punto flotante. 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). 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á). todas las generaciones del 80X86 tienen un coprocesador matemático empotrado. Desde el Pentium. Aún los primeros sistemas sin un coprocesador pueden instalar un software que emula el coprocesador matemático. Intel suministraba un circuito integrado adicional llamado coprocesador matemático. Esto no significa que ellos no podían efectuar operaciones de punto flotante. El procesador 80486DX integró el coprocesador matemático en el 80486 en sí mismo 5 . Para estos primeros sistemas.Los primeros procesadores Intel no tenían soporte de hardware para las operaciones de punto flotante. . El coprocesador para el 8086/8088 fue llamado 8087. sin embargo todavía se programa como si fuera una unidad separada. Para el 80286 era el 80287 y para el 80386 un 80387.

agregar: push ecx .3f'. ecx . si hay mas argumentos continua el ciclo. . argv[ecx] call atoi .section . insertar dato en la pila push dword [edx+ecx*4] .mal forma de uso global main extern printf extern atoi section . 0 . 10.text main: mov ecx. [esp+4] . mov edx. sumatoria de los enteros formato: db '%. 4 . dec ecx . 0 . eax . guardar dato en sumatoria dec ecx jnz agregar . Checar que se hayan introducido enteros.data valor: dd 0 .Formato de impresión de resultado error: db 'No se introducieron valores'. sacar registros usados de los registros pop ecx add [suma]. convertir el arg de entrada a entero add esp. jz nada mov [valor]. 10. guardar el numero introducido argc o argumentos de enteros. [esp+8] . aumentar el apuntador de la pila pop edx . insertar dato en la pila push edx .argc suma: dd 0 .

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

9375: 1. El programador necesita tener cuidado con esto. 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.Para la multiplicación.0100000 × 21 10100110 + 10100110 1. Considere 10. las mantisas son multiplicadas y los exponentes son sumados. pero tiene problemas similares con errores de redondeo. Uno podría intentar usar la siguiente instrucción para mirar si x es una raíz: if ( f (x) == 0.5 = 25.375 × 2.0100110 × 23 × 1. 4 . El principal punto de esta sección es que los cálculos de punto flotante no son exactos. Un error común que el programador hace con números de punto flotante es compararlos asumiendo que el cálculo es exacto.0 ) .10011111000000 × 24 Claro está. el resultado real podría ser redondeado a 8 bits para dar: La división es más complicada.