You are on page 1of 194

Lenguaje Ensamblador para PC

Paul A. Carter 18 de octubre de 2006

Copyright c 2001, 2002, 2003, 2004 by Paul Carter Traducido al espaol por Leonardo Rodr n guez Mjica. Sus comentaros y u sugerencias acerca de la traduccin por favor a: lrodri@udistrital.edu.co o Este documento puede ser reproducido y distribuido totalmente (incluida esta paternidad literaria, copyright y aviso de autorizacin), no se puede coo brar por este documento en s mismo, sin el consentimiento del autor. Esto incluye una utilizacin racional de extractos como revisiones y anuncios, o y trabajos derivados como traducciones. Observe que esta restriccin no est prevista para prohibir el cobro por el o a servicio de impresin o copia del documento o A los docentes se les recomienda usar este documento como recurso de clase; sin embargo el autor apreciar ser noticado en este caso. a

Prefacio
Propsito o
El propsito de este libro es dar la lector un mejor entendimiento de cmo o o trabajan realmente los computadores a un nivel ms bajo que los lenguaa jes de alto nivel como Pascal. Teniendo un conocimiento profundo de cmo o trabajan los computadores, el lector puede ser ms productivo desarrollana do software en lenguajes de alto nivel tales como C y C++. Aprender a programar en lenguaje ensamblador es una manera excelente de lograr este objetivo. Otros libros de lenguaje ensamblador an ensean a programar el u n procesador 8086 que us el PC original en 1981. El procesador 8086 slo o o soporta el modo real. En este modo, cualquier programa puede acceder a cualquier direccin de memoria o dispositivo en el computador. Este modo o no es apropiado para un sistema operativo multitarea seguro. Este libro, en su lugar discute cmo programar los procesadores 80386 y posteriores en o modo protegido (el modo en que corren Windows y Linux). Este modo soporta las caracter sticas que los sistemas operativos modernos esperan, como memoria virtual y proteccin de memoria. Hay varias razones para usar el o modo protegido 1. Es ms fcil de programar en modo protegido que en el modo real del a a 8086 que usan los otros libros. 2. Todos los sistemas operativos de PC se ejecutan en modo protegido. 3. Hay disponible software libre que se ejecuta en este modos. La carencia de libros de texto para la programacin en ensamblador de PC o para modo protegido es la principal razn por la cual el autor escribi este o o libro. Cmo lo dicho antes, este libro hace uso de Software Libre: es decir el o ensamblador NASM y el compilador de C/C++ DJGPP. Ambos se pueden descargar de Internet. El texto tambin discute cmo usar el cdigo del ene o o samblador NASM bajo el sistema operativo Linux y con los compiladores de C/C++ de Borland y Microsoft bajo Windows. Todos los ejemplos de estas i

ii

PREFACIO

plataformas se pueden encontrar en mi sitio web: http://www.drpaulcarter.com/pcasm. Debe descargar el cdigo de los ejemplos, si desea ensamblar y correr los muo chos ejemplos de este tutorial. Tenga en cuenta que este libro no intenta cubrir cada aspecto de la programacin en ensamblador. El autor ha intentado cubrir los tpicos ms o o a importantes que todos los programadores deber tener an

Reconocimientos
El autor quiere agradecer a los muchos programadores alrededor del mundo que han contribuido al movimiento de Software Libre. Todos los programe y an este libro en s mismo fueron producidos usando software libre. El u autor desear agradecerle especialmente a John S. Fine, Simon Tatham, a Julian Hall y otros por desarrollar el ensamblador NASM ya que todos los ejemplos de este libro estn basados en l; a DJ Delorie por desarrollar el a e compilador usado de C/C++ DJGPP; la numerosa gente que ha contribuido al compilador GNU gcc en el cual est basado DJGPP; a Donald Knuth y a A otros por desarrollar los lenguajes de composicin de textos TEX y L TEX 2 o que fueron usados para producir este libro; a Richar Stallman (fundador de la Free Software Fundation), Linus Torvalds (creador del ncleo de Linux) y u a otros que han desarrollado el software que el autor ha usado para producir este trabajo. Gracias a las siguientes personas por correcciones: John S. Fine Marcelo Henrique Pinto de Almeida Sam Hopkins Nick DImperio Jeremiah Lawrence Ed Beroset Jerry Gembarowski Ziqiang Peng Eno Compton Josh I Cates Mik Miin Luke Wallis

iii Gaku Ueda Brian Heward Chad Gorshing F. Gotti Bob Wilkinson Markus Koegel Louis Taber Dave Kiddell Eduardo Horowitz Sbastien Le Ray e Nehal Mistry Jianyue Wang Jeremias Kleer Marc Janicki

Recursos en Internet
Pgina del autor a Pgina de NASM en SourceForge a DJGPP Ensamblador con Linux The Art of Assembly USENET Documentacin de Intel o

http://www.drpaulcarter.com/ http://sourceforge.net/projects/nasm/ http://www.delorie.com/djgpp http://www.linuxassembly.org/ http://webster.cs.ucr.edu/ comp.lang.asm.x86 http://developer.intel.com/design/Pentium4/documentation.ht

Comentarios
El autor agradece cualquier comentario sobre este trabajo. E-mail: WWW: pacman128@gmail.com http://www.drpaulcarter.com/pcasm

iv

PREFACIO

Cap tulo 1

Introduccin o
1.1. Sistemas de numeracin o

La memoria en un computador est compuesta de nmeros. La memoria a u del computador no almacena estos nmeros en decimal (base 10). Porque u se simplica mucho el hardware, los computadores almacenan toda la informacin en binario (base 2). Primero haremos una revisin del sistema o o decimal.

1.1.1.

Decimal

Los nmeros con base 10 estn compuestos de 10 posibles d u a gitos (0-9). Cada d gito de un nmero tiene una potencia de 10 asociada con l, basada u e en su posicin en el nmero. Por ejemplo: o u 234 = 2 102 + 3 101 + 4 100

1.1.2.

Binario

Los nmeros en base dos estn compuestos de dos posibles d u a gitos (0 y 1). Cada d gito de un nmero tiene una potencia de 2 asociada con l basada u e en su posicin en el nmero. Por ejemplo: o u 110012 = 1 24 + 1 23 + 0 22 + 0 21 + 1 20 = 16 + 8 + 1 = 25 Esto muestra cmo los nmeros binarios se pueden convertir a decimal. o u El Cuadro 1.1 muestra cmo se representan los primeros nmeros en binario. o u

2 Decimal 0 1 2 3 4 5 6 7 Binary 0000 0001 0010 0011 0100 0101 0110 0111

CAP ITULO 1. INTRODUCCION Decimal 8 9 10 11 12 13 14 15 Binary 1000 1001 1010 1011 1100 1101 1110 1111

Cuadro 1.1: Decimal de 0 a 15 en binario No hay carry antes 0 1 1 +1 +0 +1 1 1 0 c S hay carry antes 0 1 1 +1 +0 +1 0 0 1 c c c

0 +0 0

0 +0 1

Figura 1.1: Suma binaria (c es carry)

La Figura 1.1 muestra cmo se suman los d o gitos binarios individuales 110112 (bits). Ac sigue un ejemplo: +100012 a 1011002 Si uno considera la siguiente divisin decimal: o 1234 10 = 123 r 4 podemos ver que esta divisin suprime el d o gito ms a la derecha de nmero a u y desplaza los otros d gitos una posicin a la derecha. Dividiendo por dos o hacemos una operacin similar, pero para los d o gitos binarios de un nmero. u Consideremos la siguiente divisin binaria1 : o 11012 102 = 1102 r 1 Este hecho se puede usar para convertir un nmero decimal a su repreu sentacin equivalente en binario como muestra la Figura 1.2. Este mtodo o e encuentra primero el bit del extremo derecho, llamado bit menos signicativo (lsb). El bit del extremo izquierdo es llamado bit ms signicativo (msb). a La unidad bsica de memoria est compuesta de 8 bits y es llamado byte a a
1 El sub ndice 2 se usa para mostrar que el nmero est representado en binario no en u a decimal

1.1. SISTEMAS DE NUMERACION

Decimal 12 2 = 6 r 0 62=3r 0 32=1r 1 12=0r 1

Binary 1100 10 = 110 r 0 110 10 = 11 r 0 11 10 = 1 r 1 1 10 = 0 r 1

25 2 = 12 r 1 11001 10 = 1100 r 1

As 2510 = 110012 Figura 1.2: Conversin a decimal o

1.1.3.

Hexadecimal

Los nmero hexadecimales tienen base 16. Los hexadecimales (o hex ) se u pueden usar como una representacin resumida de los nmeros binarios. Los o u nmeros hexadecimales tienen 16 d u gitos posibles. Esto crea un problema ya que no hay s mbolos para estos d gitos adicionales despus del nueve. e Por convencin se usan letras para estos d o gitos adicionales. Los 16 d gitos hexadecimales son: 0-9 y luego A, B, C, D, E, F. El d gito A equivale a 10 en decimal, B es 11 etc. Cada d gito de un nmero hexadecimal tiene una u potencia de 16 asociada con l. Por ejemplo: e 2BD16 = 2 162 + 11 161 + 13 160 = 512 + 176 + 13 = 701

Para convertir de decimal a hex use la misma idea que la usada para la conversin binaria excepto que se divide por 16. Vea la Figura 1.3 para un o ejemplo. La razn por la cual los hexadecimales son utiles es que hay una manera o fcil para convertir entre hex y binario. Los nmero binarios se tornan largos a u y molestos rpidamente. Los hex son una manera mucho ms compacta de a a representar los nmeros binarios. u Para convertir un nmero hexadecimal a binario simplemente convierta u cada d gito hexadecimal a un nmero binario de 4 bits. Por ejemplo, 24D16 u se convierta a 0010 0100 11012 . Observe que los ceros delanteros son importantes! Si los ceros del d gito de la mitad de 24D16 no se usan el resultado es errneo. Convertir de binario a hex es as de fcil. Uno hace el proceso o a

CAP ITULO 1. INTRODUCCION

589 16 = 36 r 13 36 16 = 2 r 4 2 16 = 0 r 2

As 589 = 24D16 Figura 1.3: inverso, convierte cada segmento de 4 bits a hexadecimal comenzando desde el extremo derecho, no desde el izquierdo, del nmero binario. Esto asegura u 2 . Ejemplo: que el segmento de 4 bits es correcto 110 6 0000 0 0101 5 1010 A 0111 7 11102 E16

Un nmero de 4 bits es llamado nibble . As cada d u gito hexadecimal corresponde a un nibble. Dos nibbles conforman un byte y por lo tanto un byte puede ser representado por dos d gitos hexadecimales. Los valores de un byte van de 0 a 11111111 en binario, 0 a FF en hex y 0 a 255 en decimal.

1.2.
1.2.1.

Organizacin del computador o


La Memoria

La memoria es medida La unidad bsica de memoria es el byte. Un computador con 32 Mega a en unidades de kilobytes bytes de memoria puede almacenar aproximadamente 32 millones de bytes ( 210 = 1024 bytes), mega de informacin. Cada byte est etiquetado por un n mero unico conocido o a u bytes ( 220 = 1048576 como su direccin. Tal como lo muestra la Figura 1.4. o bytes) y giga bytes ( 230 = 1073741824 bytes). Direccin 0 o 1 2 3 4 5 6 7

Memoria

2A

45

B8

20

8F

CD

12

2E

Figura 1.4: Direcciones de Memoria

A menudo la memoria se usa en trozos ms grandes que un byte. en la a arquitectura del PC, los nombres que se le han dado a estas secciones de memoria ms grandes se muestran en la Tabla 1.2. a
2 Si no es claro porque el punto de inicio hace la diferencia, intente convertir el ejemplo comenzando desde la izquierda

1.2. ORGANIZACION DEL COMPUTADOR word double word quad word paragraph 2 bytes 4 bytes 8 bytes 16 bytes

Cuadro 1.2: Unidades de memoria

Todos los datos en la memoria son numricos. Los caracteres son almacee nados usando cdigos de caracteres que traduce un nmero en un carcter. o u a Uno de los cdigos de caracteres es conocido como ASCII (American Stano dar Code for Information Interchange). Un nuevo cdigo, ms completo, o a que est reemplazando al ASCII es el Unicode. Una diferencia clave entre a los dos cdigos es que el ASCII usa un byte para codicar un carcter, pero o a Unicode usa dos bytes (o una palabra) por carcter. Por ejemplo ASCII dea codica el byte 4116 (6510 ) en la A mayscula. Unicode la codica con la u palabra 004116 . Ya que ASCII usa un byte est limitado a slo 256 caracteres a o 3 Unicode ampl los valores ASCII a palabras y permite que se diferentes. a representen muchos ms caracteres. Esto es importante para representar los a caracteres de todas las lenguas del mundo.

1.2.2.

La CPU

La Unidad Central de Procesamiento (CPU) es el dispositivo f sico que ejecuta las instrucciones. Las instrucciones que ejecuta la CPU son por lo general muy simples. Las instrucciones pueden requerir datos que estn en un e lugar especial de almacenamiento de la CPU en s misma llamados registros. La CPU puede acceder a los datos en los registros mucho ms rpido que en a a la memoria. Sin embargo el nmero de registros en la CPU es limitado, as el u programador debe tener cuidado de dejar slo los datos que est usando en o e los registros. Las instrucciones que un tipo de CPU ejecuta las hace en lenguaje de mquina. Los programas en lenguaje de mquina tienen una estructura mua a cho ms bsica que los lenguajes de alto nivel. Las instrucciones en lenguaje a a de mquina son codicadas como nmeros no en formatos de texto amia u gables. Una CPU debe estar en capacidad de decodicar una instruccin o de propsito muy rpidamente para correr ecientemente. Los lenguajes de o a mquina son diseados con este objetivo en mente , no para ser fcilmente a n a descifrados por humanos. Los programas escritos en otros lenguajes deben ser convertidos en lenguaje de mquina nativo para que se ejecute en el coma putador. Un compilador es un programa que traduce programas escritos en
3

De hecho ASCII slo usa los 7 bits ms bajos y slo tiene 128 valores diferentes o a o

CAP ITULO 1. INTRODUCCION

GHz signica giga Hertz o mil millones de ciclos por segundo. Una CPU de 1.5 GHz tiene mil quinientos millones de pulsos de reloj por segundo.

un lenguaje de programacin a el lenguaje de mquina de una arquitectura o a en particular de un computador. En general cada tipo de CPU tiene su propio y unico lenguaje de mquina. Esa es una de las razones por las cuales a programas escritos para un Mac no corren en un PC tipo IBM Los computadores usan un reloj para sincronizar la ejecucin de las o instrucciones.. El reloj pulsa a una frecuencia ja conocida como velocidad del reloj. Cuando Ud. compra un computador de 1.5 GHz, la frecuencia de su reloj es 15.GHz. Simplemente toca a una razn constante, la electrnica o o de la CPU usa un ritmo para realizar sus operaciones correctamente, como el ritmo de un metrnomo para la ejecucin de msica al ritmo correcto. o o u El nmero de toques (o como ellos llaman comnmente ciclos) que una u u instruccin requiere depende de la instruccin anterior y de otros factores o o tambin. e

1.2.3.

La familia de CPU 80x86

Las PC de tipo IBM tienen una CPU de la familia Intel (o un clon de ellas) Las CPU de esta familia todas tienen algunas caracter sticas comunes incluyendo el lenguaje de mquina bsico. Sin embargo los miembros ms a a a recientes ampl grandemente las caracter an sticas. 8888,8086: Estas CPU desde el punto de vista de la programacin son o iguales. Ellas fueron las CPUs usadas en las primeras PC. Ellos usan varios registros AX, BX, CX, DX, SI, DI, BP, SP, CS, DS, SS, ES, IP, FLAG. Ellas solo soportan hasta 1 Mega byte de memoria y slo opera o en modo real. En este modo un programa puede acceder a cualquier direccin de memoria, an a la memoria de otros programas Esto hace o u la depuracin y seguridad muy dif o cil. Tambin la memoria del proe grama tiene que ser dividida en segmentos. Cada segmento no puede ser ms largo que 64 KB a 80286: Esta CPU se usa en los PC tipo AT. Agrega unas instrucciones nuevas al lenguaje de mquina base del 8080/86. Sin embargo la nueva a caracter stica principal nueva es el modo protegido de 16 bits. En este modo puede acceder hasta 16 Mega bytes de memoria y proteger los programas del acceso de otros. Sin embargo los programas todav a estn divididos en segmentos que no pueden ser ms grandes de 64K. a a 80386: Esta CPU es una gran ampliacin del 80286. Primero extiende los o registros para almacenar 32 bits ((EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP, EIP) y aade dos nuevos registros de 16 bits FS y GS. Tamn bin aade un nuevo modo protegido de 32 bits. En este modo pueden e n acceder hasta 4 Gigabyes. Los programas otra vez estn divididos en a

1.2. ORGANIZACION DEL COMPUTADOR AX AH AL Figura 1.5: El registro AX

segmentos, pero ahora cada segmento tambin puede ser hasta de 4 e Giga bytes en tamao n 80486/Pentium/Pentium Pro: Estos miembros de la familia 80x86 aaden n muy pocas caracter sticas nuevas. Ellos principalmente aceleran la ejecucin de las instrucciones. o Pentium MMX: Este procesador aade instrucciones MMX (eXtensiones n MultiMedia) al Pentium. Estas instrucciones pueden acelerar instrucciones comunes grcas. a item[Pentium II:] Este es el procesador Pentium Pro con las instrucciones MMX aadidas (El pentium III es esencialmente slo un Penn o tium II rpido. a

1.2.4.

Registros de 16 bits del 8086

La CPU original 8086 suministra 4 registros de 16 bits de propsito geno eral AX, BX, CX y DX. Cada uno de esos registros puede ser descompuesto en los registros AL y AH que muestra la Figura 1.5. El registro AH contiene los 8 bits superiores de AX t AL contiene los 8 bits bajos de AX. A menudo AH y AL son usados como registros independientes de 8 bits sin embargo cambiando el valor de AX cambiar AH y AL y viceversa. Los a registros de propsito general son usados en muchos movimientos de datos o e instrucciones aritmticas. e Hay dos registros de ndice de 16 bits SI y DI. Ellos son a menudo usados como apuntadores, pero pueden ser usados para muchos de los mismos propsitos como los registros generales. Sin embargo, ellos no se pueden o descomponer en registros de 8 bits. Los registros de 16 bits BP y SP son usados para sealar a los datos en n la pila y son llamados Apuntador Base (Base Pointer) y apuntador a la pila (Stack Pointer), respectivamente. Ellos se discutirn luego. a Los registros de 16 bits CS, DS, SS y ES son registros de segmento. Ellos sealan qu memoria es usada por diferentes partes de un programa. CS n e signica segmento de cdigo (code segment), DS segmento de datos (data o segment), SS Segmento de la pila (Stack Segment) y ES segmento extra (Extra Segment). ES es usado como un registro temporal. Los detalles de estos registros estn en las Secciones 1.2.6 y 1.2.7. a

CAP ITULO 1. INTRODUCCION

El registro IP Apuntador a la instruccin (instruction pointer) es usado o con el registro CS para obtener la direccin de la siguiente instruccin a ser o o ejecutada por la CPU. Normalmente cuando se ejecuta una instruccin IP o avanza hasta sealar a la siguiente instruccin en memoria. n o The Instruction Pointer (IP) register is used with the CS register to keep track of the address of the next instruction to be executed by the CPU. Normally, as an instruction is executed, IP is advanced to point to the next instruction in memory. El registro FLAGS almacena informacin importante sobre los resultados o de una instruccin anterior. Estos resultados son almacenados como bits o individuales en el registro. Por ejemplo el bit Z es 1 si el resultado de la instruccin anterior fue cero o 0 si el resultado no fue cero. No todas las o instrucciones modican bits en FLAGS, consulte la tabla en el apndice e para ver cmo instrucciones espec o cas afectan el registro FLAGS

1.2.5.

Registros de 32 bits del 80386

El 80386 y los procesadores posteriores tienen registros extendidos. Por ejemplo el registro de 16 bits AX se extendi para se de 32 bits. Para la o compatibilidad con sus predecesores, AX se reere al registro de 16 bits y EAX se usa para referirse al registro extendido de 32 bits. AX son los 16 bits inferiores de EAX tal como AL son los 8 bits inferiores de AX (y EAX). No hay forma de acceder directamente a los 16 bits superiores de EAX Los otros registros extetendidos son EBX, ECX, EDX, ESI and EDI. Muchos de los otros registros se extienden tambien BP se convierte en EBP, SP se convierte en ESP , FLAGS en EFLAGS e IP en EIP. Sin embargo son diferentes los registros de ndice y los de propsito general, en el modo o protegido de 32 bits (discutidos abajo) slo se usan las versiones extendidas o de estos registros. Los registros de segmento continan siendo de 16 bits en el 80386. Hay u tambin dos nuevos registros de segmento: FS y GS. Sus nombres no signife ican nada. Ellos son registros adicionales para segmentos temporales (como ES). Una de las deniciones del trmno word se reere a el tamo del registro e n de datos de la CPU. Para la familia 80x86, el trmino es ahora un poco e confuso. En la Tabla 1.2, uno ve que word est denida para ser 20 bytes a (o 16 bits). Este fue el signicado que se le dio, cuando se lanz la primera o vez el 8086. Cuando se desarroll el 80386, se decidi dejar la denicin de o o o word sin cambio, auque el tamao del registro cambi. n o

1.2.6.
De dnde viene el ino fame l mite de 640K de DOS? La BIOS requerida algunode 1M para el cdio go y para los dispositivos de hardware como la pantalla de video

Modo Real

En el modo real la memoria est limitada a slo 1 mega byte (220 bytes) a o

1.2. ORGANIZACION DEL COMPUTADOR

Las direcciones vlidas estn desde 0000 hasta FFFFF. (en hexadecimal) a a Estas direcciones requieren un nmero de 20 bits Obviamente un nmero u u de 20 bits no cabr en ningn registro de 16 bits. Intel solucion este proba u o lema usando 2 valores de 16 bits para determinar una direccin. El primer o valor de 16 bits es llamado selector. Los valores del selector deben estar almacenados en registros de segmento El segundo valor de 16 bits es llamado desplazamiento (oset) La direccin f o sica referenciada por un par selector:desplazamiento es calculada por la frmula: o 16 selector + oset multiplicar por 16 en hexadecimal es muy fcil, es slo aadir un 0 a la a o n derecha del nmero. Por ejemplo la direccin f u o sica referenciada por 047C:0048 est dada por: a 047C0 +0048 04808 En efecto, el valor selector es un nmero prrafo (vea la Tabla 1.2). u a direcciones reales segmentadas tienen desventajas: Un slo valor de selector slo puede referenciar 64 K de memoria (el o o l mite superior del desplazamiento de 16 bits). Qu pasa si un prograe ma tiene ms de 64 K de cdigo? Un solo valor en CS no se puede usar a o para toda la ejecucin del programa. El programa se debe dividir en o secciones (llamadas segmentos menores de 64 K en tamao. Cuando la n ejecucin se mueve de un segmento a otro los valores de CS se deben o cambiar. Esto puede ser muy incmodo o Cada byte de memoria no tiene una sola direccin segmentada. La dio reccin f o sica 04804 puede ser referenciada por 047C:0048, 047D:0038, 0047E:0028 o 047B:0058. Esto puede complicar la comparacin de dio recciones segmentadas.

1.2.7.

Modo protegido de 16 bits

En el modo protegido del 80286 los valores del selector son interpretados completamente diferente que en el modo En el modo real, un valor de selector es un nmero de prrafo de memoria f u a sica. En el modo protegido un valor selector es un ndice en una tabla de descripcin. En ambos modos, los o programas son divididos en segmentos. En modo real estos segmentos estn a en posiciones jas en la memoria f sica y el selector denota el nmero de u prrafo de comienzo del segmento. En modo protegido los segmentos no a

10

CAP ITULO 1. INTRODUCCION

estn en posiciones jas en la memoria f a sica. De hecho no tiene que estar todo el segmento en memoria. El modo protegido usa una tcnica llamada memoria virtual . La idea e bsica de un sistema de memoria virtual, es dejar slo los datos y el cdigo a o o que los programas estn usando en un momento dado. Otros datos y cdigo a o son almacendos temporalmente en el disco hasta que ellos se necesiten de nuevo. Cuando retorna un segmento a la memoria del disco, es muy probable que se coloque en un rea diferente de memoria en el que estuvo antes de a ser enviada al disco. Todo esto es hecho transparementemente por el sistema operativo. El programa no se tiene que escribir de otra manera para que la memoria virtual trabaje. En el modo protegido a cada segmento se le asigna una entrada en una tabla de descriptores. Esta entrada tiene toda la informacin que el sistema o necesita conocer sobre el segmento. Esta informacin incluye: si est aco a tualemente en memoria, si es as dnde est, permiso de acceso (ejem: slo o a o lectura). El ndice de la entrada del segmento es el valor del selector que est almacendo en los registros de segmento. a Un conocido columnista de Una gran desventaja del modo protegido es que los desplazamientos estn a PC llam al 286 cerebro a n en cantidades de 16 bits Como una consecuencia de esto, los tama os o u n muerto. de los segmentos estn todav limitados a un mximo de 64K. Esto hace a a a problemtico el uso de arreglos grades. a

1.2.8.

Modo protegido de 32 bits

El 80386 introdujo el modo protegido de 32 bits. Hay dos grandes diferencias entre los modos protegidos de un 386 de 32 bits y un 286 de 16 bits 1. Los desplazamientos se ampl a 32 bits. Esto permite un rango de dean splazamiento hasta 4 billones. As los segmentos pueden tener tamaos n hasta de 4 gigabytes. 2. Los segmentos pueden ser divididos en unidades ms pequeas de 4K a n llamadas pginas. El sistema de memoria virtual trabaja ahora con a pginas en lugar de segmentos. Esto signica que slo partes de un a o segmento pueden estar en memoria a la vez. En el modo de 16 bits del 286 o todo el segmento est en memoria o no est. Esto no es prctico a a a con los grandes segmentos que permite el modo de 32 bits. En Windows 3.x el modo standar se reere al modo protegido de 16 bits del 286 y el modo ampliado se reere al modo de 32 bits.

1.2.9.

Interrupciones

Algunas veces el ujo ordinario de un programa debe ser interrumpido para procesar eventos que requieren una respuesta rpida. El hardware de a

1.3. LENGUAJE ENSAMBLADOR

11

un computador provee un mecanismo llamado interrupcin para manipuo lar estos eventos. Por ejemplo cuando se mueve el ratn la interrupcin de o o hardware del ratn es el programa actual para manejar el movimiento del o ratn (para mover el cursor del mouse, etc) Las interrupciones hacen que o el control se pase a un manipulador de interrupciones. Los manipuladores de interrupciones son rutinas que procesan la interrupcin. A cada tipo de o interrupcin se le asigna un nmero entero. En el comienzo de la memoria o u f sica una tabla de vectores de interrupcin que contiene la direccin del sego o mento de los manipuladores de la interrupcin. El nmero de la interrupcin o u o es escencialmente un ndice en esta tabla. Las interrupciones externas son levantadas desde el exterior de la CPU (el ratn es un ejemplo de esto). Muchos dispositivos de E/S levantan intero rupciones (teclado, temporizador, disco duro CD ROM y tarjetas de sonido) Las interrupciones internas son levantadas desde la CPU, desde una instruccin de error o desde una instruccin de interrupcin. Las instrucciones de o o o error tambin se llaman trampas. Las instrucciones generadas desde la ine struccin de interrupcin son llamadas interrupciones de sofware. DOS usa o o estas interrupciones paa implementar su API (Interfaz de programas de Aplicacin) Sistema operativos ms modernos (como Windows y Linux) uso a 4 an una interfaz basada en C Muchos manipuladores de interrupcin devuelven el control al programa o interrumpido cuando ella culmina. Ella restaura todos los registros con los mismos valores que ten antes que ocurriera la interrupcin. Las trampas an o generalmente no retornan. A menudo ellas acaban el programa.

1.3.
1.3.1.

Lenguaje ensamblador
Lenguaje de mquina a

Cada tipo de CPU entiende su propio lenguaje de mquina. Las instruca ciones en lenguaje de mquina son nmeros almacenados como bytes en a u memoria. Cada instruccin tiene su propio y unico cdigo llamado cdigo o o o de operacin u opcode. Las instrucciones del procesador 80X86 var en o an tamao. El opcode est siempre al inicio de la instruccin. Muchas instrucn a o ciones incluyen tambin datos (ver constantes o direcciones) usados por las e instrucciones. El lenguaje de mquina es muy dif de programar directamente. Dea cil scifrar el signicado de las instrucciones codicadas numricamente es tee dioso para los humanos. Por ejemplo la instruccin para sumar los registros o EAX y EBX y almacenar el resultado en EAX est codicada por los sigua ientes cdigos hexadecimales o
4

Sin embargo, ellas pueden usar una interfaz de bajo nivel (a nivel del kernel)

12 03 C3

CAP ITULO 1. INTRODUCCION

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

1.3.2.

Lenguaje ensamblador

Un programa Escrito en lenguaje ensamblador es almacenado como texto (tal como programas de alto nivel). Cada instruccin representa exactamente o una instruccin de la mquina. Por ejemplo, la instruccin de suma descrita o a o arriba podr ser representada en lenguaje ensambaldor como: a add eax, ebx Ac el signicado de la instruccin es mucho ms claro que el cdigo de la a o a o mquina. La palabra add es el nemnico nemnico para la instruccin de a o o o suma . La forma general de una instruccin de ensamblaje es: o mnemonico operando(s) Un ensamblador es un programa que lee un archivo de texto con instrucciones de ensamblador y convierte el ensamblador en cdigo de mquina. Los o a compiladores son programas que hacen conversiones similares para lenguajes de programacin de alto nivel. Un ensamblador es mucho ms simple que un o a Les tom varios aos a compilador. Cada instruccin de lenguaje ensamblador representa una sola o n o los cient cos de la com- instruccin de la mquina. Las instrucciones de un lenguaje de alto nivel son o a putacin imaginarse cmo mucho ms complejas y pueden requerir muchas instrucciones de mquina. o o a a escribir un compilador Otra diferencia importante entre los lenguajes ensamblador y de alto nivel es que debido a que cada tipo de CPU tiene su propio lenguaje de mquina, tambin tiene su propio lenguaje ensamblador. Trasladar prograa e mas entre arquitecturas de computador diferentes es mucho ms dif que a cil en un lenguaje de alto nivel. En los ejemplos de este libro se usa Netwide Assembler o NASM . Est disponible libremente en internet (vea el prefacio para la URL). Los a ensambladores ms comunes son el ensamblador de Microsoft (MASM) y el a de Borland (TASM) . Hay algunas diferencias en la sintaxis del ensamblador de NASM, MASM y TASM .

1.3.3.

Operandos de las instrucciones

Los cdigos de las instrucciones de mquina tienen una variedad de tipos o a y operandos; sin embargo, en general cada instruccin en si misma tiene un o nmero jo de operandos (0 a 3). Los operandos pueden tener los siguientes u tipos:

1.3. LENGUAJE ENSAMBLADOR

13

registro: Estos operandos se reeren directamente al contenido de los registros de la CPU. memoria: Estos se reeren a los datos en la memoria. La direccin de los o datos puede ser una constante ja en la instruccin o puede ser calo culada usando los valores de los registros. Las direcciones son siempre desplazamientos relativos al comienzo de un segmento. immediato: Estos son valores jos que estn listados en la instruccin en a o s misma. Ellos son almacenados en la instruccin en si misma (en el o segmento de cdigo), no en el segmento de datos. o implicado: Estos operandos no son mastrados expl citamente. Por ejemplo, la instruccin de incremento aade uno a un registro o a memoria. El o n uno est impl a cito.

1.3.4.

instrucciones bsicas a

La instruccin esencial es MOV . Ella translada datos de un lugar a otro o (como el operador de asignacin en un lenguaje de alto nivel). Toma dos o operandos: mov dest, src El dato especicado por src es copiado a dest. Una restriccin es que los dos o operandos no pueden ser operandos de memoria. Esto seala otra peculiarin dad del ensamblador. Hay a menudo algunas reglas arbitrarias sobre cmo o se usan las instrucciones. Los operandos deben tener el mismo tamao. El n valor de AX no puede ser almacenado en BL. Ac hay un ejemplo(los ; inician un comentario) a mov mov add add sub sub eax, 3 bx, ax eax, 4 al, ah ; almacena 3 en el registro EAX (3 es el operando inmediato) ; almacena el valor de AX en el registro BX ; eax = eax + 4 ; al = al + ah

La instruccin ADD se usa para sumar enteros. o

La instruccin SUB resta enteros. o bx, 10 ; bx = bx - 10 ebx, edi ; ebx = ebx - edi

Las instrucciones INC y DEC incrementan o decrementan valores en uno. Ya que el uno es un operando impl cito, el cdigo de de mquina para INC o a y el DEC es ms pequeo que los de las instrucciones ADD y SUB. a n inc dec ecx dl ; ecx++ ; dl--

14

CAP ITULO 1. INTRODUCCION

1.3.5.

Directivas

Una directiva es un articio 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 cdigo de mquina. Los usos o a comunes de las directivas son: Denir constantes Denir memoria para almacenar datos en ella Denir la memoria para almacenar datos en ella Agrupar la memoria en segmentos Inclu cdigo fuente condicionalmente r o Inclu otros archivos r El cdigo de NASM pasa a travs de un preprocesador tal como C. o e Tiene muchas de las rdenes del preprocesador tal como C. Sin embargo las o directivas del preprocesador de NASM comienzan con un como en C. directiva equ La directiva equ se puede usar para denir un s mbolo. Los s mbolos son constantes con nombre que se pueden emplear en el programa ensamblador. El formato es: smbolo equ valor Los valores de los s mbolos no se pueden redenir posteriormente. La directiva %dene Esta directiva es parecida a la #dene de C. Se usa normalmente para denir macros tal como en C. %define SIZE 100 mov eax, SIZE El cdigo de arriba dene un macro llamado size y muestra su uso en una o instruccin MOV. Los macros son ms exibles que los s o a mbolos de dos maneras. Los macros se pueden redenir y pueden ser ms que simples constantes a nmericas. u

1.3. LENGUAJE ENSAMBLADOR Unidad byte word double word quad word ten bytes Letra B W D Q T

15

Cuadro 1.3: Letras para las directivas RESX y DX Directivas de datos Las directivas de datos son usadas en segmentos de datos para denir espacios de memoria. Hay dos formas en que la memoria puede ser reservada. La primera es solo denir el espacio para los datos; la segunda manera dene el espacio y el valor inicial. El primer mtodo usa una de las directivas e RESX. La X se reemplaza con una letra que determina el tamao del objeto n (u objetos) que ser almacenados. La tabla 1.3 muestra los valores posibles. a El segundo mtodo (que dene un valor inicial tambin) usa una de las e e directivas DX. Las X son las mismas que las de la directiva RESX. Es muy comn marcar lugares de memoria con etiquetas. Las etiquetas u le permiten a uno referirse fcilmente a lugares de la memoria en el cdigo. a o Abajo hay varios ejemplos. L1 L2 L3 L4 L5 L6 L7 L8 db dw db db db dd resb db 0 1000 110101b 12h 17o 1A92h 1 "A" ; ; ; ; ; ; ; ; byte etiquetado como L1 con valor inicial 0 palabra etiquetada como L2 con valor inicial de 1000 byte con valor inicial binario de 110101 (53 en decimal) byte con valor inicial hex de 12 (18 en decimal) byte con valor inicial octal de 17 (15 en decimal) plabra doble con valor inicial hex de 1A92 un byte sin valor inicial byte con valor inicial del cdigo ASCII para A (65) o

Las comillas dobles o simples se interpretan igual. Las deniciones consecutivas de datos se almacenarn secuencialmente en memoria. Esto es, la a palabra L2 se almacenar inmediatamente despus que la L1. Se pueden a e denir tambin secuencias de memoria. e L9 L10 L11 db db db 0, 1, 2, 3 "w", "o", "r", d, 0 word, 0 ; define 4 bytes ; define una cadena tipo C = "word" ; igual que L10

La directiva DD se puede usar para denir o enteros o constantes de punta otante de presicin simple.5 Sin embargo DQ solo se puede usar o para denir constantes de punta otante de doble precisin. o
5

Punto otante de presicin simple es equivalente a la variable oat en C. o

16

CAP ITULO 1. INTRODUCCION

Para secuencias largas la directiva TIMES de NASM es a menudo util. Esta directiva repite su operando un nmero especicado de veces por ejemu plo: L12 L13 times 100 db 0 resw 100 ; equivalente a 100 veces db 0 ; reserva lugar para 100 palabras

Recuerde que las etiqueta pueden ser usadas para referirse a datos en el cdigo. Si se usa una etiqueta sta es interpretada como la direccin (o o e o desplazamiento) del dato. Si la etiqueta es colocada dentro de parntesis e cuadrados ([]), se interpreta como el dato en la direccin. En otras palabras, o uno podr pensar de una etiqueta como un apuntador al dato y los parntea e sis cuadrados como la des referencia al apuntador tal como el asterisco lo hace en C (MSSM y TASM siguen una convencin diferente). En el modo o de 32 bits las direcciones son de 32 bits. Ac hay algunos ejemplos. a
1 2 3 4 5 6 7

mov mov mov mov add add mov

al, [L1] eax, L1 [L1], ah eax, [L6] eax, [L6] [L6], eax al, [L6]

; ; ; ; ; ; ;

copia el byte que est en L1 en AL a EAX = direccin del byte en L1 o copia AH en el byte en L1 copia la palabra doble en L6 en EAX EAX = EAX + la palabra doble en L6 la palabra doble en L6 += EAX copia el primer byte de la palabra doble en L6 en AL

La l nea 7 de los ejemplos muestra una propiedad importante de NASM. El ensamblador no recuerda el tipo de datos al cual se reere la etiqueta. De tal forma que el programador debe estar seguro que usa la etiqueta correctamente. Luego ser comn almacenar direcciones de datos en registros a u y usar los registros como una variable apuntador en C. Una vez ms no se a verica que el apuntador se use correctamente. De este modo el ensamblador es mucho ms propenso a errores an que C. a u Considere la siguiente instruccin: o mov [L6], 1 ; almacena 1 en L6

Esta instruccin produce un error de tamao no especicado. Por qu? o n e Porque el ensamblador no sabe si almacenar el 1 como byte, palabra o palabra doble. Para denir esto, se aade un especicador de tamao . n n mov dword [L6], 1 ; almacena 1 at L6

Esto le dice al ensamblador que almacene un 1 en la palabra doble que comienza en L6. Otros especicadores son: BYTE, WORD, QWORD Y TWORD. 6
6 TWORD dene un rea de memoria de 10 bytes. El coprocesador de punto otante a usa este tipo de dato.

1.3. LENGUAJE ENSAMBLADOR print int print char print string imprime en la pantalla el valor del entero almacendo en EAX imprime en la pantalla el caracter cuyo cdigo ASCII o est almacendo en AL e imprime en la pantalla el contenido de la cadena en la direccin almacenada en EAX. La cadena debe ser o tipo C, terminada en NULL). imprime en pantalla el caracter de nueva l nea. lee un entero del teclado y lo almacena en el registro. lee un solo caracter del teclado y almacena el cdigo o ASCII en el registro EAX.

17

print nl read int read char

Cuadro 1.4: Rutinas de E/S en ensamblador

1.3.6.

Entrada y Salida

La entrada y salida son acciones muy dependientes del sistema . Involucra comunicarse con el hardware del sistema. Los lenguquajes del alto nivel, como C, proveen bibliotecas normalizadas de rutinas que suministran una interfas de programacin simple y uniforme para la dE/ S. Los lenguajes o ensamblador no disponen de bibliotecas normalizadas. Ellas deben acceder directamente al hardware (que es una operacin privilegiada en el modo o protegido) o usar rutina de bajo nivel que provea el sistema operativo. Es muy comn que se interfacen rutinas de ensamblador con C. Una de u las ventajas de esto es que el cdigo en ensamblador puede usar las rutinas o E/S de las bibliotecas estandar de C. Sin embargo uno debe conocer las reglas que usa C para pasar informacin entre rutinas. Estas reglas son o muy complicadas para cubrir ac (ellas se vern luego) . Para simplicar la a a E/S el autor ha desarrollado sus propias rutinas que ocultan las complejas reglas de C y provee una interfas mucho ms simple. La tabla 1.4 describe a las rutinas suministradas. Todas las rutinas preservan el valor de todos los registros , excepto las rutinas de lectura. Estas rutinas modican el valor del registro EAX. Para usar estas rutinas uno debe incluir un archivo con la informacin que el ensamblador necesita usarlas. Para incluir un archivo o en NASM use la directiva del preprocesador %include. La siguiente l nea incluye el archivo necesario para las rutinas de E/S hechos por el autor http://www.drpaulcarter.com/pcasm: %include "asm_io.inc" Para usar una de las rutinas print, uno carga EAX con el valor correcto y usa la instruccin Call para invocarla.La instruccin Call es equivalente o o a un llamado de funcin en un lenguaje de alto nivel. Hace un salto en la o ejecucin hacia otra seccin de cdigo pero despus retorna al origen luego o o o e

18

CAP ITULO 1. INTRODUCCION

que la rutina a culminado. El programa muestra varios ejemplos de llamadas de estas rutinas de E/S.

1.3.7.

Depuracin o

La biblioteca del autor tambin contiene algunas rutinas utiles para e depurar los programas. Estas rutinas de depuracin muestran informacin o o sobre el estado del computador sin modicar su estado. Estas rutinas son en realidad macros que muestran el estado de la CPU y luego hacen un llamado a una subrutina. Los macros estn denidos en el archivo code a asm io.inc discutido antes. Los matros se usan como instrucciones normales. Los operandos de los macros se separan con comas. Hay cuatro rutinas de depuracin llamadasdump regs, dump mem, dump stack o and dump math; Ellas muestran los valores de los registros, memoria, pila y el coprocesador matemtico respctivamente a dump regs Este macro imprime los valores de los registros (en hexadecimal) del computador stdout (la pantalla). Tambin imprime el estado e 7 Por ejemplo si la bandera cero es 1 de los bits del registto FLAGS. se muestra ZF. Si es cero no semuestra nada. Torma un solo entero como parmetro que luego se imprime. Este entero se puede usar para a distinguir la salida de diferentes rdenes dump regs. o dump mem Este macro imprime los valores de una regin de memoria o (en hexadecimal) y tambin como caracteres ASCII. Toma tres argue mentos delimitados por comas. El primero es un entero que es usado para identicar la salida (tal cual como el argumento de dump regs). El segundo argumento es la direccin a mostrar (esta puede ser una o etiqueta). El ultimo argumento es un nmero de l6 bytes para mostrar u luego de la direcccin. La memoria mostrada comenzar en el primer o a l mite un prrafo antes de la direccin solicitada. a o dump stack Este macro imprime los valores de la pila de la CPU (la pila se ver en el cap a tulo 4). La pila est organizada como palabras a dobles y est rutina las mostrar de esta forma. Toma tres parmeta a a ros separados por comas. El primero es un identicador entero (como u dump regs). El segundo es el nmero de palabras dobles para mostrar luego de la direccin que tenga almacenada el regisstro EBP, y el tero cer argumento es el nmero de palabras dobles a imprimir sobre la u direccin de EBP. o dump math Este macro imprime los valores de los registros del coprocesador matemtico. Toma un solo parmetro entero como argumena a
7

El cap tulo 2 discute este registro

1.4. CREANDO UN PROGRAMA int main() { int ret status ; ret status = asm main(); return ret status ; } Figura 1.6: cdigo de driver.c o

19

to que se usa para identicar la salida tal como el argumento de dump regs lo hace.

1.4.

Creando un programa

Hoy d no es comn crear un programa independiente escrito totalmente a u en lenguaje ensamblador. El ensamblador es usado para desarrollar ciertas rutinas cr tica. Por qu? Es mucho ms fcil programar en un lenguaje e a a de alto nivel que en ensamblador. Tambin al usar ensamblador es muy e dif transportar el programa a otras plataformas. De hecho es raro usar el cil ensamblador en todo. Por qu alguien quisiera aprender ensamblador? e 1. Algunas veces el cdigo escrito en ensamblador puede ser ms rpido o a a y pequeo que el cdigo generado por un compilador. n o 2. El ensamblador permite acceder directamente a caracter sticas del hardware del sistema que puede ser dif o imposible de usar descil de un lenguaje de alto nivel. 3. Aprender a programar en ensamblador le ayuda a uno a ganar un entendimiento profundo de cmo trabaja el computador. o 4. Aprender a programar en ensamblador ayuda a entender mejor cmo o trabajan los compiladores y los lenguajes de alto nivel como C. Los ultimos dos puntos demuestran que aprender ensamblador puede ser util an si uno nunca programa en l ms. De hecho, el autor raramente u e a programa en ensamblador pero usa las ideas aprendidas de l todos los d e as.

1.4.1.

Primer programa

Los primeros programas en este texto comenzarn todos con un prograa ma sencillo de C mostrado en la Figura 1.6. Simplemente llama otra funcin o

20

CAP ITULO 1. INTRODUCCION

llamada asm main. Esta es la rutina escrita en ensamblador. Hay varias ventajas de usar este programa en C. Primero dejamos que C je todos los parmetros para que el programa se ejecute correctamente en el modo proa tegido. Todos los segmentos y sus correspondientes registro de segmento sern iniciados por C. El cdigo en ensamblador no necesita preocuparse de a o nada de esto. Segundo las bibliotecas de C estarn disponibles para ser usa adas en el cdigo de ensamblador. Las rutinas de E/S del autor aprovechan o esto. Ellas usan las funciones de E/S de C (printf, etc.). Ahora se muestra un programa elemental en ensamblador. The early programs in this text will all start from the simple C driver program in Figure 1.6. It simply calls another function named asm main. This is really a routine that will be written in assembly. There are several advantages in using the C driver routine. First, this lets the C system set up the program to run correctly in protected mode. All the segments and their corresponding segment registers will be initialized by C. The assembly code need not worry about any of this. Secondly, the C library will also be available to be used by the assembly code. The authors I/O routines take advantage of this. They use Cs I/O functions (printf, etc.). The following shows a simple assembly program. first.asm ; Archivo: first.asm ; Primer programa en ensamblador. Este programa pide dos ; enteros como entrada e imprime su suma ; Para crear el ejecutable usando djgpp: ; ; nasm -f coff first.asm ; gcc -o first first.o driver.c asm_io.o %include "asm_io.inc" ; ; Los datos iniciados se colocan en el segmento .data ; segment .data ; ; Estas etiquetas se refieren a las cadenas usadas para la salida ; prompt1 db "Digite un nmero: ", 0 u ; no olvide el fin de cadena prompt2 db "Digite otro nmero: ", 0 u outmsg1 db "Ud. ha digitado ", 0 outmsg2 db " y ", 0 outmsg3 db ", la suma es ", 0

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

1.4. CREANDO UN PROGRAMA


23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64

21

; ; Los datos no iniciados se colocan en el segmento .bss ; segment .bss ; ; Estas etiquetas se~alan a palabras dobles usadas para almacenar los datos n ; de entrada ; input1 resd 1 input2 resd 1 ; ; El cdigo se coloca en el segmento .text o ; segment .text global _asm_main _asm_main: enter 0,0 ; setup routine pusha mov call call mov mov call call mov mov add mov eax, prompt1 print_string read_int [input1], eax eax, prompt2 print_string read_int [input2], eax eax, [input1] eax, [input2] ebx, eax ; print out prompt

; lee un entero ; lo almacena en input1 ; print out prompt

; lee un entero ; lo almacena en input2 ; eax = dword en input1 ; eax += dword en input2 ; ebx = eax ; imprime los valores de los registros ; imprimer la memoria

dump_regs 1 dump_mem 2, outmsg1, 1

; ; ahora, se imprimen los resultados en una serie de pasos ;

22
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82

CAP ITULO 1. INTRODUCCION mov call mov call mov call mov call mov call mov call call popa mov leave ret eax, outmsg1 print_string eax, [input1] print_int eax, outmsg2 print_string eax, [input2] print_int eax, outmsg3 print_string eax, ebx print_int print_nl

; se imprime el primer mensaje ; se imprime input1 ; se imprime el segundo mensaje ; se imprime input2 ; se imprime el tercer mensaje ; se imprime la suma (ebx) ; se imprime una nueva linea

eax, 0

; retorna a first.asm

La l nea 13 del programa dene una seccin del programa que especica o la memoria al ser almacenada en el segmento de datos ( cuyo nombre es .data ). Solo los datos iniciados se deber denir en este segmento. En an las l neas 17 a 21 se declaran varias cadenas. Ellas sern impresas con las a bibliotecas de C y como tal deben estar terminadas con el caracter null (el cdigo ASCII 0). Recuerde que hay una gran diferencia entre 0 y 0. o Los datos no iniciados deber declararse en el segmento bss (llamado an .bss en la l nea 26). Este segmento toma su nombre de un operador de ensamblador basado en UNIX que signica block started by simbol. Existe tambin el segmento de la pila.Ser discutido despus. e a e El segmento de cdigo es llamado .text por razones histricas. Ac es o o a donde se colocan las instrucciones. Observe que la etiqueta de la rutina principal (l nea 38) tiene un prejo de guin bajo. Esto es parte de las cono venciones de llamado de C. Esta convencin especica las reglas que usa C o cuando compila el cdigo. Es muy importante conocer esta convencin cuano o do se interfaza C con ensamblador. Luego se presentara toda la convencin, o sin embargo por ahora uno solo necesita conocer que todos los s mbolos de C ( funciones y variables globales ) tienen un guin bajo como prejo anexado o a l por el compilador de C. (Esta regla es espec e ca para Dos/windows, el compilador de C de linux no antepone nada a los nombres de los s mbolos). La directiva global en la l nea 37 le dice al ensamblador que tome la etiqueta asm main como global. Como en C, las etiquetas tienen un alcance interno por defecto. Esto signica que solo el cdigo en el mismo mdulo o o

1.4. CREANDO UN PROGRAMA

23

puede usar la etiqueta. La directiva global da a la (s) etiqueta (s) espec cadas de (s) un alcance externo. Este tipo de etiqueta puede ser alcanzado por cualquier mdulo en el programa. El mdulo asm io declara las etiquetas o o print int etc., globales . Este es el porque uno puede usarlas en el mdulo o first.asm.

1.4.2.

Dependencias del compilador

El cdigo de ensamblador de arriba es espec o co del compilador libre GNU C/C++8 DJGPP .9 Este compilador puede ser descargado libremente de internet. Requiere un PC 386 o posterior y se ejecuta bajo Dos, Windows 95/98 o NT. Este compilador usa archivos objeto con formato COFF (common objet le format). Para ensamblar este formato use la opcin -f coff o con nasm (como se muestra en los comentarios del cdigo). La extensin del o o archivo objeto resultante ser o. a El compilador de C de linux tambin es GNU. Para convertir el cdigo e o para que corra bajo linux simplemente quita los guin bajos de prejos en las o l neas 37 y38. Linux usa el formato ELF (Excecutable an Linkable Format) para los archivos objetos. Use la opcin -f elf para linux. Tambin produce o e un objeto con una extensin o. o Borland C/C++ es otro compilador popular. Usa el formato de OMF de microsoft para los archivos objeto. Use la opcin -f obj para los como piladores de Borland. La extensin del archivo objeto ser obj. El formato o a OMF utiliza unas directivas de segmento diferentes que los otros formatos de objetos. El segmento data (l nea 13) se debe cambiar a: segment DATA public align=4 class=DATA use32 el segmento bss (line 26) se debe cambiar a: segment BSS public align=4 class=BSS use32 El segmento text (line 36) se debe cambiar a: segment TEXT public align=1 class=CODE use32 Adems se debe aadir una nueva l a n nea antes de la l nea 36. group DGROUP BSS DATA El compilador de Microsoft C/C++ puede usar el formato OMF o el win 32 para los archivos objeto (si le dan un formato OMF, l convierte la e informacin internamente en win 32). El formato win 32 permite que los o segmentos se denan tal como DJGPP y linux. Use la opcin -f win 32 o para este formato. La extensin del archivo objeto ser obj. o a
8 9

Los archivos de un compilador dado estn a disponibles en la pgina a web del autor ya modicados para que trabajen con el compilador apropiado.

GNU es un proyecto de la Free Software Foundation (http://www.fsf.org) http://www.deloire.com/djgpp

24

CAP ITULO 1. INTRODUCCION

1.4.3.

Ensamblando el cdigo o

El primer paso es ensamblar el cdigo. Desde la l o nea de orden digite ; nasm -f formato-de-objeto first.asm Donde el formato del objeto es co ,elf , obj o win 32 dependiendo que compilador de C ser usado. (Recuerde que tambin se deben cambiar los archivos a e fuente por linux y Borland tambin). e

1.4.4.

Compilando el cdigo de C o

Compile el archivo driver.c usando un compilador de C. Para DJGPP, digite: gcc -c driver.c La opcin -c signica que solo compile, no intente encadenar an. Esta o u misma opcin trabaja en los compiladores de Linux, Borland, y Microsoft o tambin. e

1.4.5.

encadenando los archivos objeto

El encadenamiento es un proceso de combinar el cdigo de mquina y o a los datos en archivos objeto con archivos de biblioteca para crear un archivo ejecutable. Como se ver adelante, este proceso es complicado. a El cdigo de C requieren la biblioteca estandar de C y un cdigo de inicio o o especial para ejecutarse. Es mucho ms fcil dejar que el compilador de C a a llame al encadenador con los parmetros correctos que intentar llamar al a encadenador directamente. Por ejemplo encadenar el cdigo para el primer o programa utilizando DJGPP, digite: gcc -o first driver.o first.o asm io.o Esto crea un ejecutable llamado first.exe (o solo first bajo Linux). Con Borland uno usar a: bcc32 first.obj driver.obj asm io.obj Borland usa el nombre del primer archivo en la lista para determinar el nombre del ejecutable. As en el caso anterior el programa deber llamarse a first.exe. Es posible combinar el paso de compilar y encadenar. Por ejemplo: gcc -o first driver.c first.o asm io.o Ahora gcc compilar driver.C y entonces lo encadenar. a a

1.4. CREANDO UN PROGRAMA

25

1.4.6.

Entender un archivo de listado de ensamblador

La opcin -l archivo-de-listado se puede usar para decirle a nasm o que cree un archivo de listado con un nombre dado. Este archivo muestra cmo se ensambl el cdigo. Se muestra cmo las l o o o o neas 17 y 18 (en el segmento data) aparecen en el archivo de listado. Los nmeros de las l u neas estn en el archivo de listado; sin embargo observe que los nmeros de las a u l neas en el archivo fuente pueden no ser los mismas que las del archivo de listado. 48 49 50 51 52 00000000 00000009 00000011 0000001A 00000023 456E7465722061206E756D6265723A2000 456E74657220616E6F74686572206E756D6265723A2000 prompt1 db prompt2 db "Enter a number: ", 0 "Enter another number: ", 0

La primera columna en cada l nea es el nmero de l u nea y la segunda es el desplazamiento (en hex) de los datos en el segmento. La tercera columna muestra los valores en hexadecimal que sern almacenados. En este caso el a dato hexadecimal corresponde a cdigos ASCII. Finalmente en la l o nea se muestra el texto del cdigo fuente. Los desplazamientos mostrados en la seo gunda columna son muy probables que no sean los desplazamientos reales, los datos sern colocados en el programa completo. Cada mdulo puede a o denir sus propias etiquetas en el segmento de datos ( y en los otros segmentos tambin). En el paso de encadenamientoi vea la Seccin 1.4.5 , todas e o estas deniciones de segmentos y etiquetas son combinadas para formar un solo segmento de datos. El encadenador entonces calcula el desplazamiento denitivo. Se muestra una pequea seccin (l n o neas 54 a 56 del archivo fuente) del segmento de texto en el archivo de listado. 94 0000002C A1[00000000] 95 00000031 0305[04000000] 96 00000037 89C3 mov add mov eax, [input1] eax, [input2] ebx, eax

La tercera columna muestra el cdigo de mquina generado por el ensamo a blador. A menudo el cdigo completo de una instruccin no se puede calcular o o an. Por ejemplo, en la l u nea 94 el desplazamiento (o direccin) de input1 o no se conoce hasta que el cdigo se encadene. El ensamblador puede calcuo lar el cdigo de la instruccin mov (que del listado es A1), pero escribe el o o desplazamiento en parntesis cuadrados porque el valor exacto no se puede e calcular en este momento. En este caso se utiliza un desplazamiento temporal de 0 porque input1 est al inicio de la parte del segmento bss denido a en este archivo. Recuerde que esto no signica que estar al comienzo del a segmento bss denitivo del programa. Cuando el cdigo es encadenado, el o

26

CAP ITULO 1. INTRODUCCION

encadenador insertar el desplazamiento en la posicin correcta. Otras ina o strucciones como la l nea 96 no hacen referencia a ninguna etiqueta. Ac el a ensamblador puede calcular el cdigo de mquina completo. o a Representaciones Big y Little Endian Si uno mira de cerca en la l nea 95 hay algo muy extrao sobre el den splazamiento en los parntesis cuadrados del cdigo de mquina. La etiqueta e o a input2 tiene un desplazamiento de 4 (como est denido en este archivo); sin a embargo, el desplazamiento que aparece en la memoria no es 0000004, pero 04000000. Por qu? Diferentes procesadores almacenan enteros de varios e bytes en ordenes diferentes en la memoria. Existen dos mtodos populares e Endian se pronuncia como de almacenar enteros: big endian y littel endian. Big endian es el mtodo que e indian. se ve ms natural. El byte mayor (ms signicativo) se almacena primero, y a a luego los siguientes. Por ejemplo, la palabra doble 00000004 se deber almaa cenar como los cuatro bytes 00 00 00 04. Los mainframes IBM, la mayor de a los procesadores RISC y los procesadores Motorola todos ellos usan el mtoe do de Big endian. Sin embargo los procesadores Intel usn el mtodo little a e endian. Ac se almacena primero el byte menos signicativo. As 00000004 a se almacenar en memoria como 04 00 00 00. Este formato est cableado en a a la CPU y no se puede cambiar. Normalmente el programador no necesita preocuparse sobre que formato est usando. Sin embargo hay circunstancias a donde esto es importante. 1. Cuando un dato binario es transere entre computadores diferentes (o de archivos o a travs de una red) e 2. Cuando un dato binario es escrito fuera de la memoria como un entero multibyte y luego se vuelve a leer como bytes individuales o vice versa Lo Endian no se aplica al orden de los elementos de un arreglo. El primer elemento de un arreglo est siempre en la direccin menor. Esto se aplica a a o cadenas (que slo son arreglos de caracteres). Lo Endian slo se aplica a los o o elementos idividuales de un arreglo.

1.5.

Archivo esqueleto

La Figura 1.7 muestra un archivo esqueleto que se puede usar como punto de partida para escribir programas en ensamblador

1.5. ARCHIVO ESQUELETO

27

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

skel.asm %include "asm_io.inc" segment .data ; ; los datos iniciados se colocan en el segmento de ; datos ac a ; segment .bss ; ; Datos no iniciados se colocan en el segmento bss ; segment .text global _asm_main _asm_main: enter 0,0 ; rutina de pusha ; ; El cdigo est colocado en el segmento de texto. No modifique el o a ; cdigo antes o despus de este comentario o e ; popa mov eax, 0 ; retornar a C leave ret skel.asm Figura 1.7: Programa esqueleto

28

CAP ITULO 1. INTRODUCCION

Cap tulo 2

Lenguaje ensamblador bsico a


2.1.
2.1.1.

Trabajando con enteros


Representacin de enteros o

Hay dos tipos de enteros: sin signo y con signo. Los enteros sin signo (que son no negativos) estn representados de una manera muy sencilla en a binario. El nmero 200 como un byte entero sin signo ser representado u a como 11001000 (o C8 en hex). Los enteros con signo (que pueden ser positivos o negativos) se representan de modo ms complejo. Por ejemplo considere 56. +56 como byte a ser representado por 00111000. En el papel uno podr representar 56 a a como 111000, pero cmo podr representarse en un byte en la memoria o a del computador? Cmo se almacenar el signo? o a Hay 3 tcnicas que se han usado para representar nmeros enteros en la e u memoria del computador. Todos estos mtodos usan el bit ms signicativo e a como un bit de signo. Este bit es 0 si el nmero es positivo y 1 si es negativo. u Magnitud y signo El primer mtodo es el ms elemental y es llamado magnitud y signo. e a Representa los enteros como dos partes. La primera es el bit de signo y la segunda es la magnitud del entero. As 56 ser representado como el byte a 00111000 (el bit de signo est subrayado) y 56 ser 10111000. El mayor a a valor de un byte ser 01111111 o +127 y el menor valor ser 11111111 o a a 127. Para negar un valor se cambia el bit del signo. Este mtodo es sencillo, e pero tiene sus inconvenientes. Primero hay dos valores posibles de cero +0 (00000000) y 0 (10000000). Ya que cero no es ni positivo ni negativo, las dos representaciones podr servir igual. Esto complica la lgica de la aritmtica an o e para la CPU. Segundo, la aritmtica general tambin es complicada. Si se e e 29

30

CAP ITULO 2. LENGUAJE ENSAMBLADOR BASICO

aade 10 a 56, ste debe transformarse en la resta de 10 y 56. Una vez n e ms, esto complica la lgica de la CPU. a o Complemento a uno El segundo mtodo es conocido como complemento a uno. El complemene to a uno de un nmero se encuentra invirtiendo cada bit en el nmero. (Otra u u manera de ver esto es que el nuevo valor del bit es 1 elvalorantiguodelbit). Por ejemplo el complemento a uno de 00111000 (+56) es 11000111. En la notacin de complemento a uno calcular el complemento a uno es equivao lente a la negacin. As 11000111 es la representacin de 56. Observe que o o el bit de signo fue cambiado automticamente por el complemento a uno y a que como se esperar al aplicar el complemento a 1 dos veces produce el a nmero original. Como el primer mtodo hay dos representaciones del cero u e e u 00000000 (+0) y 11111111 (0). La aritmtica con nmeros en complemento a uno es complicada. Hay un truco util para encontrar el complemento a 1 de un nmero en u hexadecimal sin convertirlo a binario. El truco es restar el nmero hexadecu imal de F (o 15 en decimal) . Este mtodo supone que el nmero de d e u gitos binarios en el nmero es un mltiplo de 4. Un ejemplo: +56 se representa u u por 38 en hex. Para encontrar el complemento a 1 reste F de cada d gito para obtener C7 en hexadecimal. Esto es coherente con el resultado anterior. Complemento a dos Los dos primeros mtodos descritos fueron usados en los primeros come putadores. Los computadores modernos usan un tercer mtodo llamado la e representacin en complemento a 2. El complemento a 2 de un nmero se o u halla con los dos pasos siguientes: 1. Hallar el complemento a uno del nmero. u 2. Sumar uno al resultado del paso 1. Ac est un ejemplo usando 00111000 (56). Primero se calcula el complea a mento a uno: 11000111. Entonces se aade uno: n 11000111 1 11001000

En la notacin de complemento a dos, calcular el complemento a dos es o equivalente a negar el nmero. As 11001000 es la representacin en compleu o mento a dos de 56. Dos negaciones deber reproducir el nmero original. an u

2.1. TRABAJANDO CON ENTEROS Number 0 1 127 -128 -127 -2 -1 Hex Representation 00 01 7F 80 81 FE FF

31

Cuadro 2.1: Representacin de complemento a dos o Sorprendentemente el complemento a dos no rene este requisito. Tome el u complemento a dos de 11001000 aadiendo uno al complemento a uno. n 00110111 + 1 00111000 Cuando realizamos la suma en una operacin en complemento a dos, la o suma del bit del extremo izquierdo puede producir un carry. Este carry no se usa. Recuerde que todos los datos en el computador son de un tamao n jo (en trminos de nmeros de bits). Sumar 2 bytes siempre produce como e u resultado un byte (tal como sumar 2 palabras produce otra palabra, etc.). Esta propiedad es importante para la notacin en complemento a dos. Por o ejemplo, considere el cero como un nmero en complemento a dos de un u byte (00000000). Calcular el complemento a 2 produce la suma: 11111111 + 1 c 00000000 donde c representa un carry (luego se mostrar como detectar este carry, a pero no se almacena en el resultado). As en la notacin de complemento a o dos existe solo un cero. Esto hace la aritmtica de complemento a dos ms e a simple que los mtodos anteriores. e Usando la notacin en complemento a dos un byte con signo se puede usar o para representar los nmeros desde 128 hasta +127. La Tabla 2.1 muestra u algunos valores seleccionados. Si se usan 16 bits, se pueden representar los nmeros con signo desde 32,768 hasta 32,767 que est representado por u a 7FFF, 32,768 por 8000, 128 como FF80 y -1 como FFFF. Los nmeros u de 32 bits en complemento a dos estn en el rango de 2 mil millones a +2 a mil millones aproximadamente. La CPU no tiene ni idea que supuesta representacin tiene un byte en o particular (palabra, o palabra doble). El ensamblador no tiene ni idea de los

32

CAP ITULO 2. LENGUAJE ENSAMBLADOR BASICO

tipos de datos que tienen los lenguajes de alto nivel. Cmo se interpretan o los datos depende de qu instruccin se usa con el dato. Si el valor FF e o es considerado para representar 1 o +255 depende del programador. El lenguaje C dene tipos de entero con y sin signo (Signed, unisigned). Esto le permite al compilador determinar las instrucciones correctas a usar con el dato.

2.1.2.

Extensin del signo o

En ensamblador, todos los datos tienen un tamao determinado. No es n raro necesitar cambiar el tamao del dato para usarlo con otro dato. Reducir n el tamao es fcil. n a Reduciendo el tama o de los datos n Para reducir el tamao del dato simplemente quite los bits ms signin a cativos del dato. Un ejemplo trivial: mov mov ax, 0034h cl, al ; ax = 52 (almacenado en16 bits) ; cl = los 8-bits inferiores de ax

Claro est, si el nmero no se puede representar correctamente en el a u tamao ms pequeo la reduccin de tamao no funcionar. Por ejemplo n a n o n a si AX era 0134h (o 308 en decimal) entonces el cdigo anterior almacenar o a en CL 34h. Este mtodo trabaja con nmeros con o sin signo. Considere e u nmeros con signo, si AX era FFFFh (-1 como palabra), entonces code CL u ser FFh (1 como byte). Sin embargo, observe que esto no es correcto si a el valor en AX era sin signo! La regla para nmeros sin signo es que todos los bits al ser quitados u deben ser 0 para que la conversin sea correcta. La regla para los nmeros o u con signo es que los bits que sean quitados deben ser o todos 1o todos 0. Adems el primer bit no se debe quitar pero debe tener el mismo valor que a los bits quitados. Este bit ser el nuevo bit de signos del valor ms pequeo. a a n Es importante que sea el bit del signo original. Aumentando el tama o de los datos n Incrementar el tamao de los datos es ms complicado que disminuirlo. n a Considere el byte hex FF. Si se extiende a una palabra, Qu valor deber e a tener la palabra? Depende de cmo se interprete la palabra. Si FF es un o byte sin signo (255 en decimal), entonces la palabra deber ser 00FF; sin a embargo, si es un byte con signo (1 en decimal), entonces la palabra deber a ser FFFF.

2.1. TRABAJANDO CON ENTEROS

33

En general, para extender un nmero sin signo, uno hace cero todos u los bits nuevos del nmero extendido. As FF se convierte en 00FF. Sin u embargo, para extender un nmero con signo uno debe extender el bit de u signo. Esto signica que los nuevos bits se convierten en copias del bit de signo. Ya que el bit de signo de FF es 1, los nuevos bits deben ser todos unos, para producir FFFF. Si el nmero con signo 5A (90 en decimal) fue u extendido, el resultado ser 005A. a Existen varias instrucciones que suministra el 80386 para la extensin de o los nmeros. Recuerde que el computador no conoce si un nmero est con o u u a sin signo. Es responsabilidad del programador usar la instruccin adecuada. o Para nmeros sin signo, uno puede simplemente colocar ceros en los bits u superiores usando una instruccin MOV. Por ejemplo, para extender el byte o en AL a una palabra sin signo en AX: mov ah, 0 ; cero los 8-bits superiores

Sin embargo, no es posible usar la instruccin MOV para convertir la palabra o sin signo en AX a una palabra doble en EAX. Por qu no? No hay manera e de referirse a los 16 bits superiores de EAX con una instruccin MOV. El 80386 o resuelve este problema con una nueva instruccin MOVZX . Esta instruccin o o tiene dos operandos. El destino (primer operando) debe ser un registro de 16 o 32 bits. La fuente (segundo operando) puede ser un registro de 8 o 16 bits o un byte o una palabra en memoria. La otra restriccin es que el destino o debe ser mayor que la fuente (la mayor de instrucciones requieren que la a fuente y el destino sean del mismo tamao). Algunos ejemplos: n movzx movzx movzx movzx eax, ax eax, al ax, al ebx, ax ; ; ; ; extiende extiende extiende extiende ax al al ax en en en en eax eax ax ebx

Para nmeros con signo, no hay una manera fcil de usar la instruccin u a o MOV. EL 8086 suministra varias instrucciones para extender nmeros con u signo. La instruccin CBW (Convert Byte to Word) extiende el registro AL o en AX. Los operandos son impl citos. La instruccin CWD (Convert Word o to Double Word) extiende AX en DX:AX. La notacin DX:AX signica ino terpretar los registros DX y AX como un registro de 32 bits con los 16 bits superiores almacenados en DX y los 16 bits inferiores en AX. (Recuerde que el 8086 no ten ningn registro de 32 bits). El 80386 aadi varias a u n o instrucciones nuevas. La instruccin CWDE (Convert Word to Double word o Extended) extiende AX en EAX. La instruccin CDQ (Convert Double word o to Quad word) extiende EAX en EDX:EAX (64 bits!). Finalmente, la instruccin MOVSX trabaja como MOVZX excepto que usa las reglas para nmeros o u con signo.

34

CAP ITULO 2. LENGUAJE ENSAMBLADOR BASICO

unsigned char uchar = 0xFF; signed char schar = 0xFF; int a = (int ) uchar ; / a = 255 (0x000000FF) / int b = (int ) schar ; / b = 1 (0xFFFFFFFF) / Figura 2.1: char ch; while( (ch = fgetc(fp )) != EOF ) { / hace algo con ch / } Figura 2.2:

Aplicacin a la programacin en C o o
ANSI C no dene si el tipo char es con signo o no, es responsabilidad de cada compilador decidir esto. Esta es la razn por la cual o el tipo est expl a citamente denido en la Figura 2.1.

Extender enteros con y sin signo tambin ocurre en C. Las variables en C e se pueden declarar como int signed o unsigned (int es signed) . Considere el cdigo de la Figura 2.1. En la l o nea 3, la variable code a se extiendo usando las reglas para valores sin signo (usando MOVZX), pero en la l nea 4 se usan las reglas con signo para b (usando MOVSX). Hay un error muy comn en la programacin en C que tiene que ver u o con esto directamente. Considere el cdigo de la Figura 2.2. El prototipo de o fgetc( ) es: fgetc()is: int fgetc( FILE * ); Uno podr preguntar Por qu la funcin retorna un int siendo que lee a e o caracteres? La razn es que normalmente retorna un char (extendido a un o valor entero usando la extensin cero). Sin embargo hay un valor que puede o retornar que no es un carcter, EOF. Este es un macro que normalmente se a dene como 1. As fgetc() o retorna un carcter extendido a entero (que a es como 000000xx en hex) o EOF (que es FFFFFFF en hex). El problema principal con el programa de la Figura 2.2 es que fgetc() retorna un entero, pero este valor se almacena en un char. C truncar los a bits de mayor peso para que el entero quepa en el caracter. El unico problema es que los nmeros (en hex) 000000FF y FFFFFFFF ambos se truncarn al u a byte FF. As el ciclo while no puede distinguir entre el byte FF y el n de archivo (EOF).

2.1. TRABAJANDO CON ENTEROS

35

Lo que sucede exactamente en este caso, depende de si el char es con signo o sin signo por qu? Porque en la l e nea 2 ch es comparada con EOF. Ya que EOF es un valor int. 1 ch ser extendido a un int de modo que los a dos valores comparados sean del mismo tamao. 2 como se muestra en la n Figura 2.1, donde si la variable es con signo o sin signo es muy importante. Si char es unsigned, FF se extender a 000000FF. Esto se compara con a EOF (FFFFFFFF) y encontrar que no es igual. As el bucle nunca terminar!. a , a Si char es con signo se extender a FFFFFFFF. Esto se compara como a igual y el bucle naliza. Sin embargo, ya que el byte FF puede haber sido le de un archivo, el bucle podr terminar prematuramente. do a La solucin a este problema es denir la variable ch como un int no o como un char. Cuando esto se hace no se truncar o extender en la l a a nea 2. Dentro del bucle es seguro truncar el valor ya que ah ch debe ser un simple byte.

2.1.3.

Aritmtica de complemento a dos e

Como se vio al principio, la instruccin add efecta una suma y la ino u struccin sub efecta una resta. Dos de los bits en el registro FLAGS, que o u estas instrucciones jan son las banderas de desborde y carry. La bandera de desborde se ja si el resultado verdadero de la operacin es muy grande o para caber en el destino para aritmtica con signo. La bandera de carry se e ja si hay carry en el bit ms signicativo de una suma o un prstamo en a e el bit ms signicativo de una resta. As l se puede usar para detectar un a , e desborde para aritmtica sin signo. Los usos de la bandera de carry para e aritmtica con signo se vern dentro de poco. Una de las grandes ventajas e a del complemento a 2 es que las reglas para la adicin y sustraccin son exaco o tamente las mismas que para los enteros sin signo. As add y sub se pueden usar con enteros con o sin signo. 002C + FFFF 002B 44 + (1) 43

Hay un carry generado, pero no es parte de la respuesta.


Es un concepto errneo pensar que los archivos tienen un carcter EOF al nal. Esto o a no es verdad! 2 La razn para este requerimiento se mostrar luego. o a
1

36 dest

CAP ITULO 2. LENGUAJE ENSAMBLADOR BASICO source1 reg/mem8 reg/mem16 reg/mem32 reg/mem16 reg/mem32 immed8 immed8 immed16 immed32 reg/mem16 reg/mem32 reg/mem16 reg/mem32 source2 Action AX = AL*source1 DX:AX = AX*source1 EDX:EAX = EAX*source1 dest *= source1 dest *= source1 dest *= immed8 dest *= immed8 dest *= immed16 dest *= immed32 dest = source1*source2 dest = source1*source2 dest = source1*source2 dest = source1*source2

reg16 reg32 reg16 reg32 reg16 reg32 reg16 reg32 reg16 reg32

immed8 immed8 immed16 immed32

Cuadro 2.2: instrucciones imul Hay dos instrucciones diferentes para multiplicar y dividir. Primero para multiplicar use la instruccin MUL o IMUL. La instruccin MUL se emplea para o o multiplicar nmeros sin signo e IMUL se usa para multiplicar enteros con u signo. Por qu se necesitan dos instrucciones diferentes? Las reglas para la e multiplicacin son diferentes para nmeros en complemento a dos con signo o u o sin signo. Cmo as Considere la multiplicacin del byte FF con s mismo o ? o dando como resultado una palabra. Usando una multiplicacin sin signo es o 255 veces 255 o 65025 (o FE01 en hex). Usando la multiplicacin con signo o es 1 veces 1 (o 0001 en hex). Hay varias formas de las instrucciones de multiplicacin. La ms antigua o a es: mul source

source es un registro o una referencia a memoria. No puede ser un valor inmediato. Exactamente qu multiplicacin se realiza depende del tamao e o n del operando source. Si el operando es de un byte, ste es multiplicado por e el byte del registro AL y el resultado se almacena en los 16 bits de AX. Si la fuente es de 16 bits, se multiplica por la palabra en AX y el resultado de 32 bits se almacena en DX:AX. Si la fuente es de 32 bits ste se multiplica e por EAX y el resultado de 64 bits se almacena en EDX:EAX. La instruccin IMUL tiene la misma forma de MUL, pero tambin tiene o e algunos otros formatos. Hay dos y tres formas de operandos. imul imul dest, source1 dest, source1, source2

La Tabla 2.2 muestra las posibles combinaciones.

2.1. TRABAJANDO CON ENTEROS

37

Los dos operadores para la divisin son DIV e IDIV. Ellas efectan la o u divisin sin signo y con signo respectivamente. El formato general es: o

div

source

Si la fuente es de 8 bits, entonces AX es dividido por el operando. El cociente se almacena en AL y el residuo en ah. Si la fuente es de 16 bits, entonces DX:AX se divide por el operando. El cociente se almacena en AX y el residuo en DX. Si la fuente es de 32 bits, entonces EDX:EAX se divide por el operando y el cociente se almacena en EAX y el residuo en EDX. La instruccin IDIV trabaja de la misma manera. No hay instrucciones especiales o IDIV como las especiales en IMUL. Si el cociente es muy grande para caber en el registro o el divisor es cero, el programa se interrumpe y termina. Un error muy comn es olvidar iniciar DX o EDX antes de la divisin. u o La instruccin NEG niega su operando calculando su complemento a dos. o El operando puede ser cualquier registro de 8, 16 o 32 bits o un lugar de memoria.

2.1.4.

Programa de ejemplo

math.asm

38
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49

CAP ITULO 2. LENGUAJE ENSAMBLADOR BASICO

%include "asm_io.inc" segment .data prompt db square_msg db cube_msg db cube25_msg db quot_msg db rem_msg db neg_msg db segment .bss input resd 1 segment .text global _asm_main: enter pusha mov call call mov imul mov mov call mov call call mov imul mov call mov call call imul mov call mov call call mov

; Cadenas de salida "Digite un nmero: ", 0 u "La entrada al cuadrado es ", 0 "La entrada al cubo es ", 0 "La entrada al cubo 25 veces es ", 0 "El cociente del cubo/100 es ", 0 "El residuo del cube/100 es ", 0 "La negacin del residuo es ", 0 o

_asm_main 0,0 ; rutina de inicio

eax, prompt print_string read_int [input], eax eax ebx, eax eax, square_msg print_string eax, ebx print_int print_nl ebx, eax ebx, [input] eax, cube_msg print_string eax, ebx print_int print_nl ecx, ebx, 25 eax, cube25_msg print_string eax, ecx print_int print_nl eax, ebx ; edx:eax = eax * eax ; guarda la respuesta en

ebx

; ebx *= [input]

; ecx = ebx*25

2.2. ESTRUCTURAS DE CONTROL math.asm

39

2.1.5.

Aritmtica de precisin extendida e o

El lenguaje ensamblador tambin suministra instrucciones que le permie tan a uno hacer operaciones de suma y resta de nmeros ms grandes que u a palabras dobles. Como se vio antes las instrucciones ADD y SUB modican la bandera de carry si se ha generado un carry o un prstamo respectivamente. e Esta informacin almacenada en la bandera de carry se puede usar para o sumar o restar nmeros grandes dividiendo la operacin en piezas pequeas u o n de palabras dobles (o menores). Las instrucciones ADC y SBB usan esta informacin en la bandera de carry. o La instruccin ADC hace la siguiente operacin: o o operando1 = operando1 + bandera de carry + operando2 La instruccin SBB realiza: o operando1 = operando1 - bandera de flag - operando2 Cmo se usan? Considere la suma de enteros de 64 bits en EDX:EAX y o EBX:ECX . El siguiente cdigo pod almacenar la suma en EDX:EAX o a
1 2

add adc sub sbb

eax, ecx edx, ebx eax, ecx edx, ebx

; suma los 32-bits inferiores ; suma los 32-bits superiores y el carry de la suma anterior ; resta los 32-bits inferiores ; resta los 32-bits superiores y el prstamo e

La resta es muy similar. El siguiente cdigo resta EBX:ECX de EDX:EAX o


1 2

Para nmeros realmente grandes, se puede usar un bucle (vea seccin u o 2.2). Para el bucle suma ser conveniente usar la instruccin ADC para a o cada iteracin (en todas menos la primera). Esto se puede hacer usando la o instruccin CLC (CLear Carry) antes que comience el bucle para iniciar la o bandera de carry a cero. Si la bandera de carry es cero no hay diferencia entre ADD y ADC. La misma idea se puede usar tambin para la resta. e

2.2.

Estructuras de control

Los lenguajes de alto nivel suministran estructuras del alto nivel (instrucciones if while), que controlan el ujo de ejecucin. El lenguaje ensamblador o no suministra estas complejas estructuras de control. En lugar de ello usa el difamado goto y su uso inapropiado puede resultar en un cdigo spaghetTi! o Sin embargo es posible escribir programas estructurados en ensamblador. El procedimiento bsico es disear la lgica del programa usando las estruca n o turas de control de alto nivel y traducir el diseo en lenguaje ensamblador n apropiado (parecido a lo que har el compilador). a

40

CAP ITULO 2. LENGUAJE ENSAMBLADOR BASICO

2.2.1.

Comparaciones

Las estructuras de control deciden que hacer basados en la comparacin o de datos. En ensamblador, el resultado de una comparacin se almacenan o en el registro FLAGS para usarlas luego. El 80x86 suministra la instruccin o CMP para realizar comparaciones. El registro FLAGS se ja basado en la diferencia de los dos operandos de la instruccin CMP. Los operandos se o restan y se ja el registro FLAGS basado en el resultado, pero el resultado no se almacena en ninguna parte. Si necesita el resultado use la instruccin o SUB en lugar de la instruccin CMP. Para enteros sin signos hay dos banderas o (bits en el registro FLAGS) que son importante: cero (ZF) y carry (CF). La bandera cero se ja si el resultado de la resta es cero. La bandera carry se usa como bandera de prstamo para la resta. Considere una comparacin e o como: cmp vleft, vright

Por qu hace SF = OF e si vleft > vright? Si no hay desborde, entonces la diferencia tendr el valor a correcto y debe ser no negativo. As SF = OF = 0. , Sin embargo, si hay un desborde, la diferencia no tendr el valor correcto (y a de hecho ser negativo). a As SF = OF = 1.

La resta de vleft - vright se calcula y las banderas se jan de acuerdo al resultado. Si la diferencia de CMP es cero, vleft = vright, entonces ZF se ja (ZF=1) y CF se borra (CF=0). Si vleft > vright, entonces ZF se borra y CF tambin (no hay prstamo). Si vleft < vright, entonces ZF e e se borrar y CF se jar (hay prstamo). a a e Para enteros con signo, hay tres banderas que son importante: la bandera cero (ZF), la bandera de desborde (OF) y la bandera de signo (SF). La bandera de desborde se ja si el resultado de una operacin se desborda (o o underows). La bandera de signo se ja si el resultado de una operacin es o negativo. Si vleft = vright, la ZF se ja ( tal como para los enteros sin signo). Si vleft > vright, ZF es cero y SF = OF. Si vleft < vright, ZF es cero y SF = OF. No olvide que otras instrucciones tambin cambian el registro FLAGS, e no solo CMP.

2.2.2.

Instrucciones de ramicacin o

Las instrucciones de ramicacin pueden transferir la ejecucin de un o o programa a un punto arbitrario. En otras palabras funcionan como goto. Hay dos tipos de ramicaciones: condicionales e incondicionales. Una ramicacin incondicional es tal cual como goto, siempre hace el salto. Una o ramicacin condicional puede o no hacer el salto dependiendo de las bano deras del registro FLAGS. Si una ramicacin condicional no hace el salto, o el control pasa a la siguiente instruccin. o La instruccin JMP (acrnimo de jump) hace ramicaciones incondio o cionales. Su argumento normalmente es una etiqueta de cdigo a la instruco cin a la cual se debe saltar. El ensamblador o el encadenador reemplazar la o a

2.2. ESTRUCTURAS DE CONTROL

41

etiqueta con la direccin correcta de la instruccin. Esta es otra de las labores o o aburridas que realiza el ensamblador para hacer la vida del programador ms a fcil. Es importante tener en cuenta que la instruccin inmediatamente dea o spus de la instruccin JMP nunca se ejecutar a menos que otra instruccin e o a o salte a ella. Hay variaciones de la instruccin de salto. o SHORT Este salto es de tamao muy limitado, solo se puede mover arn riba o abajo 128 bytes en memoria. La ventaja de este tipo es que usa menos memoria que otros. Usa un byte con signo para almacenar el desplazamiento del salto. El desplazamiento es cuntos bytes a se mueve adelante o atrs. (El desplazamiento se aade a EIP). Para a n especicar un salto corto, use la palabra SHORT inmediatamente antes de la etiqueta en la instruccin JMP. o NEAR Este salto es el tipo por defecto en las ramicaciones condicionales e incondicionales y se puede usar para saltar a cualquier lugar del segmento. Actualmente el 80386 soporta 2 tipos de saltos cercanos. Uno usa dos bytes para el desplazamiento. Esto le permite a uno moverse aproximadamente 32000 bytes arriba o abajo. El otro tipo usa cuatro bytes para el desplazamiento, que le permite a uno moverse a cualquier lugar en el segmento de cdigo. El tipo de 4 bytes es el de defecto en o el modo protegido del 386. El tipo de 2 bytes se puede especicar colocando la palabra WORD antes de la etiqueta en la instruccin JMP. o FAR Este salto permite mover el control a otro segmento de cdigo. Este o es una cosa muy rara para hacerla en el modo protegido del 386. Las etiquetas de cdigo vlidas siguen las mismas reglas que las etiqueo a tas de datos. Las etiquetas de cdigo estn denidas para colocarlas en el o a segmento de cdigo al frente de la instruccin sus etiquetas. Dos puntos se o o colocan al nal de la etiqueta en este punto de la denicin. Los dos puntos o no son parte del nombre. Hay muchas instrucciones de ramicacin condicional diferentes. Ellas o tambin toman la etiqueta como su operando. Las ms sencillas solo ven e a una bandera en el registro FLAGS para determinar si salta o no. Vea la Tabla 2.3 para una lista de estas instrucciones (PF es la bandera de paridad que indica si el nmero de unos en los 8 bit inferiores es par o impar). u El siguiente pseudocdigo: o if ( EAX == 0 ) EBX = 1; else EBX = 2;

42

CAP ITULO 2. LENGUAJE ENSAMBLADOR BASICO JZ JNZ JO JNO JS JNS JC JNC JP JNP salta salta salta salta salta salta salta salta salta salta solo solo solo solo solo solo solo solo solo solo si si si si si si si si si si ZF es uno ZF es cero OF es uno OF es cero SF es uno SF es cero CF es uno CF es cero PF es uno PF es cero

Cuadro 2.3: Saltos condicionales simples Se podr escribir en ensamblador como: a


1 2 3 4 5 6 7

cmp jz mov jmp thenblock: mov next:

eax, 0 thenblock ebx, 2 next ebx, 1

; ; ; ;

establece las banderas (ZF se fija si si ZF es 1 salta a thenblock parte ELSE del if salta sobre la parte THEN del IF

eax - 0 = 0)

; parte THEN del IF

Las otras comparaciones no son fciles usando las ramicaciones condia cionales de la Tabla 2.3 Para ilustrar esto considere el siguiente pseudocdio go: if ( EAX >= 5 ) EBX = 1; else EBX = 2; Si EAX es mayor que o igual a 5, ZF debe estar ja o borrada y SF ser igual a a OF. Aqu est el cdigo en ensamblador que prueba estas condiciones a o (asumiendo que EAX es con signo):
1 2 3 4 5 6 7 8

cmp js jo jmp signon: jo elseblock: mov

eax, 5 signon elseblock thenblock thenblock ebx, 2

; salta a signon si SF = 1 ; salta a elseblock si OF = 1 y SF = 0 ; salta a thenblock si SF = 0 y OF = 0 ; salta a thenblock si SF = 1 y OF = 1

2.2. ESTRUCTURAS DE CONTROL Signed salta si vleft salta si vleft salta si vleft salta si vleft salta si vleft salta si vleft Unsigned salta si vleft salta si vleft salta si vleft salta si vleft salta si vleft salta si vleft

43

JE JNE JL, JNGE JLE, JNG JG, JNLE JGE, JNL

= vright = vright <vright vright >vright vright

JE JNE JB, JNAE JBE, JNA JA, JNBE JAE, JNB

= vright = vright <vright vright >vright vright

Cuadro 2.4: Instrucciones de comparacin con y sin signo o


9 10 11 12

jmp thenblock: mov next:

next ebx, 1

El cdigo anterior es muy complicado. Afortunadamente, el 80x86 sumo inistra otras instrucciones de ramicacin que hace este tipo de pruebas o mucho ms fcil. Hay versiones con y sin signo para cada tipo. La Tabla 2.4 a a muestra estas instrucciones. Las ramicaciones igual y no igual (JE y JNE) son idnticas para enteros con y sin signo. (De hecho JE y JNE son las mise mas que JZ y JNZ respectivamente). Cada una de las otras instrucciones de ramicacin tienen dos sinnimos. Por ejemplo mire que JL (jump less o o than) y JNGE (jump not greater than or equal to). Son la misma instruccin o porque: x < y = not(x y) Las ramicaciones sin signo usan A por above y B para below en lugar de L y G. Usando estas nuevas instrucciones de ramicacin, el pseudocdigo de o o arriba puede ser traducido a ensamblador mucho ms fcil. a a
1 2 3 4 5 6 7

cmp jge mov jmp thenblock: mov next:

eax, 5 thenblock ebx, 2 next ebx, 1

2.2.3.

Instrucciones de bucle

El 80x86 suministra varias instrucciones para implementar bucles del tipo for. Cada una de esas instrucciones tiene una etiqueta como unico operando.

44

CAP ITULO 2. LENGUAJE ENSAMBLADOR BASICO

LOOP Decrementa ECX, si ECX = 0, salta a la etiqueta LOOPE, LOOPZ Decrementa ECX (el registro FLAGS no se modica), si ECX = 0 y ZF = 1, salta LOOPNE, LOOPNZ Decrementa ECX (FLAGS sin cambio), si ECX = 0 y ZF = 0, salta Las dos ultimas instrucciones de bucle son utiles para bucles de bsqueda u secuencial. El siguiente pseudocdigo: o sum = 0; for ( i =10; i >0; i ) sum += i; Podr ser traducido a ensamblador como: a
1 2 3 4 5

mov mov loop_start: add loop

eax, 0 ecx, 10 eax, ecx loop_start

; eax es sum ; ecx es i

2.3.

Traducir estructuras de control estndares a

Esta seccin muestra cmo las estructuras de control estndares de los o o a lenguajes de alto nivel se pueden implementar en lenguaje ensamblador.

2.3.1.

instrucciones if

El siguiente pseudocdigo: o if ( condicin ) o bloque entonces; else bloque else ; podr implementarse como: a
1 2 3 4 5 6 7

; code to set FLAGS jxx else_block ; selecciona xx tal que salta si la condicin es falsa o ; cdigo para bloque entonces o jmp endif else_block: ; cdigo para bloque else o endif:

2.3. TRADUCIR ESTRUCTURAS DE CONTROL ESTANDARES

45

Si no hay else, entonces el else block ramicado puede ser reemplazado por una rama a endif. If there is no else, then the else block branch can be replaced by a branch to endif.
1 2 3 4

; code to set FLAGS jxx endif ; selecciona xx tal que salta si la condicin es falsa o ; cdigo para el bloque entonces o endif:

2.3.2.

bucles while

El bucle while se prueba al inicio del bucle: while( condicin ) { o cuerpo del bucle ; } Esto podr traducirse en: a
1 2 3 4 5 6

while: ; cdigo que fija FLAGS basado en la condicin de o o jxx endwhile ; selecciona xx tal que salte si xx es falso ; body of loop jmp while endwhile:

2.3.3.

bucles do while

El bucle do while se prueba al nal del bucle: do { cuerpo del bucle ; } while( condicin ); o Esto podr ser traducido en: a
1 2 3 4

do: ; cuerpo del bucle ; cdigo para fijar FLAGS basado en la condicin o o jxx do ; seleccionar xx tal que salte si es verdadero

46

CAP ITULO 2. LENGUAJE ENSAMBLADOR BASICO /

unsigned guess; / La conjetura actual para el primo unsigned factor ; / el posible factor / unsigned limit ; / encontrar primos hasta este valor / printf (Find primes up to : ); scanf( %u, &limit); printf (2\n); / trata los dos primeros primos / printf (3\n); / como caso especial / guess = 5; / conjetura inicial / while ( guess <= limit ) { / busca un factor / factor = 3; while ( factor factor < guess && guess % factor != 0 ) factor += 2; if ( guess % factor != 0 ) printf ( %d\n, guess); guess += 2; / slo busca en los unmeros impares / o } Figura 2.3:

2.4.

Ejemplo: hallar n meros primos u

Esta seccin trata de un programa que encuentra nmeros primos. Reo u cuerde que un nmero primo es divisible slo por 1 y por s mismo. No hay u o frmula para hacer esto. El mtodo bsico de este programa es encontrar los o e a factores de todos los nmeros impares 3 bajo un l u mite dado. Si no se puede encontrar un factor para un nmero impar, es primo La Figura 2.3 muestra u el algoritmo bsico escrito en C. a Ac est la versin en ensamblador a a o %include "asm_io.inc" segment .data Message db segment .bss Limit Guess
3

prime.asm "Halle primos hasta: ", 0

1 2 3 4 5 6 7 8

resd resd

1 1

; halle primos hasta este lmite ; la conjetura actual para el primo

2 es el unico nmero par. u

2.4. EJEMPLO: HALLAR NUMEROS PRIMOS


9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50

47

segment .text global _asm_main: enter pusha mov call call mov mov call call mov call call mov while_limit: mov cmp jnbe mov while_factor: mov mul jo cmp jnb mov mov div cmp je

_asm_main 0,0 ; rutina de inicio

eax, Message print_string read_int [Limit], eax eax, 2 print_int print_nl eax, 3 print_int print_nl dword [Guess], 5 eax,[Guess] eax, [Limit] end_while_limit ebx, 3 eax,ebx eax end_while_factor eax, [Guess] end_while_factor eax,[Guess] edx,0 ebx edx, 0 end_while_factor

; scanf("%u", & limit );

; printf("2\n");

; printf("3\n");

; Guess = 5; ; while ( Guess <= Limit )

; use jnbe ya que los nmeros son sin signo u ; ebx is factor = 3;

; edx:eax = eax*eax ; Si la respuesta no cabe en eax ; if !(factor*factor < guess)

; edx = edx:eax % ebx ; if !(guess % factor != 0) ; factor += 2;

add ebx,2 jmp while_factor end_while_factor: je end_if mov eax,[Guess]

; if !(guess % factor != 0) ; printf("%u\n")

48
51 52 53 54 55 56 57 58 59 60 61

CAP ITULO 2. LENGUAJE ENSAMBLADOR BASICO call call print_int print_nl ; guess += 2

end_if: add dword [Guess], 2 jmp while_limit end_while_limit: popa mov leave ret

eax, 0

; retorna a C prime.asm

Cap tulo 3

Operaciones con bits


3.1. Operaciones de desplazamientos

El lenguaje ensamblador le permite al programador manipular bits individuales de los datos. Una operacin comn es llamada un desplazamiento. o u Una operacin de desplazamiento mueve la posicin de los bits de algn o o u dato. Los desplazamientos pueden ser hacia la izquierda (hacia el bit ms a signicativo) o hacia la derecha (el bit menos signicativo).

3.1.1.

Desplazamientos lgicos o

Un desplazamiento lgico es el tipo ms simple de desplazamiento. Deo a splaza de una manera muy directa. La Figura 3.1 muestra un ejemplo del desplazamiento de un byte. Original Left shifted Right shifted 1 1 0 1 1 1 1 0 1 0 1 1 1 0 0 0 1 1 1 0 0 0 0 1

Figura 3.1: Desplazamientos lgicos o Observe que los nuevos bits son siempre cero. Se usan las instrucciones SHL y SHR para realizar los desplazamientos a la izquierda y derecha respectivamente. Estas instrucciones permiten desplazar cualquier nmero de u posiciones. El nmero de posiciones puede ser o una constante o puede estar u almacenado en el registro CL. El ultimo bit desplazado se almacena en la bandera de carry. A continuacin, algunos ejemplos: o
1 2 3 4

mov shl shr shr

ax, ax, ax, ax,

0C123H 1 1 1

; desplaza un bit a la izquierda, ax = 8246H, CF = 1 ; desplaza un bit a la derecha, ax = 4123H, CF = 0 ; desplaza un bit a la derecha, ax = 2091H, CF = 1 49

50
5 6 7 8

CAP ITULO 3. OPERACIONES CON BITS ax, ax, cl, ax, 0C123H 2 3 cl

mov shl mov shr

; desplaza dos bit a la izquierda,

ax = 048CH, CF = 1

; desplaza tres bit a la derecha, ax = 0091H, CF = 1

3.1.2.

Uso de los desplazamientos

Los usos ms comunes de las operaciones de desplazamientos son las a multiplicaciones y divisiones rpidas. Recuerde que en el sistema decimal la a multiplicacin y divisin por una potencia de 10 es sencilla, slo se desplazan o o o los d gitos. Lo mismo se aplica para las potencias de dos en binario. Por ejemplo para duplicar el nmero binario 10112 (o 11 en decimal), al desplazar u una vez a la izquierda obtenemos 101102 (o 22). El cociente de una divisin o por una potencia de dos es el resultado de un desplazamiento a la derecha. Para dividir por 2 solo haga un desplazamiento a la derecha; para dividir por 4 (22 ) desplace los bits 2 lugares; para dividir por 8 desplace 3 lugares a la derecha etc. Las instrucciones de desplazamiento son muy elementales y son mucho ms rpidas que las instrucciones correspondientes MUL y DIV . a a Actualmente, los desplazamientos lgicos se pueden usar para multiplicar o y dividir valores sin signo. Ellos no funcionan para valores con signo. Considere el valor bytes FFFF (1). Si ste se desplaza lgicamente a la derecha e o una vez el resultado es 7FFF que es +32,767. Se pueden usar otro tipo de desplazamientos para valores con signo.

3.1.3.

Desplazamientos aritmticos e

Estos desplazamientos estn diseados para permitir que nmeros con a n u signo se puedan multiplicar y dividir rpidamente por potencias de 2. Ellos a aseguran que el bit de signo se trate correctamente. SAL (Shift aritmetic left). Esta instruccin es solo sinnimo para SHL. Se o o ensambla con el mismo cdigo de mquina que SHL. Como el bit de o a signo no se cambia por el desplazamiento, el resultado ser correcto. a SAR SAR Shift Arithmetic Right - Esta es una instruccin nueva que no desplaza o el bit de signo (el bit ms signicativo) de su operando. Los otros bits a se desplazan como es normal excepto que los bits nuevos que entran por la derecha son copias del bit de signo (esto es, si el bit de signo es 1, los nuevos bits son tambin 1). As si un byte se desplaza con e , esta instruccin, slo los 7 bits inferiores se desplazan. Como las otras o o instrucciones de desplazamiento, el ultimo bit que sale se almacena en la bandera de carry.

3.1. OPERACIONES DE DESPLAZAMIENTOS


1 2 3 4

51

mov sal sal sar

ax, ax, ax, ax,

0C123H 1 1 2

; ax = 8246H, CF = 1 ; ax = 048CH, CF = 1 ; ax = 0123H, CF = 0

3.1.4.

Desplazamientos de rotacin o

Los desplazamientos de rotacin trabajan como los desplazamientos lgio o cos excepto que los bits perdidos en un extremo del dato se desplazan al otro lado. As el dato es tratado como si fuera una estructura circular. Las dos , rotaciones ms simples son ROL y ROR , que hacen rotaciones a la izquierda a y a la derecha respectivamente. Tal como los otros desplazamientos, estos desplazamientos dejan una copia del ultimo bit rotado en la bandera de carry.
1 2 3 4 5 6

mov rol rol rol ror ror

ax, ax, ax, ax, ax, ax,

0C123H 1 1 1 2 1

; ; ; ; ;

ax ax ax ax ax

= = = = =

8247H, 048FH, 091EH, 8247H, C123H,

CF CF CF CF CF

= = = = =

1 1 0 1 1

Hay dos instrucciones de rotacin adicionales que desplazan los bits en o el dato y la bandera de carry llamadas RCL y RCR . Por ejemplo, si el registro AX rota con estas instrucciones, los 17 bits se desplazan y la bandera de carry se rota.
1 2 3 4 5 6 7

mov clc rcl rcl rcl rcr rcr

ax, 0C123H ax, ax, ax, ax, ax, 1 1 1 2 1 ; ; ; ; ; ; borra la bandera ax = 8246H, CF = ax = 048DH, CF = ax = 091BH, CF = ax = 8246H, CF = ax = C123H, CF = de carry (CF = 0) 1 1 0 1 0

3.1.5.

Aplicacin simple o

A continuacin est un fragmento de cdigo que cuenta el nmero de o a o u bits que estn encendidos (1) en el registro EAX. a
1 2

mov mov

bl, 0 ecx, 32

; bl contendr el nmero de bits prendidos a u ; ecx es el bucle contador

52 X 0 0 1 1

CAP ITULO 3. OPERACIONES CON BITS Y 0 1 0 1 X AND Y 0 0 0 1

Cuadro 3.1: La operacin AND o 1 1 1 0 1 0 1 0 0 0 0 0 1 1 1 0 0 0 1 0 0 0 1 0

AND

Figura 3.2: ANDdo un byte


3 4 5 6 7 8

count_loop: shl jnc inc skip_inc: loop

eax, 1 skip_inc bl count_loop

; desplaza los bits en la bandera de carry ; si CF == 0, va a skip_inc

El cdigo anterior destruye el valor original de EAX (EAX es cero al nal o del bucle). Si uno desea conservar el valor de EAX, la l nea 4 deber ser a reemplazada con rol eax,1.

3.2.

Operaciones booleanas entre bits

Hay cuatro operadores booleanos bsicos AND, OR, XOR y NOT. Una a tabla de verdad muestra el resultado de cada operacin por cada posible o valor de los operandos.

3.2.1.

La operacin AND o

El resultado del AND de dos bits es 1 solo si ambos bits son 1, si no el resultado es cero como muestra la Tabla 3.1 Los procesadores tienen estas operaciones como instrucciones que actan u independientemente en todos los bits del dato en paralelo. Por ejemplo, si el contenido de AL y BL se les opera con AND, la operacin se aplica a cada uno o de los 8 pares de bits correspondientes en los dos registros como muestra la Figura 3.2 . Sigue un cdigo de ejemplo: o
1 2

mov and

ax, 0C123H ax, 82F6H

; ax = 8022H

3.2. OPERACIONES BOOLEANAS ENTRE BITS X 0 0 1 1 Y 0 1 0 1 X OR Y 0 1 1 1

53

Cuadro 3.2: La operacin OR o X 0 0 1 1 Y 0 1 0 1 X XOR Y 0 1 1 0

Cuadro 3.3: La operacin XOR o

3.2.2.

La operacin OR o

El O inclusivo entre dos bits es 0 solo si ambos bits son 0, si no el resultado es 1 como se muestra en la Tabla 3.2 . Sigue un cdigo de ejemplo: o
1 2

mov or

ax, 0C123H ax, 0E831H

; ax = E933H

3.2.3.

La operacin XOR o

El O exclusivo entre 2 bits es 0 si y solo si ambos bits son iguales, sino el resultado es 1 como muestra la tabla 3.3. Sigue un cdigo de ejemplo: o
1 2

mov xor

ax, 0C123H ax, 0E831H

; ax = 2912H

3.2.4.

La operacin NOT o

La operacin NOT es unaria (acta sobre un solo operando, no como o u las operaciones binarias como AND). El NOT de un bit es el valor opuesto del bit como se muestra en la Tabla 3.4. Sigue un cdigo de ejemplo: o
1 2

mov not

ax, 0C123H ax

; ax = 3EDCH

Observe que NOT halla el complemento a 1. Diferente a las otras operaciones entre bits, la instruccin NOT no cambian ninguno de los bits en el o registro FLAGS.

54

CAP ITULO 3. OPERACIONES CON BITS X 0 1 NOT X 1 0

Cuadro 3.4: La operacin NOT o Prende el bit i Apaga el bit i OR the number with 2i (which is the binary number with just bit i on) AND el nmero binario con slo el bit i apau o gado . Este operando es a menudo llamado mscara a XOR el nmero con 2i u

Complementa el bit i

Cuadro 3.5: Usos de las operaciones booleanas

3.2.5.

La instruccin TEST o

La instruccin TEST realiza una operacin AND, pero no almacena el o o resultado. Solo ja las banderas del registro FLAGS dependiendo del resultado que fuera (muy parecido a lo que hace la instruccin CMP con la resta que o solo ja las banderas). Por ejemplo, si el resultado fuera cero, ZF se jar a.

3.2.6.

Usos de las operaciones con bits

Las operaciones con bits son muy utiles para manipular bits individuales sin modicar los otros bits. La Tabla 3.5 muestra los 3 usos ms comunes a de estas operaciones. Sigue un ejemplo de cmo implementar estas ideas. o
1 2 3 4 5 6 7 8

mov or and xor or and xor xor

ax, ax, ax, ax, ax, ax, ax, ax,

0C123H 8 0FFDFH 8000H 0F00H 0FFF0H 0F00FH 0FFFFH

; ; ; ; ; ; ;

prende el bit 3, ax = C12BH apaga el bit 5, ax = C10BH invierte el bit 31, ax = 410BH prende el nibble, ax = 4F0BH apaga nibble, ax = 4F00H invierte nibbles, ax = BF0FH complemento a uno , ax = 40F0H

La operacin AND se puede usar tambin para hallar el residuo de una o e divisin por una potencia de dos. Para encontrar el residuo de una divisin o o i , AND el n mero con una mscara igual a 2i 1. Esta mscara conpor 2 u a a tendr unos desde el bit 0 hasta el bit i 1. Son solo estos bits los que a contienen el residuo. El resultado de la AND conservar estos bits y dea jar cero los otros. A continuacin un fragmento de cdigo que encuentra el a o o cociente y el residuo de la divisin de 100 por 16. o

3.3. EVITANDO SALTOS CONDICIONALES


1 2 3 4 5 6

55

mov mov count_loop: shl adc loop

bl, 0 ecx, 32 eax, 1 bl, 0 count_loop

; bl contendr el nmero de bits prendidos a u ; ecx es el bucle contador ; se desplaza el bit en la bandera de carry ; a~ade solo la bandera de carry a bl n

Figura 3.3: Contando bits con ADC


1 2 3

mov mov and

eax, 100 ebx, 0000000FH ebx, eax

; 100 = 64H ; mcara = 16 - 1 = 15 or F a ; ebx = residuo = 4

Usando el registro CL es posible modicar arbitrariamente bits. El siguiente es un ejemplo que ja (prende) un bit arbitrario en EAX. El nmero del bit u a pender se almacena en BH.
1 2 3 4

mov mov shl or

cl, bh ebx, 1 ebx, cl eax, ebx

; first build the number to OR with ; se desplaza a la derecha cl veces ; prende el bit

Apagar un bit es solo un poco ms dif a cil.


1 2 3 4 5

mov mov shl not and

cl, bh ebx, 1 ebx, cl ebx eax, ebx

; first build the number to AND with ; se desplaza a la derecha cl veces ; invierte los bits ; apaga el bit

El cdigo para complementar un bit arbitrario es dejado como ejercicio al o lector. Es comn ver esta instruccin en un programa 80X86. u o xor eax, eax ; eax = 0

A number XORed with itself always results in zero. This instruction is used because its machine code is smaller than the corresponding MOV instruction.

3.3.

Evitando saltos condicionales

Los procesadores modernos usan tcnicas sosticadas para ejecutar el e cdigo tan rpido como sea posible. Una tcnica comn se conoce como o a e u

56

CAP ITULO 3. OPERACIONES CON BITS

ejecucin especulativa. Esta tcnica usa las capacidades de procesamiento o e paralelo de la CPU para ejecutar mltiples instrucciones a la vez. Las inu strucciones condicionales tienen un problema con esta idea. El procesador, en general, no sabe si se realiza o no la ramicacin.Si se toma, se ejecuo tar de instrucciones diferentes que si no se toma. El procesador trata de a predecir si ocurrir la ramicacin. Si la prediccin fue errnea el procesador a o o o ha perdido su tiempo ejecutando un cdigo errneo. o o Una manera de evitar este problema, es evitar usar ramicaciones condicionales cuando es posible. El cdigo de ejemplo en 3.1.5 muestra un proo grama muy simple donde uno podr hacer esto. En el ejemplo anterior, los a bits encendidos del registro EAX usa una ramicacin para saltarse la o instruccin INC. La gura 3.3 muestra cmo se puede quitar la ramicacin o o o usando la instruccin ADC para sumar el bit de carry directamente. o Las instrucciones SETxx suministran una manera para suprimir ramicaciones en ciertos casos. Esta instruccin ja el valor de un registro byte o o un lugar de memoria a cero, basado en el estudio del registro FLAGS. Los caracteres luego de SET son los mismos caracteres usados en saltos condicionales. Si la condicin correspondiente de SETxx es verdadero, el resultado o almacenado es uno, si es falso se almacena cero. Por ejemplo, setz al ; AL = 1 if Z flag is set, else 0

Usando estas instrucciones, uno puede desarrollar algunas tcnicas ingee niosas que calculan valores sin ramicaciones. Por ejemplo, considere el problema de encontrar el mayor de dos valores. La aproximacin normal para resolver este problema ser el uso de CMP y o a usar un salto condicional y proceder con el valor que fue ms grande. El a programa de ejemplo de abajo muestra cmo se puede encontrar el mayor o sin ninguna ramicacin. o For example, consider the problem of nding the maximum of two values. The standard approach to solving this problem would be to use a CMP and use a conditional branch to act on which value was larger. The example program below shows how the maximum can be found without any branches. ; Archivo: max.asm %include "asm_io.inc" segment .data message1 db "Digite un nmero: ",0 u message2 db "Digite otro nmero: ", 0 u message3 db "El mayor nmero es: ", 0 u segment .bss

1 2 3 4 5 6 7 8 9

3.3. EVITANDO SALTOS CONDICIONALES


10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47

57

input1

resd

; primer nmero ingresado u

segment .text global _asm_main: enter pusha mov call call mov mov call call xor cmp setg neg mov and not and or mov call mov call call popa mov leave ret

_asm_main 0,0 ; setup routine

eax, message1 print_string read_int [input1], eax eax, message2 print_string read_int ebx, eax, bl ebx ecx, ecx, ebx ebx, ecx, ebx [input1]

; imprime el primer mensaje ; ingresa el primer nmero u

; imprime el segundo mensaje ; ingresa el segundo nmero (en u ; ; ; ; ; ; ; ; ; ebx = 0 compara el segundo y el ebx = (input2 > input1) ebx = (input2 > input1) ecx = (input2 > input1) ecx = (input2 > input1) ebx = (input2 > input1) ebx = (input2 > input1) ecx = (input2 > input1) eax)

ebx eax [input1] ebx

primer nmero u ? 1 : ? 0xFFFFFFFF : ? 0xFFFFFFFF : ? input2 : ? 0 : ? 0 : ? input2 :

0 0 0 0 0xFFFFFFFF input1 input1

eax, message3 print_string eax, ecx print_int print_nl

; imprime los resultado

eax, 0

; retorna a C

El truco es crear una mscara que se pueda usar para seleccionar el valor a mayor. La instruccin SETG en la l o nea 30 ja BL a 1. Si la segunda entrada es mayor o 0 en otro caso, sta no es la mscara deseada. Para crear la e a

58

CAP ITULO 3. OPERACIONES CON BITS

mscara que se r, la l a nea requerida 31 usa la instruccin NEG en el registro o EBX. (Observe que se borr EBX primero). Si EBX es 0 no hace nada; sin o embargo si EBX es 1, el resultado es la representacin en complemento a o dos de -1 o 0xFFFFFFFF. Esta es la mscara que se necesita. El resto del a cdigo usa esta mscara para seleccionar la entrada correcta como el mayor. o a Como truco alternativo usar la instruccin DEC. En el cdigo de arriba, si o o NEG se reemplaza con un DEC, de nuevo el resultado ser 0 o 0xFFFFFFFF. a Sin embargo, los valores son invertidos que cuando se usa la instruccin NEG. o

3.4.
3.4.1.

Manipulando bits en C
Las operacones entre bits de C

A diferencia de algunos lenguajes de alto nivel C suministra operadores para operaciones entre bits. La operacin AND se representa con el operador o 1 . La operacin OR se representa por el operador binario |. La operacin & o o XOR se representa con el operador binario ^ . Y la operacin NOT se o representa con el operador unario ~ . Los desplazamientos son realizados por C con los operadores binarios << y >> . El operador << realiza desplazamientos a la izquierda y el operador >> hace desplazamientos a la derecha. Estos operadores toman 2 operandos. El de la derecha es el valor a desplazar y el de la izquierda es el nmero u de bits a desplazar. Si el valor a desplazar es un tipo sin signo, se realiza un desplazamiento lgico. Si el valor es con signo (como int), entonces se o usa un desplazamiento aritmtico. Sigue un ejemplo en C del uso de estos e operadores: short int s ; short unsigned u; s = 1; u = 100; u = u | 0x0100; s = s & 0xFFF0; s = s u; u = u << 3; s = s >> 2; / se asume que short int es de 16 bits / / / / / / / / s = 0xFFFF (complemento a dos) / u = 0x0064 / u = 0x0164 / s = 0xFFF0 / s = 0xFE94 / u = 0x0B20 (desplazamiento lgico) / o s = 0xFFA5 (desplazamiento aritmtico) / e

3.4.2.

Usando las operaciones entre bits en C

Los operadores entre bits se usan en C para los mismos propsitos que en o lenguaje ensamblador. Ellos le permiten a uno manipular bits individuales
1

Este operador es diferente del operador binario && y del unario &!

3.4. MANIPULANDO BITS EN C Macro S IRUSR S IWUSR S IXUSR S IRGRP S IWGRP S IXGRP S IROTH S IWOTH S IXOTH Meaning el propietario puede leer el propietario puede escribir el propietario puede ejecutar el grupo de propietario puede leer el grupo del propietario puede escribir el grupo del propietario puede ejecutar los otros pueden leer los otros pueden escribir los otros pueden ejecutar

59

Cuadro 3.6: Macros POSIX para permisos de archivos y se pueden usar para multiplicaciones y divisiones rpidas. De hecho, un a compilador de C inteligente usar desplazamientos automticamente para a a multiplicaciones como X*=2, Muchos API2 (Como POSIX 3 y Win 32). tienen funciones que usan operandos que tienen datos codicados como bits. Por ejemplo, los sistemas POSIX mantienen los permisos de los archivos para 3 tipos diferentes de usuarios (un mejor nombre ser propietario), grupo y a otros. A cada tipo de usuario se le puede conceder permisos para leer, escribir o ejecutar un archivo. Para cambiar los permisos de un archivo requiere que el programador de C manipule bits individuales. POSIX dene varios macros para ayudas (vea la tabla 3.6). La funcin chmod se puede usar para o establecer los permisos de un archivo. Esta funcin toma dos parmetros, o a una cadena con el nombre del archivo sobre el que se va a actuar y un entero4 Con los bits apropiados para los permisos deseados. Por ejemplo, el cdigo o de abajo ja los permisos para permitir que el propietario del archivo leerlo y escribirlo, a los usuarios en e, grupo leer en archivo y que los otros no tengan acceso. chmod(foo, S IRUSR | S IWUSR | S IRGRP ); La funcin POSIX stat se puede usar para encontrar los bits de permisos o actuales de un archivo. Usada con la funcin chmod, es posible modicar o algunos de los permisos sin cambiar los otros. Un ejemplo que quita el acceso a la escritura a los otros y aade el acceso al propietario del archivo a la n lectura. Los otros permisos no son alterados Los otros permisos no se alteran. struct stat le stats ; / estructura usada por stat () / stat (foo, & le stats ); / lee la informacin del archivo . o
Aplication Programming Interface Signica Portable Operatting System Interface for Computer Enviroments. Una norma desarrollada por el IEEE basado en UNIX. 4 Actualmente un parmetro de tipo mode t que es un typedef a un tipo integral. a
3 2

60

CAP ITULO 3. OPERACIONES CON BITS

le stats .st mode holds permission bits / chmod(foo, ( le stats .st mode & S IWOTH) | S IRUSR);

3.5.

Representaciones Littel Endian y Big Endian

El cap tulo 1 introdujo el concepto de las representaciones big y little endian de datos multibyte. Sin embargo, el autor ha encontrad que este tema confunde a muchas personas. Esta seccin cubre el tpico con ms o o a detalle. El lector recordar que lo endian se reere al orden en que los bytes a individuales se almacenan en memoria (no bits) de un elemento multibyte se almacena en memoria. Big endian es el mtodo ms directo. Almacena e a el byte ms signicativo primero, luego el siguiente byte en peso y as sucea sivamente. En otras palabras los bits de ms peso se almacenan primero. a Little endian almacena los bytes en el orden contrario (primero el menos signicativo). La familia de procesadores X86 usa la representacin little o endian. Como un ejemplo, considere la palabra doble 1234567816 . En la representacin big endian, los bytes se almacenar como 12 34 56 78. En la o an representacin little endian los bytes se almacenar como 78 56 34 12. o an El lector probablemente se preguntar as mismo, por qu cualquier a e diseador sensato usa la representacin little endian? Eran los ingenieros n o de Intel sdicos para inigir esta confusa representacin a multitud de proa o gramadores? Parecer que la CPU no tiene que hacer trabajo extra para a almacenar los bytes hacia atrs en la memoria (e invertirlos cuando los lee de a la memoria). La respuesta es que la CPU no hace ningn trabajo extra para u leer y escribir en la memoria usando el formato little endian. O entiende que la CPU est compuesta de muchos circuitos electrnicos que simplemente a o trabajan con bits. Los bits (y bytes) no estn en un orden en particular en a la CPU. Considere el registro de 2 bytes AX. Se puede descomponer en registros de un byte (AH y AL). Hay circuitos en la CPU que mantienen los valores de AH y AL. Los circuitos no estn en un orden particular en la CPU. Esto a signica que estos circuitos para AH no estn antes o despus que los circuitos a e para AL. Quiere decir que, no es dif para la CPU hacer que almacene AH cil primero. El mismo argumento se aplica a los bits individuales dentro de un byte, no hay realmente ningn orden en los circuitos de la CPU (o la memoria). Sin u embargo, ya que los bits individuales no se pueden direccionar directamente en la CPU o en la memoria, no hay manera de conocer que orden ellos parece que conservaran internamente en la CPU.

3.5. REPRESENTACIONES LITTEL ENDIAN Y BIG ENDIAN unsigned short word = 0x1234; / se asume sizeof( short) == 2 / unsigned char p = (unsigned char ) &word; if ( p[0] == 0x12 ) printf (Mquina Big Endian\n); a else printf (Mquina Little Endian\n); a Figura 3.4: Cmo determinar lo Endianness o

61

El cdigo en C en la gura 3.4 muestra cmo se puede determinar lo eno o dian de una CPU. El apuntador p trata la variable word como dos elementos de un arreglo de caracteres. As p [0] evala el primer byte de word en la , u memoria que depende de lo endian en la CPU. El cdigo en C en la Figura 3.4 muestra cmo se puede determinar o o lo endian de una CPU. El apuntador p trata la variable word como dos elementos de un arreglo de caracteres. As p [0] evala el primer byte de , u word en la memoria que depende de lo endian en la CPU.

3.5.1.

Cuando tener cuidado con Little and Big Endian

Para la programacin t o pica, lo endian de la CPU no es importante. La mayor de las veces esto es importante cuando se transere datos binarios a entre sistemas de cmputo diferente. Esto es ocurre normalmente usando un o medio de datos f sico (como un disco) o una red. Ya que el cdigo ASCII es o de 1 byte la caracter stica endian no le es importante. Todos los encabezados internos de TCP/IP almacena enteros en big Endian (llamado orden de byte de la red ). Y las bibliotecas de suministran funciones de C para tratar la cuestin endian de una manera porttil. Por o a ejemplo la funcin htonl () convierte una palabra doble ( o long int) del foro mato del host al de red. La funcin ntohl () hace la transformacin inversa.5 o o Para un sistema big endian, las dos funciones slo retornan su entrada sin o cambio alguno. Esto le permite a uno escribir programas de res que compilarn y se ejecutarn correctamente en cualquier sistema sin importar lo a a endian. Para ms informacin sobre lo endian y programacin de redes vea a o o el excelente libro de W. Richard Steven UNIX Network Programming. La Figura 3.5 muestra una funcin de C que invierte lo endian de una o palabra doble. El 486 ha introducido una nueva instruccin de mquina o a
Ahora, invertir lo endian de un entero simplemente coloca al revs los bytes; as cone vertir de big a little o de little a big es la misma operacin. As ambas funciones hacen la o , misma cosa.
5

Ahora con los conjuntos de caracteres multibyte como UNICODE , lo endian es importante an para texu to. UNICODE soporta ambos tipos de representacin o y tiene un mecanismo para especicar cul se est usa a ando para representar los datos.

62

CAP ITULO 3. OPERACIONES CON BITS

unsigned invert endian ( unsigned x ) { unsigned invert ; const unsigned char xp = (const unsigned char ) &x; unsigned char ip = (unsigned char ) & invert; ip [0] ip [1] ip [2] ip [3] = = = = xp [3]; xp [2]; xp [1]; xp [0]; / invierte los bytes individuales /

return invert ; }

/ retorna los bytes invertidos /

Figura 3.5: Funcin invert endian o llamada BSWAP que invierte los bytes de cualquier registro de 32 bits. Por ejemplo: bswap edx ; intercambia los bytes de edx

Esta instruccin no se puede usar en los registros de 16 bits, sin embargo la o instruccin XCHG se puede usar para intercambiar los bytes en los registros o de 16 bits que se pueden descomponer en registros de 8 bits. Por ejemplo: xchg ah,al ; intercambia los bytes de ax

3.6.

Contando bits

Primero se dio una tcnica directa para contar el nmero de bits que e u estn encendidos en una palabra doble. Esta seccin mira otros mtodos a o e menos directos de hacer esto, como un ejercicio de usar las operaciones de bits discutidas en este cap tulo.

3.6.1.

Mtodo uno e

El primer mtodo es muy simple, pero no obvio. La gura 3.6 muestra e el cdigo. o Cmo trabaja este mtodo? En cada iteracin el bucle, se apaga un bit o e o del dato. Cuando todos los bits se han apagado (cuando el dato es cero), el bucle naliza. El nmero de iteraciones requerido para hacer el dato cero u es igual al nmero de bits en el valor original del dato. u

3.6. CONTANDO BITS int count bits ( unsigned int data ) { int cnt = 0; while( data != 0 ) { data = data & (data 1); cnt++; } return cnt ; } Figura 3.6: Contando bits: mtodo uno e

63

La l nea 6 es donde se apaga un bit del dato. Cmo se hace esto? o Considere la forma general de la representacin en binario del dato y el 1 o del extremo derecho de esta representacin. Por denicin cada bit despus o o e de este 1 debe ser cero. Ahora, Cul ser la representacin de data -1? a a o Los bits a la izquierda del 1 del extremo derecho sern los mismos que para a data, pero en el punto del 1 del extremo derecho ellos sern el complemento a de los bits originales de data. Por ejemplo: data = xxxxx10000 data - 1 = xxxxx01111 donde X es igual para ambos nmeros. Cuando se hace data AND data u -1, el resultado ser cero el 1 del extremo derecho en data y deja todos los a otros bits sin cambio.

3.6.2.

Mtodo dos e

Para contar bits se puede usar tambin la bsqueda en una tabla de e u una palabra doble arbitraria. La aproximacin directa ser precalcular el o a nmero de bits para cada palabra doble y almacenar esto en un arreglo. u Sin embargo, hay dos problemas relacionados con esta aproximacin. Hay o alrededor de 4 mil millones de palabras dobles! Esto signica que el arreglo ser muy grande e iniciarlo consumir mucho tiempo. (de hecho, a menos a a que uno vaya a utilizar realmente el arreglo de ms que 4 mil millones de a veces, se tomar ms tiempo iniciando el arreglo que el que requerir solo a a a calcular la cantidad de bits usando el mtodo uno). e Un mtodo ms real ser precalcular la cantidad de bits para todas e a a las posibilidades de un byte y almacenar esto en un arreglo. Entonces la palabra doble se puede dividir en 4 bytes. Los bits considerados de estos 4 bytes se miran del arreglo y se suman para encontrar la cantidad de bits de la palabra doble original. La gura 3.7 muestra la implementacin de esta o

64

CAP ITULO 3. OPERACIONES CON BITS / lookup table /

static unsigned char byte bit count [256]; void initialize count bits () { int cnt , i , data;

for ( i = 0; i < 256; i ++ ) { cnt = 0; data = i ; while( data != 0 ) { / mtodo uno / e data = data & (data 1); cnt++; } byte bit count [ i ] = cnt; } } int count bits ( unsigned int data ) { const unsigned char byte = ( unsigned char ) & data; return byte bit count [ byte [0]] + byte bit count [ byte [1]] + byte bit count [ byte [2]] + byte bit count [ byte [3]]; } Figura 3.7: Mtodo dos e aproximacin. o La funcin initialize count bits debe ser llamada antes, del primer o o llamado a la funcin count bits . Esta funcin inicia el arreglo global o byte bit count. La funcin count bits mira la variable data no como una o palabra doble, sino como un arreglo de 4 bytes. El apuntador dword acta u como un apuntador a este arreglo de 4 bytes. As dword [0] es uno de los bytes en data ( el byte ms signicativo o el menos signicativo dependiena do si es little o big endian respectivamente). Claro est uno podr usar una a a instruccin como: o (data >> 24) & 0x000000FF Para encontrar el byte ms signicativo y hacer algo parecido con los otros a bytes; sin embargo, estas construcciones sern ms lentas que una referencia a a al arreglo. Un ultimo punto, un bucle for se podr usar fcilmente para calcular a a

3.6. CONTANDO BITS int count bits (unsigned int x ) { static unsigned int mask[] = { 0x55555555, 0x33333333, 0x0F0F0F0F, 0x00FF00FF, 0x0000FFFF }; int i ; int shift ; / unmero de posiciones a desplazarse a la derecha / for ( i =0, shift =1; i < 5; i ++, shift = 2 ) x = (x & mask[i ]) + ( ( x >> shift) & mask[i ] ); return x; } Figura 3.8: Mtodo tres e

65

la suma en las l neas 22 y 23. Pero el bucle for incluir el trabajo extra a de iniciar el ndice del bucle, comparar el ndice luego de cada iteracin e o incrementar el ndice. Calcular la suma como la suma expl cita de 4 valores ser ms rpido. De hecho un compilador inteligente podr convertir la a a a a versin del bucle for a la suma expl o cita. Este proceso de reducir o eliminar iteraciones de bucles es una tcnica de optimizacin conocida como loop e o unrolling.

3.6.3.

Mtodo tres e

Hay otro mtodo ingenioso de contar bits que estn en un dato. Este e a mtodo literalmente aade los unos y ceros del dato unido. Esta suma debe e n ser igual al nmero de unos en el dato. Por ejemplo considere calcular los u unos en un byte almacenado en una variable llamada data. El primer paso es hacer la siguiente operacin: o data = (data & 0x55) + ((data >> 1) & 0x55); Qu hace esto? La constante hexadecimal 0X55 es 01010101 en binario. En e el primer operando de la suma data es AND con l, los bits en las posiciones e pares se sacan. El Segundo operando (data >> 1 & 0x55), primero mueve todos los bits de posiciones pares a impares y usa la misma mscara para a sacar estos mismos bits. Ahora, el primer operando contiene los bits pares y el segundo los bits impares de data. Cuando estos dos operandos se suman, se suman los bits pares e impares de data. Por ejemplo si data es 101100112 , entonces:

66

CAP ITULO 3. OPERACIONES CON BITS

00 01 00 01 01 01 00 01 01 10 00 10 La suma de la derecha muestra los bits sumados. Los bits del byte se dividen en 4 campos de 2 bits para mostrar que se realizan 4 sumas independientes. Ya que la mayor de estas sumas pueden ser dos, no hay posibilidad a de que la suma desborde este campo y dae otro de los campos de la suma. n Claro est, el nmero total de bits no se ha calculado an. Sin embargo a u u la misma tcnica que se us arriba se puede usar para calcular el total en e o una serie de pasos similares. El siguiente paso podr ser: a or + data = (data & 0x33) + ((data >> 2) & 0x33); Continuando con el ejemplo de arriba (recuerde que data es ahora 011000102 ): data & 001100112 0010 0010 + (data >> 2) & 001100112 or + 0001 0000 0011 0010 Ahora hay 2 campos de 4 bits que se suman independientemente. El prximo paso es sumar estas dos sumas unidas para conformar el o resultado nal: data = (data & 0x0F) + ((data >> 4) & 0x0F); Usando el ejemplo de arriba (con data igual a 001100102 ): data & 000011112 00000010 + (data >> 4) & 000011112 or + 00000011 00000101 Ahora data es 5 que es el resultado correcto. La Figura 3.8 muestra una implementacin de este mtodo que cuenta los bits en una palabra doble. o e Usa un bucle for para calcular la suma. Podr ser ms rpido deshacer el a a a bucle; sin embargo, el bucle clarica cmo el mtodo generaliza a diferentes o e tamaos de datos. n

data & 010101012 + (data >> 1) & 010101012

Cap tulo 4

Subprogramas
Este cap tulo se concentra en el uso de subprogramas para hacer los programas modulares e interfaces con programas de alto nivel (como C). Las funciones y los procedimientos son ejemplos, en lenguajes de alto nivel de subprograma. El cdigo que llama en subprograma y el subprograma en s mismo deben o estar de acuerdo en cmo se pasan los datos entre ellos. Estas reglas de cmo o o se pasarn el dato son llamadas convenciones de llamado. Una gran parte de a este cap tulo tratar de las convenciones de llamado estndares de C, que se a a pueden usar para interfasar subprogramas de ensamblador con programas de C. Estas (y otras convenciones) a menudo pasan direcciones de datos ( apuntadores) para permitirle al subprograma acceder a datos en la memoria.

4.1.

Direccionamiento indirecto

El direccionamiento indirecto le permite a los registros comportarse como variables apuntador. Para indicar que un registro se va a usar indirectamente como apuntador, se encierra entre parntesis cuadrados ([]) por ejemplo: e
1 2 3

mov mov mov

ax, [Data] ebx, Data ax, [ebx]

; Direccionamiento directo de memoria de una palabra ; ebx = & Data ; ax = *ebx

Porque AX almacena una palabra, la l nea 3 lee una palabra comenzando en la direccin almacenada en EBX. Si AX lee reemplazando con AX, se leer o a un solo bate. Es importante notar que los registros no tienen ningn tipo u como lo hacen las variables en C. Que EBX se asume que seala est ton a talmente determinada por qu instrucciones se usan. Si EBX se utiliza ine correctamente; a menudo no habr error en el ensamblador; sin embargo el a programa no trabajar correctamente. Esta es una de las muchas razones a 67

68

CAP ITULO 4. SUBPROGRAMAS

por la cual el ensamblador es ms propenso a errores que los lenguajes de a alto nivel. Todos los registros de 32 bits de propsito general (EAX, EBX, ECX, o EDX) y los registros de ndice (ESI y EDI) se pueden usar para el direccionamiento indirecto. En general los registros de 8 y 16 bits no.

4.2.

Sencillo subprograma de ejemplo

Un subprograma es una unidad independiente de cdigo que puede ser o usada desde diferentes partes de un programa. En otras palabras, un subprograma es como una funcin en C. Se puede usar un salto para invocar el o subprograma, pero el retorno representa un problema. Si el subprograma es usado en diferentes partes del programa debe retornar a la parte del cdio go desde la que se la invoc. As el salto de retorno desde el subprograma o , no puede ser a una etiqueta. El cdigo siguiente muestra cmo se puede o o realizar esto usando una forma indirecta de la instruccin JMP. Esta forma o de la instruccin JMP usa el valor de un registro para determinar a dnde o o saltar (as el registro se comporta como un apuntador a una funcin en , o C). Ac est el primer programa del cap a a tulo 1 reescrito para usarlo como subprograma.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

sub1.asm ; file: sub1.asm ; Subprograma programa de ejemplo %include "asm_io.inc" segment prompt1 prompt2 outmsg1 outmsg2 outmsg3 .data db db db db db

"Ingrese un nmero: ", 0 u "Ingrese otro nmero: ", 0 u "Ud. ha ingresado ", 0 " y ", 0 ", la suma de ellos es ", 0

; no olvide el NULL

segment .bss input1 resd 1 input2 resd 1 segment .text global _asm_main: enter pusha

_asm_main 0,0 ; setup routine

4.2. SENCILLO SUBPROGRAMA DE EJEMPLO


22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63

69

mov call mov mov jmp ret1: mov call mov mov jmp mov add mov mov call mov call mov call mov call mov call mov call call

eax, prompt1 print_string ebx, input1 ecx, ret1 short get_int eax, prompt2 print_string ebx, input2 ecx, \$ + 7 short get_int eax, [input1] eax, [input2] ebx, eax eax, outmsg1 print_string eax, [input1] print_int eax, outmsg2 print_string eax, [input2] print_int eax, outmsg3 print_string eax, ebx print_int print_nl

; imprime el prompt

; almacena la direccin de input1 en ebx o ; almacena la direccin de retorno en ecx o ; lee un entero ; imprime el prompt

; ecx = esta direccin + 7 o

; eax = palabra doble en input1 ; eax += palabra doble en input2 ; ebx = eax

; imprime el primer mensaje ; imprime input1 ; imprime el segundo mensaje ; imprime input2 ; imprime el tercer mensaje ; imprime sum (ebx) ; imprime nueva lnea

; ; ; ; ; ;

popa mov eax, 0 ; retorno a C leave ret subprogram get_int Parameters: ebx - address of dword to store integer into ecx - direccin de la instruccin a donde retornar o o Notes: value of eax is destroyed

70
64 65 66 67

CAP ITULO 4. SUBPROGRAMAS

get_int: call mov jmp

read_int [ebx], eax ecx

; almacena la entrada en memoria ; salta al llamador sub1.asm

El subprograma get int usa una convencin de llamado simple basada o en un registro. Ella espera que el registro EBX almacene la direccin de la o DWORD a almacenar el nmero de entrada y el registro ECX a almacenar u el cdigo de la instruccin a saltar. En las l o o neas 25 a 28, el operador $ se usa para calcular la direccin de retorno. El operador $ retorna la direccin o o actual para la l nea en que aparece. La expresin $ + 7 calcula la direccin o o de la instruccin MOV de la l o nea 36. Los dos clculos de la direccin de retorno son complicados. El primer a o mtodo requiere que una etiqueta se dena en cada llamado a subprograma. e El segundo mtodo no requiere una etiqueta, pero requiere un tratamiento e cuidadoso. Si fue usado un salto NEAR en lugar de un short el nmero a u aadirle a $ podr no ser 7! Afortunadamente hay una manera mucho ms n a a simple de invocar subprogramas. Este mtodo usa la pila. e

4.3.

La pila

Muchas CPU tienen soporte para una pila. Una pila es una lista LIFO (Last In Firist Out). La pila es un arca de memoria que est organizada de a esta manera. La instruccin PUSH aade datos a la pila y la instruccin POP o n o quita datos. El dato extra siempre es el ultimo dato insertado (esta es la do razn por la cual es llamado FIFO). o El registro de segmento SS especica el segmento de datos que contiene la pila. (Normalmente este es el mismo segmento de datos). El registro ESP contiene la direccin del dato que ser quitado de la pila. Los datos slo o a o se pueden aadir en unidades de palabras dobles. Esto es, que no se puede n insertar un solo byte en la pila. La instruccin PUSH inserta una palabra doble1 en la pila restndole o a 4 a ESP y entonces almacena la palabra doble en [ESP]. La instruccin o POP lee la palabra doble almacenada en [ESP] y luego aade 4 a ESP. El n cdigo siguiente demuestra cmo trabajan estas instrucciones asumiendo que o o el valor inicial de ESP es 1000H.
1 2 3

push push push

dword 1 dword 2 dword 3

; 1 almacendao en 0FFCh, ESP = 0FFCh ; 2 almacenado en 0FF8h, ESP = 0FF8h ; 3 almacenado en 0FF4h, ESP = 0FF4h

1 Actualmente tambin se pueden empujar palabras, pero en el modo protegido de 32 e bits es mejor trabajar slo con palabras dobles en la pila. o

4.4. LAS INSTRUCCIONES CALL Y RET


4 5 6

71

pop pop pop

eax ebx ecx

; EAX = 3, ESP = 0FF8h ; EBX = 2, ESP = 0FFCh ; ECX = 1, ESP = 1000h

La pila se puede usar como un almacn de datos temporal muy convee niente. Tambin se usa para el llamado a subprogramas, pasando parmetros e a y variables locales. El 80x86 tambin suministra la instruccin PSHA que empuja el valor de e o los registros: EAX, EBX, ECX, EDX, ESI, EOI y EBP (no en este orden). La instruccin POPA se puede usar para devolver todos estos registros a su o valor anterior.

4.4.

Las instrucciones CALL y RET

El 80X86 suministra dos instrucciones que usa la pila para hacer llamados a subprogramas rpido y fcil. La instruccin CALL hace un salto a a o incondicional a un subprograma y empuja en la pila la direccin de la prxio o ma instruccin. La instruccin RET saca una direccin de la pila y salta a o o o esta direccin. Cuando se usa esta instruccin, es muy importante que uno o o administre la pila correctamente ya que la instruccin RET debe extraer de o la pila el nmero correcto. u El programa anterior se puede reescribir usando estas nuevas instrucciones cambiando las l neas 25 a 34 por: mov call mov call ebx, input1 get_int ebx, input2 get_int

y cambiando el subprograma get int a: get_int: call mov ret

read_int [ebx], eax

Hay varias ventajas de CALL y RET Es simple Permite a los subprogramas hacer llamados anidados fcilmente. Oba serve que get int llama read int. Esta llamada empuja otra direccin o

72

CAP ITULO 4. SUBPROGRAMAS en la pila. Al nal del cdigo de red int hay un RET que saca la dio reccin de retorno y que salta de nuevo al cdigo de get int. Cuando o o o la instruccin RET de get int se ejecuta, saca la direccin de retorno o que salta de nuevo a asm main. Esto trabaja correctamente por la propiedad LIFO de la pila.

Recuerde es muy importante sacar todos los datos que se han empujado en la pila. Por ejemplo considere lo siguiente:
1 2 3 4 5

get_int: call mov push ret

read_int [ebx], eax eax ; <<saca el valor de EAX, no la direccin de retorno!! o

Este cdigo no retornar correctamente. o a

4.5.

Convenciones de llamado

Cuando un subprograma se invoca, el cdigo llamado y el subprograma o (el llamador ) deben estar de acuerdo en cmo se pasan datos entre ellos. o Los lenguajes de alto nivel tienen maneras estndares de pasarse datos, a conocidas como convenciones de llamado. Para interfasar cdigo de alto o nivel con lenguaje ensamblador, ste debe usar las mismas convenciones que e el lenguaje de alto nivel. Las convenciones de llamado pueden diferir de compilador a compilador o puede variar de cmo se compila el cdigo (si se o o ha optimizado o no). Una convencin universal es que el cdigo ser invocado o o a con la instruccin CALL y retornar con RET. o a Todos los compiladores de C para PC soportan una convencin de llamao do que ser descrito en el resto del cap a tulo por etapas. Estas convenciones le permiten a uno crear subprogramas que sean reentrantes. Un subprograma reentrante puede ser llamado en cualquier punto del programa con seguridad (an dentro del subprograma mismo). u

4.5.1.

Pasando parmetros en la pila a

Los parmetros a un subprograma se pueden pasar en la pila. Ellos se ema pujan en la pila antes de la instruccin CALL. Tal como en C, si el parmetro o a es cambiado por el subprograma se debe pasar la direccin del dato no su o valor. Si el tamao del parmetro es menor que una palabra doble, se debe n a convertir a palabra doble antes de ser empujada en la pila. Los parmetros no son sacados de la pila por el subprograma, en lugar a de ello son accedidos desde la pila misma.Por qu? e

4.5. CONVENCIONES DE LLAMADO

73

ESP + 4 ESP

Parmetro a Direccin de retorno o Figura 4.1:

ESP + 8 ESP + 4 ESP

Parmetro a Direccin de retorno o datos del subprograma Figura 4.2:

Ya que ellos se han empujado a la pila antes de la instruccin CALL, o la direccin de retorno tendr que haberse sacado primero (y luego o a metido otra vez). A menudo los parmetros tendrn que usarse en varios lugares en el a a subprograma. Normalmente, ellos no se pueden dejar en un registro durante todo el subprograma y tendr que almacenarse en memoria. a Dejndolos en la pila tenemos una copia del dato en memoria que se a puede acceder en cualquier parte del subprograma. Considere un subprograma al que se le pasa un solo parmetro en la pila. a Cuando el subprograma se invoca, la pila se ve como en la Figura 4.1. Se puede acceder al parmetro usando direccionamiento indirecto ([ESP+4])2 . a Si la pila se usa dentro del subprograma para almacenar datos, el nmero u necesario a ser agregado a ESP cambiar. Por ejemplo, la Figura 4.2 muestra a cmo se ve la pila si una palabra doble se empuja en ella. Ahora el parmetro o a es ESP + 8 y no ESP + 4. As esto puede ser muy propenso a errores usar , ESP cuando uno se reere a parmetros. Para resolver este problema, el a 80386 suministra otro registro para usar: EBP. El unico propsito de este o registro es referenciar datos en la pila. La convencin de llamado de C ordena o que un subprograma primero guarde el valor de EBP en la pila y luego lo haga igual a ESP. Esto le permite a ESP cambiar cuando los datos se empujen o se saquen de la pila sin modicar EBP. Al nal del subprograma, se debe restaurar el valor de EBP (esta es la razn por la cual se guarda el o valor al principio del subprograma). La Figura 4.3 muestra la forma general de un subprograma que sigue estas convenciones.
Es vlido aadir una constante a un registro cuando se usa direccionamiento indireca n to. Se pueden construir expresiones ms complicadas tambin. Este tpico se ver en el a e o a cap tulo siguiente.
2

Cuando se usa direccionamiento indirecto, el procesador 80X86 accede a segmentos diferentes dependiendo de qu registros e se usan en la expresin de o direccionamiento indirecto. ESP (y EBP usan el segmento de la pila) mientras que EAX, EBX, ECX y EDX usan el segmento de datos. Sin embargo, esto normalmente no tiene importancia para la mayor de los programas a en modo protegido, porque para ellos los segmentos de datos y de pilas son los mismos.

74
1 2 3 4 5 6

CAP ITULO 4. SUBPROGRAMAS

subprogram_label: push ebp mov ebp, esp ; subprogram code pop ebp ret

; guarda el valor original de EBP en la pila ; nuevo EBP = ESP ; restaura el valor original de EBP

Figura 4.3: Forma general de un subprograma

ESP + 8 ESP + 4 ESP

EBP + 8 EBP + 4 EBP

Parmetro a Direccin de retorno o EBP guardado

Figura 4.4: Las l neas 2 y 3 de la Figura 4.3 componen el prlogo general de un o subprograma. Las l neas 5 y 6 conforman el ep logo. La gura 4.4 muestra cmo se ve la pila inmediatamente despus del prlogo. Ahora los parmetros o e o a se pueden acceder con [EBP + 8] en cualquier lugar del subprograma sin importar qu haya empujado en la pila el subprograma. e Luego que el subprograma culmina, los parmetros que se empujan en a la pila se deben quitar. La convencin de llamado de C especica que el o cdigo llamador debe hacer esto. Otras convenciones son diferentes. Por o ejemplo la convencin de llamado de Pascal especica que el subprograma o debe quitar los parmetros de la pila (hay otra forma de la instruccin RET a o que hace esto fcil). Algunos compiladores de C soportan esta convencin a o tambin. El identicador pascal se usa en la denicin del prototipo de la e o funcin para decirle al compilador que use esta convencin. De hecho, la o o convencin stdcall que usan las funciones de C del API de MS Windows o trabajan de esta forma. Cul es la ventaja de este modo? Es un poco ms a a eciente que la convencin de llamado de C. Por qu todas las funciones o e no usan esta convencin entonces? En general C le permite a una funcin o o tener un nmero variable de argumentos(printf y scanf son ejemplos). u Para este tipo de funciones, la operacin de quitar los parmetros de la pila o a variar de un llamado de la funcin a otra. La convencin de C permite las a o o instrucciones para realizar esta operacin fcilmente de un llamado a otro. o a Las convenciones de Pascal y stdcall hacen esta operacin muy dif o cil. As , la convencin de Pascal (como el lenguaje Pascal) no permite este tipo de o funciones. MS Windows puede usar esta convencin ya que ninguna de las o funciones del API toma un nmero variable de argumentos. u

4.5. CONVENCIONES DE LLAMADO


1 2 3

75

push call add

dword 1 fun esp, 4

; pasa 1 como parmetro a ; quita el parmetro de la pila a

Figura 4.5: Muestra del llamado a un subprograma La Figura 4.5 muestra como ser invocado un subprograma usando la a convencin de llamado de C. La l o nea 3 quita los parmetros de la pila manipa ulando directamente el apuntador de la pila. Una instruccin POP se podr o a usar para hacer esto pero requerir que el resultado intil se almacene en un a u registro. Actualmente, para este caso en particular muchos compiladores podr usar una instruccin POP ECX para quitar el parmetro. El compilador an o a usar POP en lugar de ADD porque ADD requiere ms bytes para la instruca a cin. Sin embargo, POP tambin altera el valor de ECX. Luego est otro o e a programa de ejemplo con dos subprogramas que usan la convencin de llao mado de C discutida arriba. La l nea 54 (y otras l neas) muestran que se pueden declarar varios segmentos de datos y texto en un solo archivo fuente. Ellos sern combinados en un solo segmento de texto y datos en el proceso a de encadenamiento. Dividir el cdigo y los datos en segmentos separados o permite que los datos de un subprograma se denan cerca del cdigo del o subprograma. sub3.asm

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

%include "asm_io.inc" segment .data sum dd 0 segment .bss input resd 1

; ; pseudo-cdigo o ; i = 1; ; sum = 0; ; while( get_int(i, &input), input != 0 ) { ; sum += input; ; i++; ; } ; print_sum(num); segment .text

76
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60

CAP ITULO 4. SUBPROGRAMAS _asm_main 0,0 ; setup routine

global _asm_main: enter pusha mov while_loop: push push call add mov cmp je add inc jmp end_while: push call pop popa leave ret

edx, 1 edx dword input get_int esp, 8 eax, [input] eax, 0 end_while [sum], eax edx short while_loop

; edx es i en el pseudocdigo o ; guarda i en la pila ; empuja la direccin de input en la pila o ; quita i e &input de la pila

; sum += input

dword [sum] print_sum ecx

; empuja el valor de sum ; quita [sum] de la pila

en la pila

; subprograma get_int ; Paramtros (en el orden que es empujan en la pila) e ; nmero de input (en [ebp + 12]) u ; address of word to store input into (at [ebp + 8]) ; Notas: ; Los valores de eax y ebx se destruyen segment .data prompt db ") Ingrese un entero (0 para salir): ", 0 segment .text get_int: push

ebp

4.5. CONVENCIONES DE LLAMADO


61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98

77

mov mov call mov call call mov mov pop ret

ebp, esp eax, [ebp + 12] print_int eax, prompt print_string read_int ebx, [ebp + 8] [ebx], eax ebp ; retorna al llamador

; almacena input en memoria

; subprograma print_sum ; imprime la suma ; Parameter: ; suma a imprimir (en [ebp+8]) ; Nota: destrueye el valor de eax ; segment .data result db "La suma es ", 0 segment .text print_sum: push mov mov call mov call call pop ret

ebp ebp, esp eax, result print_string eax, [ebp+8] print_int print_nl ebp sub3.asm

78
1 2 3 4 5 6 7 8

CAP ITULO 4. SUBPROGRAMAS

subprogram_label: push ebp mov ebp, esp sub esp, LOCAL_BYTES ; subprogram code mov esp, ebp pop ebp ret

; guarda el valor original de EBP en la pila ; nuevo EBP = ESP ; = # de bytes necesitados por las variables locales ; libera las variables locales ; restaura el valor original de EBP

Figura 4.6: Forma general de un subprograma con variables locales void calc sum( int n , int sump ) { int i , sum = 0; for ( i =1; i <= n; i++ ) sum += i; sump = sum; } Figura 4.7: versin de C de sum o

4.5.2.

Variables locales en la pila

La pila se puede usar como un lugar adecuado para las variables locales. Ah es exactamente donde C almacena las variables normales (o automatic como se dice en C). Usar la pila para las variables es importante si uno desea que el programa sea reentrante. Un programa reentrante trabajar si a es invocado en cualquier lugar, incluido en subprograma en s mismo. En otras palabras, los programas reentrantes pueden ser invocados recursivamente. Usar la pila para las variables tambin ahorra memoria. Los datos e no almacenados en la pila estn usando la memoria desde el comienzo hasta a el nal del programa (C llama este tipo de variable globalo static). Los datos almacenados en la pila solo usan la memoria cuando el subprograma que los dene est activo. a Las variables locales son almacenadas justo despus que se guard el valor e o EBP en la pila. Ellas son colocadas restando el nmero de bytes requeridos de u ESP en el prlogo del subprograma. La Figura 4.6 muestra el nuevo esqueleto o del subprograma. El registro EBP se usa para acceder a las variables locales. Considere la funcin de C en la Figura 4.7. La Figura 4.8 muestra cmo se o o podr escribir un programa equivalente en ensamblador. a

4.5. CONVENCIONES DE LLAMADO


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

79

cal_sum: push mov sub mov mov for_loop: cmp jnle add inc jmp end_for: mov mov mov mov pop ret

ebp ebp, esp esp, 4 dword [ebp - 4], 0 ebx, 1 ebx, [ebp+8] end_for [ebp-4], ebx ebx short for_loop

; hace espacio para la sum local ; sum = 0 ; ebx (i) = 1 ; es i <= n?

; sum += i

ebx, [ebp+12] eax, [ebp-4] [ebx], eax esp, ebp ebp

; ebx = sump ; eax = sum ; *sump = sum;

Figura 4.8: Versin en ensamblador de sum o

La Figura 4.9 muestra como se ve la pila luego del prlogo del programa o en la Figura 4.8. Esta parte de la pila que contiene la informacin de retorno, o los parmetros y las variables locales es llamado marco de pila (stack frame). a Cada invocacin a una funcin de C crea un nuevo marco de la pila en la o o pila. A pesar del hecho que El prlogo y el ep o logo de un subprograma se pueden simplicar usando dos instrucciones especiales que estn diseadas espec a n camente para este propsito. La instruccin ENTER ejecuta el cdigo del prlogo y LEAVE ejecuta o o o o el ep logo. La instruccin ENTER toma dos operandos inmediatos. Para la o convencin de llamado de C, el segundo operando es siempre 0. El primer o operando es el nmero de bytes necesarios para las variables locales. La u instruccin LEAVE no tiene operandos. La Figura 4.10 muestra cmo se usan o o estas instrucciones. Observe que el programa esqueleto (Figura 1.7) tambin e usa ENTER y LEAVE.
ENTER y LEAVE simplican el prlogo y el ep o logo ellos no se usan muy a menudo. Por qu? Porque e ellas son ms lentas que a instrucciones equivalentes ms simples. Este es un a ejemplo de cuando uno no puede asumir que una sola instruccin es ms rpida o a a que una secuencia de instrucciones.

80 ESP ESP ESP ESP ESP + + + + 16 12 8 4 EBP EBP EBP EBP EBP + 12 +8 +4 -4

CAP ITULO 4. SUBPROGRAMAS sump n Direccin de retorno o EBP guardado sum

Figura 4.9:
1 2 3 4 5 6

subprogram_label: enter LOCAL_BYTES, 0 ; subprogram code leave ret

; = nmero de bytes necesitados por las u ; variables locales

Figura 4.10: Forma general de un subprograma con variables locales usando ENTER and LEAVE The prologue and epilogue of a subprogram can be simplied by using two special instructions that are designed specically for this purpose. The ENTER instruction performs the prologue code and the LEAVE performs the epilogue. The ENTER instruction takes two immediate operands. For the C calling convention, the second operand is always 0. The rst operand is the number bytes needed by local variables. The LEAVE instruction has no operands. Figure 4.10 shows how these instructions are used. Note that the program skeleton (Figure 1.7) also uses ENTER and LEAVE.

4.6.

Programas Multinmdulo o

Un programa multimdulo es uno que est compuesto de ms de un o a a archivo objeto. Todos los programas presentados ac han sido multimdulo. a o Ellos estn compuestos del archivo objeto driver y el archivo objeto de ena samblador (ms los archivos objeto de las bibliotecas de C). Recuerde que a el encadenador combina los archivos objeto en un solo programa ejecutable. El encadenador debe emparejar las referencias hechas para cada etiqueta en un mdulo (archivo objeto) con su denicin en otro mdulo. Para que el o o o mdulo A use la etiqueta denida en el mdulo B, se debe usar la directiva o o extern. Luego de la directiva extern viene una lista de etiquetas separadas por comas. La directiva le dice al ensamblador que trate esas etiquetas como externas al mdulo. O sea, esas son etiquetas que se pueden usar en o este mdulo pero estn denidas en otro. El archivo asm io.inc dene las o a

4.6. PROGRAMAS MULTINMODULO

81

rutinas read int, etc. como externas. En ensamblador, no se puede acceder externamente a las etiquetas por defecto. Si una etiqueta puede ser accedida desde otros mdulos diferentes o al cual se deni, debe declararse global en su mdulo. La directiva global o o hace esto, la l nea 13 del programa esqueleto listado en la Figura 1.7 muestra que la etiqueta asm main est denida como global. Sin esta declaracin, a o deber haber error del encadenador. Por qu? Porque el cdigo de C no a e o podr hacer referencia a la etiqueta interna asm main. a Luego est el cdigo para el ejemplo anterior, reescrito para usar dos a o mdulos. Los dos subprogramas (get int y print sum) estn en archivos o a fuentes diferentes que la rutina asm main. %include "asm_io.inc" segment .data sum dd 0 segment .bss input resd 1 segment .text global extern _asm_main: enter pusha mov while_loop: push push call add mov cmp je add inc jmp main4.asm

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

_asm_main get_int, print_sum 0,0 ; setup routine

edx, 1 edx dword input get_int esp, 8 eax, [input] eax, 0 end_while [sum], eax edx short while_loop

; edx es i en el pseudocdigo o ; guarda i en la pila ; empuja la direccin input en la pila o ; quita i e &input de la pila

; sum += input

82
31 32 33 34 35 36 37 38 39

CAP ITULO 4. SUBPROGRAMAS

end_while: push call pop popa leave ret

dword [sum] print_sum ecx

; empuja el valor de sum de la pila ; quita sum de la pila

main4.asm sub4.asm

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32

%include "asm_io.inc" segment .data prompt db segment .text global get_int: enter mov call mov call call mov mov leave ret segment .data result db segment .text print_sum: enter mov call

") Ingrese un nmero entero (0 para salir): ", 0 u

get_int, print_sum 0,0 eax, [ebp + 12] print_int eax, prompt print_string read_int ebx, [ebp + 8] [ebx], eax

; almacena la entrada en memoria

; retorna

"La suma es ", 0

0,0 eax, result print_string

4.7. INTERFAZANDO ENSAMBLADOR CON C


33 34 35 36 37 38 39

83

mov call call leave ret

eax, [ebp+8] print_int print_nl

sub4.asm

El ejemplo anterior solo tiene etiquetas de cdigo global sin embargo las o etiquetas de datos trabajan exactamente de la misma manera.

4.7.

Interfazando ensamblador con C

Hoy d pocos programas estn escritos completamente en ensamblador. a, a Los compiladores son muy buenos en convertir cdigo de alto nivel en un o cdigo de mquina eciente. Ya que es mucho ms fcil escribir cdigo en o a a a o un lenguaje de alto nivel, es ms popular. Adems, el cdigo de alto nivel a a o es mucho ms porttil que el ensamblador. a a Cuando se usa ensamblador, se usa a menudo para pequeas partes de n cdigo. Esto se puede hacer de dos maneras: llamando rutinas de ensamo blador desde C o ensamblado en l nea. El ensamblado en l nea le permite al programador colocar instrucciones de ensamblador directamente en el cdio go de C. Esto puede ser muy conveniente; sin embargo hay desventajas del ensamblado en l nea. El cdigo en ensamblador se debe escribir en el formao to que usa el compilador. No hay compilador que en el momento soporte el formato NASM. Los diferentes compiladores requieren diferentes formatos. Borland y Microsoft requieren el formato NASM . DJGPP y el gcc de Linux requieren el formato GAS3 . La tcnica de llamar una rutina en ensamblador e est mucho ms generalizada en el PC. a a Las rutinas de ensamblador comnmente se usan con C por las siguientes u razones: Se necesita acceso directo a caracter sticas del hardware del computador que es imposible o dif acceder desde C. cil La rutina debe ser lo ms rpida posible y el programador puede opa a timizar a mano el cdigo mejor que el compilador o La ultima razn no es tan vlida como una vez lo fue. La tecnolog de los o a a compiladores se ha mejorado con los aos y a menudo generan un cdigo muy n o eciente. (Especialmente si se activan las optimizaciones del compilador).
3 GAS es el ensamblador que usan todos los compiladores GNV. Usa la sintaxis AT&T que es muy diferente de la sintaxis relativamente similares de MASM, TASM y NASM.

84
1 2 3 4 5 6 7 8 9 10

CAP ITULO 4. SUBPROGRAMAS

segment .data x dd format db

0 "x = %d\n", 0

segment .text ... push dword [x] push dword format call _printf add esp, 8

; ; ; ;

empuja el valor de x empuja la direccin de la cadena con formato o observe el guin bajo o quita los parmetros de la pila a

Figura 4.11: Llamado a printf EBP + 12 EBP + 8 EBP + 4 EBP valor de x direccin de la cadena con formato o Direccin de retorno o EBP guardado

Figura 4.12: Pila dentro de printf Las desventajas de las rutinas en ensamblador son: portabilidad reducida y lo poco legible. La mayor de las convenciones de llamado ya se han especicado. Sin a embargo hay algunas caracter sticas adicionales que necesitan ser descritas.

4.7.1.
La palabra reservada register se puede usar en una declaracin de o una variable de C para sugerirle al compilador que use un registro para esta variable en vez de un lugar en memoria. Ellas se conocen como variables register. Los compiladores modernos hacen esto automticamente sin requerir a ninguna sugerencia.

Ahorrando registros

Primero, C asume que una subrutina conserva los valores de los siguientes registros: EBX, ESI, EDI, EBP, CS, DS, SS, ES. Esto no signica que la subrutina no puede cambiarlos internamente. En vez de ello, signica que si se hace un cambio en sus valores, deben restablecer los valores originales antes que la subrutina retorne. Los valores en EBX, ESI y EDI no se deben modicar porque C usa esos registros para variables register. Normalmente la pila se usa para guardar los valores originales de estos registros.

4.7.2.

Etiquetas de funciones

La mayor de compiladores anteponen un guin bajo ( ) al inicio de los a o nombres de funciones y variables globales o static. Por ejemplo una funcin o llamada f se le asignar la etiqueta f. As si sta es una rutina en ena , e samblador se debe llamar f no f. El compilador gcc de Linux no antepone ningn carcter. Bajo los ejecutables ELF de Linux, uno simplemente usar u a a

4.7. INTERFAZANDO ENSAMBLADOR CON C

85

la etiqueta f para la funcin de C f. Sin embargo el gcc de DJGPP antepone o un guin bajo. Observe que en el programa esqueleto de ensamblador (Figuo ra 1.7) la etiqueta de la rutina principal es asm main.

4.7.3.

Pasando parmetros a

Bajo la convencin de llamado de C, los argumentos de una funcin se o o empujan en la pila en el orden inverso que aparecen en el llamado a la funcin. o Considere la siguiente instruccin en C: printf ("x=%d/n",x); la Figuo ra 4.11 muestra como se compilar esto (mostrado en el formato equivalente a de NASM). La Figura 4.12 muestra cmo se ve la pila luego del prlogo deno o tro de la funcin printf. La funcin printf es una de las funciones de la o o biblioteca de C que puede tomar cualquier nmero de argumentos. Las reu glas de la convencin de llamado de C fueron escritas espec o camente para permitir este tipo de funciones. Ya que la direccin de la cadena con formato o se empuja de ultimo, ste lugar en la pila ser siempre EBP + 8 no importa e a cuantos parmetros se le pasan a la funcin. El cdigo printf puede ver a o o en la cadena con formato cuntos parmetros se le debieron haber pasado y a a verlos en la en la pila. Claro est, si se comete un error, printf ("x=%d/n"); el cdigo de a o printf esperar imprimir una palabra doble en [EBP+12]. Sin embargo este a no ser el valor de x. a

No es necesario usar ensamblador para procesar un nmero u arbitrario de parmetros en C. a El archivo de cabecera stdarg.h dene marcos que se pueden usar para procesarlos con portabilidad. Vea cualquier buen libro de C para los detalles

4.7.4.

Calculando las direcciones de las variables locales

Hallar la direccin de una variable local denida en el segmento data o o bss es sencillo. Sin embargo calcular la direccin de una variable local o (o parmetro) en la pila no es directo. Sin embargo, es una necesidad muy a comn cuando se llaman subrutinas. Considere el caso de pasar la direccin u o de una variable (la llamaremos x) a una funcin (que llamaremos foo). Si x o est en EBP 8 en la pila, uno no puede usar: a mov eax, ebp - 8

Por qu? El valor que MOV almacena en EAX debe ser calculado por el ene samblador (esto es, debe ser una constante). Sin embargo, hay una instruccin que hace el clculo deseado. Es llamado LEA (Load Efective Adress). Lo o a siguiente calcular la direccin de x y la almacena en EAX: a o lea eax, [ebp - 8]

Ahora EAX almacena la direccin de x y podr ser empujada en la pila o a cuando se llame la funcin foo. No se confunda, parece como si esta instruco cin est leyendo el dato en [EBP8]; sin embargo esto no es verdad. La o a

86

CAP ITULO 4. SUBPROGRAMAS

instruccin LEA nunca lee la memoria! Solo calcula la direccin que ser o o a le por otra instruccin y almacena esta direccin en el primer operando da o o de registro. Ya que no se hace ninguna lectura a memoria, no hay necesidad, ni est permitido denir el tamao de la memoria (dword). a n

4.7.5.

Retornando valores

Las funciones diferentes a void retornan un valor. La convencin de llao mado de C especica cmo se hace esto. Los valores de retorno se pasan a o travs de registros. Todos los tipos enteros (char, int, enum, etc.) se retore nan en el registro EAX. Si son ms pequeos que 32 bits, ellos son extendidos a n a 32 bits cuando se almacenan en EAX. (el cmo se extienden depende de si o ellos tipos son con o sin signo.) Los valores de 64 bits se retornan en el par de registros EDX:EAX. Los valores tipo apuntador tambin se almacenan en e EAX. Los valores de punto otante son almacenados en el registro ST0 del coprocesador matemtico. (Este registro se discute en el cap a tulo de punto otante).

4.7.6.

Otras convenciones de llamado

Las reglas anteriores describen la convencin de llamado estndar de C o a que es soportada por todos los compiladores de C para 80x86. A menudo los compiladores soportan otras convenciones de llamado tambin. Cuando se e interfaza con lenguaje ensamblador es muy importante conocer que convencin de llamado est usando el compilador cuando llama su funcin. Noro a o malmente, el defecto es usar la convencin de llamado estndar; sin embargo o a no siempre es este el caso4 . Los compiladores que usan varias convenciones a menudo tienen opciones de la l nea de rdenes que se pueden usar para o cambiar la convencin por defecto. Ellos tambin suministran extensiones a o e la sintaxis de C para asignar expl citamente convenciones de llamado a funciones individuales. Sin embargo, estas extensiones no estn normalizadas y a pueden variar de un compilador a otro. El compilador GCC permite diferentes convenciones de llamado. La convencin de una funcin se puede declarar expl o o citamente usando la extensin o attribute . Por ejemplo para declarar una funcin void que usa la cono vencin de llamado estndar llamada f que toma un parmetro int, use la o a a siguiente sintaxis para su prototipo: void f ( int )
4

attribute ((cdecl ));

El compilador de C de Watcom es un ejemplo de uno que no usa la convencin de o llamado estndar por defecto. Vea el cdigo fuente de ejemplo para Watcom para los a o detalles

4.7. INTERFAZANDO ENSAMBLADOR CON C

87

GCC tambin soporta la convencin de llamado estndar . La funcin de e o a o arriba se podr declarar para usar esta convencin reemplazando cdecl con a o stdcall. La diferencia entre stdcall y cdecl es que stdcall requiere que la subrutina quite los parmetros de la pila (como lo hace la convencin a o de llamado de Pascal). As la convencin stdcall slo se puede usar con , o o funciones que tomen un nmero jo de parmetros (ej unas diferentes a u a printf y scanf). GCC tambin soporta un atributo adicional regparam que le dice al e compilador que use los registros para pasar hasta 3 argumentos enteros a su funcin en lugar de usar la pila. Este es un tipo comn de optimizacin que o u o soportan muchos compiladores. Borland y Microsoft usan una sintaxis comn para declarar convenciones u de llamado. Ellas aaden a las palabras reservadas cdecl stdcall a C. Esn tas palabras reservadas actan como modicadoras de funciones y aparecen u inmediatamente antes nombre de la funcin en un prototipo. Por ejemplo, o la funcin f de arriba se podr denir para Borland y Microsoft as o a : void cdecl f ( int );

Hay ventajas y desventajas para cada convencin de llamado. La prino cipal ventaja de la convencin cdecl es que es simple y muy exible. Se o puede usar para cualquier tipo de funcin de C y cualquier compilador de o C. Usar otras convenciones puede limitar la portabilidad de la subrutina. Su principal desventaja es que puede ser ms lenta que alguna de las otras a y usa ms memoria (ya que cada invocacin de la funcin requiere cdigo a o o o para quitar los parmetros de la pila). a Las ventajas de la convencin stdcall es que usa menos memoria que o cdecl. No se requiere limpiar la pila despus de la instruccin CALL. La e o principal desventaja es que no se puede usar con funciones que tengan un nmero variable de argumentos. u La ventaja de usar una convencin que use registros para pasar enteros es o la velocidad. La principal desventaja es que la convencin es ms compleja. o a Algunos parmetros pueden estar en los registros y otros en la pila. a

4.7.7.

Ejemplos

El siguiente es un ejemplo cmo una rutina de ensamblador se puede o interfasar con un programa de C (observe que este programa no usa el programa esqueleto de ensamblador (Figura 1.7) o el mdulo driver.c) o main5.c #include <stdio.h> / prototipo para la rutina en ensamblador /

88 void calc sum( int , int ) int main( void ) { int n , sum; printf (Sumar enteros hasta : ); scanf( %d, &n); calc sum(n, &sum); printf (Sum is %d\n, sum); return 0; }

CAP ITULO 4. SUBPROGRAMAS attribute ((cdecl ));

main5.c ; ; ; ; ; ; ; ; ; ; ; ; ; sub5.asm subroutinea _calc_sum halla la suma de los enteros de 1 hasta n Parametros: n - hasta dnde sumar (en [ebp + 8]) o sump - apuntador a un int para almacenar sum (en [ebp + 12]) pseudocdigo en C: o void calc_sum( int n, int * sump ) { int i, sum = 0; for( i=1; i <= n; i++ ) sum += i; *sump = sum; }

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

segment .text global _calc_sum ; ; local variable: ; sum at [ebp-4] _calc_sum: enter 4,0 push ebx mov dword [ebp-4],0 dump_stack 1, 2, 4 mov ecx, 1 for_loop:

; hace espacio para sum en la pila ; IMPORTANTE! ; sum = 0 ; imprime la pila desde ebp-8 hasta ebp+16 ; ecx es i en el pseudocdigo o

4.7. INTERFAZANDO ENSAMBLADOR CON C Sumar enteros hasta: 10 Stack Dump # 1 EBP = BFFFFB70 ESP = BFFFFB68 +16 BFFFFB80 080499EC +12 BFFFFB7C BFFFFB80 +8 BFFFFB78 0000000A +4 BFFFFB74 08048501 +0 BFFFFB70 BFFFFB88 -4 BFFFFB6C 00000000 -8 BFFFFB68 4010648C Sum is 55

89

Figura 4.13: Muestra de la ejecucin del programa sub5 o


28 29 30 31 32 33 34 35 36 37 38 39 40 41 42

cmp jnle add inc jmp end_for: mov mov mov pop leave ret

ecx, [ebp+8] end_for [ebp-4], ecx ecx short for_loop

; cmp i y n ; si no i <= n, sale ; sum += i

ebx, [ebp+12] eax, [ebp-4] [ebx], eax ebx

; ebx = sump ; eax = sum

; restaura ebx sub5.asm

Por qu la l e nea 22 de sub5.asm es importante? Porque la convencin o de llamado de C requiere que el valor de EBX no se modique por la funcin o llamada. Si esto no se hace muy probable que el programa no trabajar cora rectamente. La l nea 25 demuestra como trabaja el macro dump stack. Recuerde que el primer parmetro es slo una etiqueta numrica, y el segundo y tercero a o e determinan cuntas palabras dobles se muestran antes y despus de EBP a e respectivamente. La Figura 4.13 muestra un ejemplo de la ejecucin de un o programa. Para este volcado, uno puede ver que la direccin de la palabra o doble para almacenar la suma es BB80 (en EBP + 12); el nmero a u sumar es 0000000A (en EBP + 8); la direccin de retorno para la rutina es o

90

CAP ITULO 4. SUBPROGRAMAS

08048501 (en EBP + 4); el valor guardado de EBP es BFFFFB88 (en EBP); el valor de la variable local es 0 en (EBP- 4); y nalmente el valor guardado de EBX es 4010648C (en EBP- 8). La funcin calc sum podr ser rescrita para devolver la suma como un o a valor de retorno en lugar de usar un parmetro apuntador. Ya que la suma a es un valor entero, la suma se podr dejar en el registro EAX. La l a nea 11 del archivo main5.c deber ser cambiada por: a sum = calc sum(n); Tambin, el prototipo de calc sum podr necesitar ser alterado. Abajo est, e a a el cdigo modicado en ensamblador. o sub6.asm ; subroutina _calc_sum ; halla la suma de los enteros de 1 hasta n ; Parmetros: a ; n - hasta dnde se suma (en [ebp + 8]) o ; Valor de retorno: ; la suma ; pseudocdigo en C: o ; int calc_sum( int n ) ; { ; int i, sum = 0; ; for( i=1; i <= n; i++ ) ; sum += i; ; return sum; ; } segment .text global _calc_sum ; ; local variable: ; sum at [ebp-4] _calc_sum: enter 4,0 ; hace espacio en la pila para sum mov mov for_loop: cmp jnle add inc dword [ebp-4],0 ecx, 1 ecx, [ebp+8] end_for [ebp-4], ecx ecx ; sum = 0 ; ecx es i en el pseudocdigo o ; cmp i and n ; si no i <= n, sale ; sum += i

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

4.8. SUBPROGRAMAS ENTRANTES Y RECURSIVOS


1 2 3 4 5 6 7 8 9 10 11

91

segment .data format db "%d", 0 segment .text ... lea eax, [ebp-16] push eax push dword format call _scanf add esp, 8 ...

Figura 4.14: Llamando scanf desde ensamblador jmp end_for: mov leave ret eax, [ebp-4] ; eax = sum short for_loop

31 32 33 34 35 36 37

sub6.asm

4.7.8.

Llamando funciones de C desde ensamblador

Una gran ventaja de interfasar C y ensamblador es que le permite al cdigo de ensamblador acceder a la gran biblioteca de C y usar funciones o escritas por el usuario. Por ejemplo, si usted desea llamar la funcin scanf o para leer un entero del teclado. La Figura 4.14 muestra el cdigo que hace o esto. Un punto muy importante que recordar es que scanf sigue la convencin de llamado de C normalizada. Esto signica que preserva los valores de o los registros EBX, ESI y EDI; sin embargo los registros EAX, ECX y EDX se pueden modicar. De hecho EAX denitivamente ser cambiado, ya que a l contiene el valor de retorno del llamado a scanf. Para otros ejemplos del e uso de interfaces con C, vea el cdigo en asm io.asm que fue usado para o crear asm io.obj.

4.8.

Subprogramas entrantes y recursivos

Un programa reentrante debe satisfacer las siguientes propiedades:

92

CAP ITULO 4. SUBPROGRAMAS No debe modicar ninguna instruccin. En un lenguaje de alto nivo el esto podr ser dif a cil, pero en ensamblador no es dif para un cil programa intentar modicar su propio cdigo. Por ejemplo: o mov add word [cs:$+7], 5 ax, 2 ; copia 5 en la palabra 7 bytes adelante ; la instruccin anterio cambia 2 por 5 o

Este cdigo trabajar en modo real, pero en sistemas operativos con o a modo protegido el segmento de cdigo est marcado de solo lectura. o a Cuando se ejecute la primera l nea, el programa se abortar en estos a sistemas. Esto es una mala forma de programar por muchas razones. Es confuso, dif de mantener y no permite compartir el cdigo (vea cil o despus). e No debe modicar datos globales (tal como los datos que estn en el a segmento data y bss). Todas las variables son almacenadas en la pila. Hay varias ventajas de escribir cdigo reentrante: o Un subprograma reentrante se puede llamar recursivamente. Un programa reentrante puede compartirse en mltiples procesos. En u muchos sistemas operativos multitarea, si hay varias instancias de un programa ejecutndose, solo una copia del cdigo est en memoria. a o a Las bibliotecas compartidas y DLL (Dinamic Link Libraries) usan esta idea tambin. e Programas reentrantes trabajan mucho mejor en programas multihilo 5 Windows 9x/NT y la mayor de los sistemas operativos tipo UNIX a (Solaris, Linux, etc.) soportan programas multihilo.

4.8.1.

Subprogramas recursivos

Estos tipos de subprogramas se invocan as mismos. La recursin puede o ser directa o indirecta. La recursin directa ocurre cuando un subprograma, o digamos foo se invoca as mismo dentro del cuerpo de foo. La recursin in o directa ocurre cuando un subprograma no se llama as mismo directamente, pero otro subprograma lo llama. Por ejemplo, el subprograma foo podr a llamar a bar y bar podr llamar a foo. a Los programas recursivos deben tener una condicin de terminacin. o o Cuando esta condicin es verdadera, no se necesita hacer ms llamadas. Si o a la rutina recursiva no tiene una condicin de terminacin o la condicin o o o nunca se vuelve verdadera, la recursin nunca termina (muy parecido a un o bucle innito).

4.8. SUBPROGRAMAS ENTRANTES Y RECURSIVOS

93

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

; finds n! segment .text global _fact _fact: enter 0,0 mov cmp jbe dec push call pop mul jmp term_cond: mov end_fact: leave ret eax, [ebp+8] eax, 1 term_cond eax eax _fact ecx dword [ebp+8] short end_fact eax, 1 ; eax = n ; si n <= 1, termina

; eax = fact(n-1) ; respuesta en eax ; edx:eax = eax * [ebp+8]

Figura 4.15: Funcin factorial recursiva o

n=3 frame

n(3)
Return address

Saved EBP n(2) n=2 frame


Return address

Saved EBP n(1) n=1 frame


Return address

Saved EBP
Figura 4.16: Marco de la pila para la funcin factorial o

94 void f ( int x ) { int i ; for ( i =0; i < x; i ++ ) { printf ( %d\n, i); f ( i ); } }

CAP ITULO 4. SUBPROGRAMAS

Figura 4.17: Otro ejemplo (versin de C) o La Figura 4.15 muestra una funcin que calcula el factorial recursivao mente. Puede ser llamado desde C con: x = fact (3); / nd 3! /

La Figura 4.16 muestra cmo se ve la pila en el punto ms profundo del o a llamado de la funcin anterior. o Las Figuras 4.17 y 4.18 muestran otro ejemplo recursivo ms complicaa do en C y en ensamblador respectivamente. Cul es la salida para f(3)? a Observe que la instruccin ENTER crea un nuevo i en la pila para cada llao mado recursivo. As cada instancia recursiva de f tiene su propia variable independiente i. Deniendo i como una palabra doble en el segmento data no trabajar igual. a

4.8.2.

Revisin de tipos de variables seg n su alcance en C o u

C suministra varios tipos de almacenamiento para las variables. global Estas variables estn denidas fuera de cualquier funcin y estn a o a almacenadas en lugares jos de memoria (en los segmentos data o bss) y existen desde el inicio hasta el nal del programa. Por defecto ellas pueden ser accedidas por cualquier funcin en el programa; sin o embargo, si ellas estn declaradas como static, solo las funciones en a el mismo mdulo pueden acceder a ellas (en trminos de ensamblador o e la etiqueta es interna, no externa). static Estas son variables locales de una funcin que son declaradas static o (desafortunadamente, C usa la palabra reservada static para dos propsitos diferentes). Estas variables se almacenan tambin en luo e gares jos de memoria (en data bss), pero solo se pueden acceder directamente en las funciones en donde ellas se denieron.
5 un programa multihilo tiene varios hilos de ejecucin. Esto es el programa en s mismo o es multitarea.

4.8. SUBPROGRAMAS ENTRANTES Y RECURSIVOS

95

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

%define i ebp-4 %define x ebp+8 ; macros tiles u segment .data format db "%d", 10, 0 ; 10 = \n segment .text global _f extern _printf _f: enter 4,0 ; toma espacio en la pila para i mov lp: mov cmp jnl push push call add push call pop inc jmp quit: leave ret eax, [i] eax, [x] quit eax format _printf esp, 8 dword [i] _f eax dword [i] short lp ; es i < x? dword [i], 0 ; i = 0

; llama printf

; llama f

; i++

Figura 4.18: Otro ejemplo (versin de ensamblador) o

96

CAP ITULO 4. SUBPROGRAMAS

automatic Este es el tipo por defecto para una variable denida dentro de una funcin. Estas variables son colocadas en la pila cuando la funcin o o en la cual estn denidas se invocada y quitadas cuando la funcin a o retorna. As ellas no tienen un lugar jo en memoria. , register Esta palabra reservada le pregunta al compilador usar un registro para el dato de esta variable. Esto es solo una solicitud. El compilador no tiene que hacerlo. Si la direccin de la variable se usa en o cualquier parte del programa la variable no ser de este tipo (ya que a los registros no tienen direccin). Tambin, solo tipos enteros pueden o e ser valores tipo register. Tipos estructurados no lo pueden ser, ellos no caben en un registro! Los compiladores de C a menudo convertirn a variables normales automatic en variables register sin ningn aviso al u programador. volatile Esta palabra clave le dice al compilador que el valor en la variable puede cambiar en cualquier momento. Esto signica que el compilador no puede hacer ninguna suposicin sobre cundo se modica la o a variable. A menudo un compilador podr almacenar el valor de una a variable en un registro temporalmente y usar el registro en vez de la variable en una parte del cdigo. No se pueden hacer este tipo de optio mizaciones con variables volatile. Un ejemplo comn de una variable u volatile podr ser una que se alterara por dos hilos de un programa a multihilo. Considere el siguiente cdigo: o x = 10; y = 20; z = x; Si x pudiera ser alterada por otro hilo, es posible que otro hilo cambie x entre las l neas 1 y 3 as que z podr no ser 10. Sin embargo, si x no a fue declarada volatile, el compilador podr asumir que x no cambia y a ja z a 10. If x could be altered by another thread, it is possible that the other thread changes x between lines 1 and 3 so that z would not be 10. However, if the x was not declared volatile, the compiler might assume that x is unchanged and set z to 10. Otro uso de volatile es evitar que el compilador use un registro para una variable.

Cap tulo 5

Arreglos
5.1. Introduccin o

Un arreglo es un bloque contiguo de una lista de datos en la memoria. Cada elemento de la lista debe ser del mismo tipo y usar exactamente el mismo nmero de bytes de memria para almacenarlo. Por estas propiedade, u los arreglos permiten un acceso eciente de los datos por su posicin (o o ndice) en el arreglo. La direccin de cualquier elemento se puede calcular o conociendo tres factores: La direccin del primer elemento del arreglo. o El nmero de bytes de cada elemento. u El ndice del elemento. Es conveniente considerar el ndice del primer elemento como 0 (tal como C). Es posible usar otros valores para el primer ndic, pero esto complica los clculos. a

5.1.1.

Denir arreglos

Denir arreglos en los segmentos data y bss Para denir un arreglo iniciado en el segmento data, use las directivas normaleas: db, dw, etc. NASM tambin suministra la directiva llamada TIMES e que se puede usar para repetir una instruccin muchas veces sin tener que o duplicar las instrucciones a mano. La Figura 5.1 muestra varios ejemplos de estos. 97

98
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17

CAP ITULO 5. ARREGLOS

segment .data ; define un arreglo de 10 plabras dobles con valores ; iniciales de 1,2,..,10 a1 dd 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ; define un arreglo de 10 palabras inicadas todas con 0 a2 dw 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ; lo mismo de antes usando TIMES a3 times 10 dw 0 ; define un arreglo de bytes con ceros y luego 100 con unos a4 times 200 db 0 times 100 db 1 segment .bss ; define un arreglo de 10 palabras dobles sin valor inicial a5 resd 10 ; define un arreglo de 100 palabras dobles sin valor inicial a6 resw 100

Figura 5.1: Denir arreglos

Para denir un arreglo iniciado en el segmento bss, use las directivas resb, resw, etc. Recuerde que estas directivas tienen un operando que espera cuntas unidades de memoria reservar. La Figura 5.1 muestra tambin a e ejemplos de este tipo de deniciones

Denir arreglos como variables locales en la pila No hay una manera directa de denir una arreglo como variable local en la pila. Como antes, uno calcula el el total de bytes necesarios para todas las variables locales, incluidos los arreglos, y restar esto de ESP (o directamente usando la instruccin ENTER). Por ejemplo, si una funcin necesita una vario o able caracter, dos enteros y 50 elementos de un arreglo de palabras, uno necesitar 1 + 2 + 50 = 109 bytes. Sin embargo, el nmero restado de ESP a u deber ser un mltiplo de cuatro (112 en este caso) para que ESP est en el a u e boundary de pna palabra doble. Uno podr formar las variables dentro de a los 10g bytes de varias maneras. La Figura 5.2 muestra dos maneras posibles. La parte sin uso del primer orden para dejar las palabras dobles en los l mites de palabras dobles para aumentar la velocidad del acceso a la memoria.

5.1. INTRODUCCION EBP - 1 EBP - 8 EBP - 12 char no usado dword 1 dword 2

99

word array

word array

EBP EBP EBP EBP

100 104 108 109

EBP - 112 Figura 5.2: Disposicin de la pila o

dword 1 dword 2 char no usado

5.1.2.

Acdeder a elementos de los arreglos

No hay operador [ ] en ensamblador como en C. Para acceder a un elemento del arreglo, se debe calcular la direccin. Considere las dos denio ciones siguientes de arreglos: array1 array2 db dw 5, 4, 3, 2, 1 5, 4, 3, 2, 1 ; arreglo de bytes ; arreglo de palabras

A continuacin algunos ejemplos usando estos arreglos. o


1 2 3 4 5 6 7

mov mov mov mov mov mov mov

al, [array1] al, [array1 + [array1 + 3], ax, [array2] ax, [array2 + [array2 + 6], ax, [array2 +

1] al 2] ax 1]

; ; ; ; ; ; ;

al = array1[0] al = array1[1] array1[3] = al ax = array2[0] ax = array2[1] (NO array2[2]!) array2[3] = ax ax = ??

En la l nea 5, se referencia el elemento 1 del arreglo, no el segundo. Por qu? e Las palabras son unidades de 2 bytes, para moverse al siguiente elemento del arreglo uno se debe mover 2 bytes adelante, no uno. La l nea 7 leer un a byte del primer elemento y uno del segundo. En C, el compilador mira el tipo de apuntador para determinar cuntos bytes mover en una expresin a o aritmtica para que el programador no tenga que hacerla. Sin embargo el e ensamblador es el que calcula el tamao de los elementos del arreglo cuando n se mueve de elemento en elemento. La Figura 5.3 muestra un fragmento de cdigo que suma todos los eleo mentos de array1 del ejemplo anterior. En la l nea 7, a AX se le suma DX. Por qu no AL? Primero, los dos operandos de la instruccin ADD deben e o

100
1 2 3 4 5 6 7 8 9

CAP ITULO 5. ARREGLOS ebx, array1 dx, 0 ah, 0 ecx, 5 al, [ebx] dx, ax ebx lp ; ebx = direccin de array1 o ; dx almacenar sum a ; ?

mov mov mov mov lp: mov add inc loop

; al = *ebx ; dx += ax (not al!) ; bx++

Figura 5.3: Sumar elementos de un arreglo (Versin 1) o

1 2 3 4 5 6 7 8 9 10

mov mov mov lp: add jnc inc next: inc loop

ebx, array1 dx, 0 ecx, 5 dl, [ebx] next dh ebx lp

; ebx = direccin de array1 o ; dx almacenar sum a

; dl += *ebx ; if no hay carry vaya a next ; inc dh ; bx++

Figura 5.4: Sumar elementos de un arreglo (Versin 2) o

ser del mismo tamao. Segundo, deber ser fcil sumar bytes y llegar a un n a a resultado que no quepa en un byte. Usando DX, se permite una suma hasta 65.535. Sin embargo es importante ver que se est sumando AH tambin. a e Esta es la razn por la cual se ja AH a cero. 1 en la l o nea 3. also. This is why AH is set to zero2 in line 3. Las Figuras 5.4 y 5.5 muestran dos alternativas para calcular la suma. Las l neas en itlica reemplazan las l a neas 6 y 7 de la Figura 5.3.

Fijando AH a cero se asume impl citamente que AL es un nmero sin signo. Si es con u signo, la accin apropiada ser insertar una instruccin CBW entre las l o a o neas 6 y 7 2 Setting AH to zero is implicitly assuming that AL is an unsigned number. If it is signed, the appropriate action would be to insert a CBW instruction between lines 6 and 7

5.1. INTRODUCCION
1 2 3 4 5 6 7 8

101 ; ebx = direccin de o ; dx almacenar sum a array1

mov mov mov lp: add adc inc loop

ebx, array1 dx, 0 ecx, 5 dl, [ebx] dh, 0 ebx lp

; dl += *ebx ; dh += bandera de carry + 0 ; bx++

Figura 5.5: Sumar elementos de un arreglo (Versin 3) o

5.1.3.

Direccionamiento indirecto ms avanzado a

No es sorprendente que se use direccionamiento indirecto con arreglos. La forma ms general de una referencia indirecta memoria es a [ reg base + factor *reg ndice + constante ] donde: reg base es uno de los registros EAX, EBX, ECX, EDX, EBP, ESP, ESI o EDI. factor es 1, 2, 4 o 8. (Si 1, el factor se omite) index reg es uno de estos registros EAX, EBX, ECX, EDX, EBP, ESI, EDI. (observe que ESP no est en la lista.) a constant es una constante de 32 bits. La constante puede ser una etiqueta (o expresin de etiqueta). o

5.1.4.

Ejemplo

Se presenta un ejemplo que usa un arreglo y se pasa a la funcin. o Usa el programa array1c.c (listado abajo) como driver y no el programa driver.ci.
1 2 3 4 5 6

%define ARRAY_SIZE 100 %define NEW_LINE 10 segment .data FirstMsg Prompt

array1.asm

db db

"Primeros 10 elementos del arreglo", 0 "Ingrese el ndice del elemento a mostrar: ", 0

102
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48

CAP ITULO 5. ARREGLOS db db db "Elemento %d es %d", NEW_LINE, 0 "Elementos 20 hasta 29 del arreglo", 0 "%d", 0

SecondMsg ThirdMsg InputFormat segment .bss array segment .text extern global _asm_main: enter push push

resd ARRAY_SIZE

_puts, _printf, _scanf, _dump_line _asm_main 4,0 ebx esi ; variable local en EBP - 4

; Se inicia el arreglo con 100, 99, 98, 97, ... mov mov init_loop: mov add loop push call pop push push call add ecx, ARRAY_SIZE ebx, array [ebx], ecx ebx, 4 init_loop dword FirstMsg _puts ecx dword 10 dword array _print_array esp, 8 ; imprime FirstMsg

; imprime los 10 primeros elementos del arreglo

; Le pregunta al usuario el ndice del elemento Prompt_loop: push dword Prompt call _printf pop ecx lea push push eax, [ebp-4] ; eax = direccin de la variable local o eax dword InputFormat

5.1. INTRODUCCION
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90

103

call add cmp je call jmp InputOK: mov push push push call add push call pop push push call add pop pop mov leave ret ; ; ; ; ; ; ; ; ;

_scanf esp, 8 eax, 1 InputOK

; eax = valor de retorno de scanf

_dump_line ; dump rest of line and start over Prompt_loop ; si la entrada no es vlida a

esi, [ebp-4] dword [array + 4*esi] esi dword SecondMsg ; imprime el valor del elemento _printf esp, 12 dword ThirdMsg _puts ecx dword 10 dword array + 20*4 _print_array esp, 8 esi ebx eax, 0 ; imprime los elementos 20 a 29

; direccin de array[20] o

; retorna a C

routine _print_array Rutina llamable desde C que imprime elementos de arreglos de palabras dobles como enteros con signo Prototipo de C: void print_array( const int * a, int n); Parmetros: a a - Apuntador al arreglo a imprimir (en ebp+8 en la pila) n - nmero de enteros a imprimir (en ebp+12 en la pila) u

segment .data

104
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119

CAP ITULO 5. ARREGLOS db "%-5d %5d", NEW_LINE, 0

OutputFormat segment .text global _print_array: enter push push xor mov mov print_loop: push push push push call add inc pop loop pop pop leave ret

_print_array 0,0 esi ebx esi, esi ecx, [ebp+12] ebx, [ebp+8] ecx dword [ebx + 4*esi] esi dword OutputFormat _printf esp, 12 esi ecx print_loop ebx esi array1.asm array1c.c ; esi = 0 ; ecx = n ; ebx = direccin del arreglo o ; <print podra cambiar ecx! ; empuja array[esi]

; quita los parmetros (<deja ecx!) a

#include <stdio.h> int asm main( void ); void dump line( void ); int main() { int ret status ; ret status = asm main(); return ret status ;

5.1. INTRODUCCION } / funcin dump line o dumps all chars left in current line from input buer / void dump line() { int ch; while( (ch = getchar()) != EOF && ch != \n) / cuerpo vacio/ ; } array1c.c

105

Revisin de la instruccin LEA o o La instruccin LEA se puede usar para otros propsitos que slo calcular o o o direcciones. Un uso comn es para clculos rpidos. Considere lo siguiente: u a a lea ebx, [4*eax + eax]

Esto efectivamente almacena el valor de 5 EAX en EBX. Usando LEA para hacer esto, es ms fcil y rpido que usar MUL. Sin embargo, uno debe tener a a a en cuenta que la expresin dentro de los parntesis cuadrados debe ser una o e direccin indirecta correcta. As por ejemplo, esta instruccin no se puede o o usar para multiplicar por 6 rpidamente. a

5.1.5.

Arreglos multidimiensionales

Un arreglo multidimensional no es realmente muy diferente que los arreglos unidimensionales ya discutidos de hecho, estn representados en memoria a tal como un arreglo unidimensional. Arreglos bidimensionales No debe sorprender que el arreglo multidimiensional ms elemental es el a arreglo unidimensional ya discutido. Un arreglo multidimensional se presenta a menudo como una malla de elementos. Cada elemento est identicado por a un par de ndices. Por convencin, el primer o ndice es identicado con la la del elemento y el segundo ndice la columna. Considere un arreglo con tres las y dos columnas denidas como:

106
1 2 3 4 5

CAP ITULO 5. ARREGLOS eax, eax, eax, eax, [ebp [ebp 1 [ebp [ebp + - 52], 44] 48] 4*eax - 40] eax ; ; ; ; ; ebp - 44 es el sitio de i multiplica i por 2 add j ebp - 40 es la direccin a[0][0] o Almacena el resultado en ebp - 52

mov sal add mov mov

Figura 5.6: Ensamblador para x = a[ i ][ j ] int a [3][2]; El compilador de C reservar espacio para un arreglo de 6 (= 2 3) enteros a y ordena los elementos como sigue: Indice Elemento 0 a[0][0] 1 a[0][1] 2 a[1][0] 3 a[1][1] 4 a[2][0] 5 a[2][1]

La tabla intenta mostrar cmo el elemento referenciado como a[0][0] es o almacenado en el comienzo de los 6 elementos de un arreglo unidimensional. El elemento a [0][l] se almacena en la siguiente posicin ( o ndice 1) y as sucesivamente. Cada la del arreglo bidimensional se almacena contigua mente en memoria. El ultimo elemento de una la es seguido por el primer elemento de la prxima la. Esto es conocido como la representacin rowo o wise del arreglo y es como un compilador de C/C++ podr representar el a arreglo. Cmo el compilador determina dnde aparece a[i][j] en la repreo o sentacin rowwise? Una frmula elemental calcular el o o a ndice de i y j. La frmula en este caso es 2i + j. No es dif ver cmo se deriva esta frmula. o cil o o Cada la es de dos elementos; as el primer elemento de la la i est en la , a posicin 2i . Entonces la posicin de la columna j se encuentra sumando j o o a 2i. Este anlisis tambin muestra cmo la frmula se generaliza para un a e o o arreglo con N columnas. N i + j. Observe que la frmula no depende del o nmero de las. u Como un ejemplo, veamos cmo compila gcc el siguiente cdigo (usando o o el arreglo a denido antes): x = a[ i ][ j ]; La Figura 5.6 muestra cmo el ensamblador trabaja esto, as el compilador o , esencialmente convierte el cdigo a: o x = (&a[0][0] + 2i + j ); y de hecho, el programador podr escribir de esta manera con los mismos a resultados.

5.1. INTRODUCCION

107

No hay nada mgico sobre la escogencia de la representacin rowwise del a o arreglo. Una representacin columnwise podr trabajar bien: o a Index Element 0 a[0][0] 1 a[1][0] 2 a[2][0] 3 a[0][1] 4 a[1][1] 5 a[2][1]

En la representacin columnwise, cada columna se almacena contigua. El o elemento [i][j] se almacenan en la posicin i + 3j. Otros lenguajes (FORo TRAN, por ejemplo) usan la representacin columnwise. Esto es importante o cuando se interfaza el cdigo con varios lenguajes. o Dimensiones mayores que dos Para las dimensiones mayores de 2, se aplica la misma idea bsica. Cona sidere el arreglo tridimensional: int b [4][3][2]; Este arreglo deber ser almacenado como si fueran cuatro arreglos bidia mensionales cada uno de tamao [3][2] consecutivamente en memoria. La n tabla de abajo muestra como comienza l: e Index Element Index Element 0 b[0][0][0] 6 b[1][0][0] 1 b[0][0][1] 7 b[1][0][1] 2 b[0][1][0] 8 b[1][1][0] 3 b[0][1][1] 9 b[1][1][1] 4 b[0][2][0] 10 b[1][2][0] 5 b[0][2][1] 11 b[1][2][1]

La frmula para calcular la posicin de b[i][j][k] es 6i + 2j + k. El 6 o o est determinado por el tamao de los arreglos [3][2]. En general, para un a n arreglo a[i][j][k] ser: M N i + N j + k. Observe que nuevamente a la primera dimensin L no aparece en la frmula. o o Para dimensiones mayores, se generaliza el mismo proceso. Para un arreglo n dimensional de dimensiones D1 a Dn , la posicin del elemento denotada o por los ndices i1 a in est dada por la frmula: a o D2 D3 Dn i1 + D3 D4 Dn i2 + + Dn in1 + in o puede ser escrito ms corto como: a
n j=1

Dk ij
Aqu es donde puede de cirle al autor que fue un mejor f sico (o fue la referencia a FORTRAN un regalo?)

k=j+1

La primera dimensin D1 no aparece en la frmula: o o Por una representacin columnwise la frmula general ser o o a: i1 + D1 i2 + + D1 D2 Dn2 in1 + D1 D2 Dn1 in

108 En la notacin matemtica griega de uber: o a


n j=1

CAP ITULO 5. ARREGLOS

j1 k=1

Dk ij

En este caso, es la ultima dimensin Dn , la que no aparece en la frmula. o o Pasar arreglos multidimensionales como parmetros en C a La representacin rowwise de arreglos multidimensionales tiene un efecto o directo en la programacin en C. Para arreglos unidimensionales, no no se o necesita el tamao del arreglo para calcular donde est localizado un elemenn a to en memoria. Esto no es cierto para arreglos multidimensionales. Acceder a los elementos de estos arreglos el compilador debe conocer todo menos la primera dimensin. Esto es aparente cuando consideramos el prototipo de la o funcin que toma un arreglo multidimensional como parmetro. Lo siguiente o a no compilar: a void f ( int a [ ][ ] ); / No hay informacin de la dimensin / o o

Sin embargo, lo siguiente compila: void f ( int a [ ][2] ); Cualquier arreglo bidimensional con dos columnas puede ser pasado a esta funcin. La primera dimensin no se necesita. 3 o o No se confunda con una funcin con este prototipo: o void f ( int a [ ] ); Esto dene un arreglo unidimensional de apuntadores enteros (que incidentalmente puede ser usado para crear un arreglo de arreglos que se comporta muy parecido a un arreglo bidimensional). Para arreglos de dimensiones mayores, se debe especicar todo menos la primera dimensin. Por ejemplo, un arreglo de 4 dimensiones se podr o a pasar como: void f ( int a [ ][4][3][2] );

5.2.

Instrucciones de arreglos/cadenas

La familia de procesadores 80X86 suministran varias instrucciones que estn diseadas para trabajar con arreglos. Estas instrucciones son llamadas a n instrucciones de cadena. Ellas usan los registros de ndice (ESI y EDI) para
3

Se puede especicar un tamao all pero ser ignorado por el compilador. n , a

5.2. INSTRUCCIONES DE ARREGLOS/CADENAS LODSB LODSW LODSD AL = [DS:ESI] ESI = ESI 1 AX = [DS:ESI] ESI = ESI 2 EAX = [DS:ESI] ESI = ESI 4 STOSB STOSW STOSD [ES:EDI] = AL EDI = EDI 1 [ES:EDI] = AX EDI = EDI 2 [ES:EDI] = EAX EDI = EDI 4

109

Figura 5.7: Instrucciones de cadena de lectura y escritura realizar una operacin y entonces automticamente incrementan o decreo a mentan uno o los dos registros de ndice. La bandera direccin (DF) en el o registro FLAGS determina dnde los registros de o ndice son incrementados o decrementados. Hay dos instrucciones que modican la bandera direccin: o CLD Borra la bandera de direccin. En este estado, los registros de o ndice se incrementan. STD Establece la bandera de direccin. En este estado los registros de o ndice se decrementan. Un error muy comn en la programacin de 80X86 es olvidar colocar la banu o dera de direccin en el estado correcto de manera expl o cita. Esto a menudo hace que el cdigo trabaje la mayor parte del tiempo (cuando sucede que o la bandera de direccin est en el estado deseado) pero no trabaja todo el o a tiempo.

5.2.1.

Leer y escribir en la memoria

Las instrucciones ms elementales leen o escriben en memoria. Ellas a pueden leer o escribir un byte, palabra o palabra doble de una vez. La Figura 5.7 muestra estas instrucciones con una corta descripcin en pseuo docdigo qu hace ella. Hay varios puntos para tener en cuenta. Primero, o e ESI se usa para leer y EDI para escribir. Es fcil recordar esto si uno tiene a en cuenta que SI signica Source Index y DI signica Destination Index. Ahora observe que el registro que almacena el dato es jo (AL, AX o AEX). Finalmente, observe que las instrucciones de almacenamiento usan ES para determinar el segmento donde escribir, no DS. En la programacin en modo o protegido esto no es normalmente un problema, ya que hay slo un segmento o de datos y ES deber ser iniciado automticamente para referenciarlo. (tal a a como es DS). Sin embargo, en la programacin en modo real es muy imporo tante para el programador iniciar ES con el valor de selector del segmento correcto. 4 La Figura 5.8 muestra un ejemplo del uso de estas instrucciones
4 Otra complicacin es que uno no puede copiar el valor de DS en el registro de ES o directamente usando la instruccin MOV. En lugar de ello, el valor DS debe copiarse a un o

110
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

CAP ITULO 5. ARREGLOS

segment .data array1 dd 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 segment .bss array2 resd 10 segment .text cld mov esi, array1 mov edi, array2 mov ecx, 10 lp: lodsd stosd loop lp

; <No olvide esto!

Figura 5.8: Ejemplo de carga y almacenamiento que copia un arreglo en otro. La combinacin de una instruccin LODSx y STOSx (en las l o o neas 13 y 14 de la Figura 5.8) es muy comn. De hecho, esta combinacin se puede u o realizar con una sola instruccin para cadenas MOVSx. La Figura 5.9 describe o las operaciones que estas instrucciones realizan. Las l neas 13 y 14 de la Figura 5.8 deber ser reemplazada con una sola instruccin MOVSD con el a o mismo efecto. La unica diferencia ser que el registro EAX no ser usado a a en el bucle.

5.2.2.

El prejo de instruccin REP o

La familia 80X86 suministra un prejo de instruccin especial 5 llamado o REP que se puede usar con las instrucciones anteriores Este prejo le dice a la CPU que repita la prxima instruccin de cadena un nmero especicado o o u de veces. El registro ECX se usa para contar las iteraciones (tal como la instruccin LOOP). Usando el prejo REP, el bucle en la Figura 5.8 (l o neas 12 a 15) se podr reemplazar con una sola l a nea: rep movsd
registro de propsito general (como AX) y entonces copiado de este registro a ES usando o dos instrucciones MOV. 5 un prejo de instruccin no es una instruccin, es un byte especial que se coloca antes o o de una instruccin de cadena que modica el comportamiento de la instruccin. Se usan o o otros prejos para sobreescribir los segmentos por defecto de los accesos a memoria

5.2. INSTRUCCIONES DE ARREGLOS/CADENAS MOVSB byte [ES:EDI] = byte [DS:ESI] ESI = ESI 1 EDI = EDI 1 word [ES:EDI] = word [DS:ESI] ESI = ESI 2 EDI = EDI 2 dword [ES:EDI] = dword [DS:ESI] ESI = ESI 4 EDI = EDI 4

111

MOVSW

MOVSD

Figura 5.9: Instrucciones de cadena de movimiento de memoria


1 2 3 4 5 6 7 8 9

segment .bss array resd 10 segment .text cld mov edi, array mov ecx, 10 xor eax, eax rep stosd

; <No olvide esto!

Figura 5.10: Ejemplo de arreglo cero La Figura 5.10 muestra otro ejemplo que llena de ceros el contenido de un arreglo.

5.2.3.

Comparacin de las instrucciones de cadena o

La Figura 5.11 muestra varias instrucciones de cadena nuevas que se pueden usar para comparar memoria con otra memoria o un registro. Ellas son utiles para comparar o buscar en arreglos. Ellas jan el registro FLAGS tal como la instruccin CMP. Las instrucciones CMPSx comparan el lugar de o memoria correspondiente y SCASx comparan lugares de memoria en busca de un valor espec co. La Figura 5.12 muestra una porcin de cdigo que busca el nmero 12 o o u en un arreglo de palabras dobles, la instruccin SCASD en la l o nea 10 siempre le suma 4 a EDI an si el valor buscado se encuentra. As si uno desea u encontrar la direccin de donde est el 12 es necesario sustraer 4 de EDI o a (como lo hace la l nea 16).

112 CMPSB

CAP ITULO 5. ARREGLOS compara el byte [DS:ESI] y el byte [ES:EDI] ESI = ESI 1 EDI = EDI 1 compara la palabra [DS:ESI] y la word [ES:EDI] ESI = ESI 2 EDI = EDI 2 compara la dword [DS:ESI] y la dword [ES:EDI] ESI = ESI 4 EDI = EDI 4 compara AL y [ES:EDI] EDI 1 compara AX y [ES:EDI] EDI 2 compara EAX y [ES:EDI] EDI 4

CMPSW

CMPSD

SCASB SCASW SCASD

Figura 5.11: Comparacin de las instrucciones de cadena o

5.2.4.

Prejos de instruccin REPx o

Hay varios prejos de instruccin del tipo REP que se pueden usar con o las instrucciones de comparacin de cadenas. La Figura 5.13 muestra los o dos nuevos prejos y describe su operacin. REPE y REPZ son sinnimos o o para el mismo prejo (como RPNE y REPNZ) si la comparacin repetida de o la instruccin de cadena se detiene por el resultado de la comparacin, el o o registro o registros ndice se incrementan y ECX se decrementa; Sin embargo, Por qu uno no solo ve el registro FLAGS almacenar el estado que termin la repeticin. e a o o si ECX es cero despus de e As es posible usar la bandera Z para determinar si las comparaciones repetir la comparacin? o repetidas pararon por una comparacin o porque ECX es cero. o La Figura 5.14 muestra una porcin cdigo de ejemplo que determina si o o dos bloques de memoria son iguales. El JE en la l nea 7 del ejemplo examina el resultado de la instruccin anterior. Si la comparacin repetida para porque o o encontr dos bytes diferentes, la bandera Z continuar borrada y no se hace o a el salto; sin embargo, si las comparaciones paran porque se vuelve cero, la bandera Z estar jada y el cdigo salta a la etiqueta equal. a o

5.2.5.

Ejemplo

Esta seccin contiene un archivo fuente en ensamblador con varias funo ciones que implementan operaciones con arreglos usando instrucciones de cadena. Muchas de las funciones duplican las funciones de las bibliotecas

5.2. INSTRUCCIONES DE ARREGLOS/CADENAS


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18

113

segment .bss array resd 100 segment .text cld mov edi, array ; apuntador al inicio del arreglo mov ecx, 100 ; nmero de elementos u mov eax, 12 ; nmero a buscar u lp: scasd je found loop lp ; cdigo a ejecutar si no se encontr o o jmp onward found: sub edi, 4 ; edi apunta ahora a 12 en array ; cdigo a ejecutar si se encontr o o onward:

Figura 5.12: Ejemplo de bsqueda u REPE, REPZ REPNE, REPNZ repite la instruccinn mientras la bandera Z est ja o mxio e a mo ECX veces repite la instruccin mientras la bandera Z est borrada o o e mximo ECX veces a Figura 5.13: Prejos de instruccin REPx o conocidas de C. memory.asm global _asm_copy, _asm_find, _asm_strlen, _asm_strcpy segment .text ; funcin _asm_copy o ; copia un bloque de memoria ; prototipo de C ; void asm_copy( void * dest, const void * src, unsigned sz); ; parmetros: a ; dest - apuntador del bfer para copiar a u ; src - apuntador del bfer para copiar desde u ; sz - nmero de bytes a copiar u

1 2 3 4 5 6 7 8 9 10 11

114
1 2 3 4 5 6 7 8 9 10 11 12

CAP ITULO 5. ARREGLOS

segment .text cld mov esi, block1 ; direccin del primer bloque o mov edi, block2 ; direccin del segundo bloque o mov ecx, size ; tama~o del bloque en bytes n repe cmpsb ; repita mientras la bandera Z est fija e je equal ; Si Z est fija, los bloque son iguales a ; cdigo para ejecutar si los bloques no son igual o jmp onward equal: ; cdigo para ejecutar si los bloques son iguales o onward:

Figura 5.14: Comparando bloques de memoria

12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36

; ahora, se definen algunos smbolos tiles u %define dest [ebp+8] %define src [ebp+12] %define sz [ebp+16] _asm_copy: enter 0, 0 push esi push edi mov mov mov cld rep pop pop leave ret esi, src edi, dest ecx, sz ; esi = direccin del bfer para copiar desde o u ; edi = direccin del bfer para copia a o u ; ecx = nmero de bytes a copiar u ; borrar la bandera de direccin o ; ejecute movsb ECX veces

movsb edi esi

; function _asm_find

5.2. INSTRUCCIONES DE ARREGLOS/CADENAS


37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78

115

; Busca en la memoria un byte dado ; void * asm_find( const void * src, char target, unsigned sz); ; parmetros a ; src - apuntador al lugar dnde buscar o ; target - valor del byte a buscar ; sz - tama~o en bytes de dnde se busca n o ; valor de retorno ; si target se encuentra, apunta a la primera ocurrencia de target ; ; si no ; retorna NULL ; OBSERVE: target es un valor de un byte, pero se empuja en la pila como ; una palabra doble. El byte se almacena en los 8 bits inferiores. ; %define src [ebp+8] %define target [ebp+12] %define sz [ebp+16] _asm_find: enter push mov mov mov cld repne je mov jmp found_it: mov dec quit: pop leave ret

0,0 edi eax, target edi, src ecx, sz ; al tiene el valor a buscar

scasb found_it eax, 0 short quit eax, edi eax edi

; busca hasta que ECX == 0 or [ES:EDI] == AL ; Si la bandera cero se fija, entonces se ; encontr el valor o ; Si no se encontr, retorna un apuntador NULL o

; Si se encuentra retorna (DI - 1)

116
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120

CAP ITULO 5. ARREGLOS

; ; ; ; ; ; ;

function _asm_strlen retorna el tama~o de una cadena n unsigned asm_strlen( const char * ); parmetro a src - apuntador a la cadena valor de retorno: nmero de caracteres en la cadena (sin contar el 0 final) (en EAX) u

%define src [ebp + 8] _asm_strlen: enter 0,0 push edi mov mov xor cld repnz edi, src ; edi = apuntador a la cadena ecx, 0FFFFFFFFh ; usa el mayor valor posible en ECX al,al ; al = 0

scasb

; busca un 0 para terminar

; ; repnz will go one step too far, so length is FFFFFFFE - ECX, ; not FFFFFFFF - ECX ; mov eax,0FFFFFFFEh sub eax, ecx ; length = 0FFFFFFFEh - ecx pop leave ret edi

; funcin _asm_strcpy o ; copia una cadena ; void asm_strcpy( char * dest, const char * src); ; parmetros a ; dest - apuntador a la cadena de destion ; src - apuntador a la cadena origen ; %define dest [ebp + 8] %define src [ebp + 12] _asm_strcpy: enter 0,0

5.2. INSTRUCCIONES DE ARREGLOS/CADENAS


121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136

117

push push mov mov cld cpy_loop: lodsb stosb or jnz pop pop leave ret

esi edi edi, dest esi, src

al, al cpy_loop edi esi

; ; ; ;

Caraga AL e inc si almacena AL e inc di fija las banderas de condicin o si no pasa terminando 0, contina u

memory.asm memex.c

#include <stdio.h> #dene STR SIZE 30 / prototipos / void asm copy( void , const void , unsigned ) attribute ((cdecl )); void asm nd( const void , char target , unsigned ) attribute ((cdecl )); unsigned asm strlen( const char ) attribute ((cdecl )); void asm strcpy( char , const char ) attribute ((cdecl )); int main() { char st1 [STR SIZE] = test string; char st2 [STR SIZE]; char st ; char ch; asm copy(st2, st1 , STR SIZE); / copia todos los 30 caracteres de la cadena/ printf ( %s\n, st2); printf (Digite un caracter : ); / busca un byte en la cadena /

118 scanf( %c %[\n], &ch); st = asm nd(st2 , ch , STR SIZE); if ( st ) printf (Encontrado en : %s\n, st ); else printf (No encontrado\n); st1 [0] = 0; printf (Digite una Cadena:); scanf( %s, st1); printf (len = %u\n, asm strlen(st1 )); asm strcpy( st2 , st1 ); printf ( %s\n, st2 ); return 0; } memex.c

CAP ITULO 5. ARREGLOS

/ copy meaningful data in string /

Cap tulo 6

Punto otante
6.1.
6.1.1.

Representacin de punto otante o


N meros binarios no enteros u

Cuando se discutieron los sistemas numricos en el primer cap e tulo, slo o se trataron los valores enteros. Obviamente, debe ser posible representar nmeros no enteros en otras bases como en decimal. En decimal, los d u gitos a la derecha del punto decimal tienen asociados potencias de 10 negativas. 0,123 = 1 101 + 2 102 + 3 103 No es sorprendente que los nmeros binarios trabajen parecido. u 0,1012 = 1 21 + 0 22 + 1 23 = 0,625 Esta idea se puede combinar con los mtodos para enteros del cap e tulo 1 para convertir un nmero. u 110,0112 = 4 + 2 + 0,25 + 0,125 = 6,375 Convertir de decimal a binario no es muy dif cil. En general divida el nmero decimal en dos partes: entero y fraccin. Convierta la parte entera u o usando a binario usando los mtodos del cap e tulo 1. La parte fraccionaria se convierte usando el mtodo descrito abajo: e Considere una fraccin binaria con los bits etiquetados como a, b, c, . . . o entonces el nmero se ve como: u 0, abcdef . . . Multiplique el nmero por dos. La representacin binaria del nuevo nmero u o u ser: a a, bcdef . . . 119

120

CAP ITULO 6. PUNTO FLOTANTE

0,5625 2 = 1,125 0,125 2 = 0,25 0,25 2 = 0,5 0,5 2 = 1,0

rst bit = 1 second bit = 0 third bit = 0 fourth bit = 1

Figura 6.1: Convertir 0.5625 a binario

0,85 2 = 1,7 0,7 2 = 1,4 0,4 2 = 0,8 0,8 2 = 1,6 0,6 2 = 1,2 0,2 2 = 0,4 0,4 2 = 0,8 0,8 2 = 1,6

Figura 6.2: Convertir 0.85 a binario Observe que el primer bit est ahora en el lugar del uno. Reemplace a con a 0 y obtiene: 0, bcdef . . . y multiplique por dos otra vez para obtener: b, cdef . . . Ahora el segundo bit (b) est en la posicin del uno. Este procedimiento se a o puede repetir hasta los bits que se deseen encontrar. La Figura 6.1 muestra un ejemplo real que convierte 0.5625 a binario. El mtodo para cuando la e parte fraccionaria es cero. Como otro ejemplo, considere convertir 23.85 a binario. Es fcil convera tir la parte entera (23 = 101112 ), pero Qu pasa con la parte fraccionaria e (0,85)? La Figura 6.2 muestra el comienzo de este clculo. Si uno mira cuidaa dosamente los nmeros, se encuentra con un bucle innito. Esto signica que u

6.1. REPRESENTACION DE PUNTO FLOTANTE

121

0.85 en un binario peridico (opuesto a los decimales peridicos en base 10). o o 1 Hay un patrn de los n meros calculados. Mirando en el patrn, uno puede o u o ver que 0,85 = 0,1101102 . As 23,85 = 10111,1101102 . Una consecuencia importante del clculo anterior es que 23.85 no se a puede representar exactamente en binario usando un nmero nito de bits u 1 (tal como 3 no se puede representar en decimal con un nmero nito de u d gitos). Como muestra este cap tulo, las variables float y double en C son almacenados en binario. As valores como 23.85 no se pueden almacenar , exactamente en estas variables. Slo se puede almacenar una aproximacin o o a 23.85 Para simplicar el hardware, los nmeros de puntos otante se almau cenan con un formato consistente. Este formato usa la notacin cient o ca (pero en binario, usando potencias de dos, no de diez). Por ejemplo 23.85 o 10111,11011001100110 . . .2 se almacenar como: a 1,011111011001100110 . . . 2100 (Donde el exponente (100) est en binario). Un nmero de punto otante a u normalizado tiene la forma: 1, ssssssssssssssss 2eeeeeee Dnde 1, sssssssssssss es la mantisa y eeeeeeee es el exponente. o

6.1.2.

Representacin IEEE de punto otante o

El IEEE (Institute of Electerical and Electronic Engineer) es una organizacin internacional que ha diseado formato binarios espec o n cos para almacenar nmeros de punto otante. Este formato se usa en la mayor u a (pero no todos) los computadores hechos hoy d A menudo es soportado a. por el hardware de computador en s mismo. Por ejemplo el coprocesador numrico (o matemtico) de Intel (que est empotrado en todas las CPU e a a desde el Pentium) lo usa. El IEEE dene dos formatos con precisin difero entes: precisin simple y doble, la precisin simple es usada por las variables o o float en C y la precisin doble es usada por la variable double. o El coprocesador matemtico de Intel utiliza un tercer nivel de mayor a precisin llamado precisin extendida. De hecho, todos los datos en el coo o procesador en s mismo estn en esta precisin. Cuando se almacenan en a o memoria desde el coprocesador se convierte a precisin simple o doble auo 2 La precisin extendida usa un formato general ligeramente tomticamente. a o
1 No deber sorprenderse de que un nmero pudiera repetirse en una base, pero no en a u otra. Piense en 1 , es peridico en decimal, pero en ternario (base3) ser 0,13 4. o a 3 2 Algunos compiladores (como Borland) los tipos long double usa esta precisin exteno dida. Sin embargo, otros compiladores usan la precisin doble para double y long double. o (Esto es permitido por ANSI C).

122 31 30 s s e f 23 e 22

CAP ITULO 6. PUNTO FLOTANTE 0 f

bit de signo - 0 = positivo, 1 = negativo exponente polarizado (8-bits) = verdadero exponente + 7F (127 decimal). Los valores 00 y FF tienen signicado especial (vea el texto). fraccin - los 23 primeros bits despus de 1. en la mantisa. o e Figura 6.3: Precisin simple de la IEEE o

diferente que los formatos oat y double de la IEEE y no sern discutidos a ac. a Presicin simple IEEE o El punto otante de precisin simple usa 32 bits para codicar el nmero. o u Normalmente son exactos los primeros 7 d gitos decimales. Los nmeros de u punto otante son almacenados en una forma mucho ms complicada que los a enteros. La Figura 6.3 muestra la forma bsica del formato de precisin sima o ple del IEEE. Hay varias peculiaridades del formato. Los nmeros de punto u otante no usan la representacin en complemento a 2 para los nmeros nego u ativos. Ellos usan la representacin de magnitud y signo. El bit 31 determina o el signo del nmero como se muestra. u 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 e el almacenamiento de un bit adicional al nal y as se incrementa un poco la precisin. Esta idea se conoce como la representacin oculta del uno. o o Cmo se podr almacenar 23.85? Primero es positivo, as el bit de signo o a es 0, ahora el exponente verdadero es 4, as que el exponente es 7F + 4 = 8316 . Finalmente la fraccin es (recuerde el uno de adelante est oculto). o a Colocando todo esto unido (para ayudar a aclarar las diferentes secciones del formato del punto otante, el bit de signo y la fraccin han sido subrayados o y los bits se han agrupado en nibles):

Uno deber tener en cuena ta que los bytes 41 BE CC CD se pueden interpretar de diferentes maneras dependiendo qu hace un proe grama con ellos. Como el nmero de punto otante u de precisin simple, ellos o 0 100 0001 1 011 1110 1100 1100 1100 11002 = 41BECCCC16 representan, pero como un entero, ellos representan. o La CPU no conoce cul es Esto no es exactamente 23.85 (ya que es un binario peridico). Si uno cona u la interpretacin correcta. vierte el n mero anterior a decimal, uno encuentra que es aproximadamente. o

Este nmero es muy cercano a 23.85, pero no es exacto. Actualmente, en C, u 23.85 no se puede representar exactamente como arriba. Ya que el bit del

6.1. REPRESENTACION DE PUNTO FLOTANTE e=0 e=0 e = FF e = FF and f = 0 and f = 0 and f = 0 and f = 0

123

denota el nmero cero (que no puede ser noru malizado ) Observe que hay un +0 y -0. denotes un nmero sin normalizar. Estos se disu cuten en la prxima seccin. o o denota innito (). Hay innitos positivo y negativo. denota un resultado indenido, Conocido como NaN (Not a Number).

Cuadro 6.1: Valores especiales de f y e extremo izquierdo ms signicativo que fue truncado de la representacin a o exacta es 1. El ultimo bit se redondea a 1. As 23.85 podr ser representado a como 41 BE CC CD en hexadecimal usando precisin simple. Convirtiendo o esto a decimal el resultado y que es una aproximacin ligeramente mejor de o 23.85. Cmo se podr representar -23.85? slo cambie el bit de signo: C1BECCCD o a o no tome el complemento a 2! Ciertas combinaciones de e y f tienen signicado especial para los oat IEEE. La Tabla 6.1 describe estos valores especiales. Un innito se produce por un desborde o por una divisin por cero. Un resultado indenido se proo duce por una operacin no vlida como tratar de encontrar la ra cuadrada o a z de un nmero negativo, sumar dos innitos, etc. u Los nmeros de precisin simple estn en el rango de 1,0 2126 ( u o a 1,1755 1035 ) to 1,11111 . . . 2127 ( 3,4028 1035 ). N meros sin normalizar u Los nmeros sin normalizar se pueden usar para representar nmeros u u con magnitudes ms pequeas que los normalizados (menores a 1,0 2126 ). a n Por ejemplo, considere el nmero 1,0012 2129 ( 1,6530 1039 ). En la u forma normalizada dada, el exponente es muy pequeo. Sin embargo, se n puede representar de una forma no normalizada como: 0,010012 2127 . Para almacenar este nmero, el exponente es jado a 0 (ver la tabla 6.1) u y la funcin de la mantisa completa del nmero escrito como un producto o u 127 (todos los bits son almacenados incluyendo el 1 a la izquierda del con 2 punto decimal). La representacin de 1,001 2129 es entonces: o 0 000 0000 0 001 0010 0000 0000 0000 0000

124 63 62 s 52 e 51

CAP ITULO 6. PUNTO FLOTANTE 0 f Figura 6.4: Precisin doble del IEEE o

doble precisin IEEE o La doble precisin IEEE usa 64 bits para representar nmeros y noro u malmente son exactos los 15 primeros d gitos decimales signicativos. Como muestra la Figura 6.4, el formato bsico es muy similar a la precisin sima o ple. Se usan ms bits para el exponente (ll) y la fraccin (52) que para la a o precisin simple. o 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 precisin simple). Segundo, se permite un gran rango de exponentes o verdaderos (y as usa un gran rango de magnitudes). Las magnitudes de precisin doble van de 10308 hasta 10308 aproximadamente. o En el campo de la fraccin el responsable del incremento en el nmero o u de d gitos signicativos para los valores dobles. Como un ejemplo, considere nuevamente 23.85 otra vez. El exponente polarizado ser 4 + 3FF = 403 en hexadecimal. As la representacin doble a o 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. Si uno convierte esto a decimal uno encuentra 23.8500000000000014 (hay 12 ceros!) que es una aproximacin mucho mejor de 23.85. o La precisin doble tiene los mismos valores especiales que la precisin o o simple. 3 Los nmeros no normalizados son muy similares tambin. La prinu e cipal diferencia es que los nmeros dobles sin normalizados usan 21023 en u lugar de 2127 .

6.2.

Aritmtica de punto otante e

La aritmtica de punto otante en un computador diferente que en la e matemtica continua. En la matemticas, todos los nmeros pueden ser cona a u siderados exactos. Como se muestra en la seccin anterior, en un computador o muchos nmeros no se pueden representar exactamente con un nmero niu u to de bits. Todos los clculos se realizan con una precisin limitada. En los a o
3 La unica diferencia es que para los valores de innito e indenido, el exponente po larizado es 7FF y no FF.

6.2. ARITMETICA DE PUNTO FLOTANTE

125

ejemplos de esta seccin, se usarn nmeros con una mantisa de 8 bits por o a u simplicidad.

6.2.1.

suma

Para sumar dos nmeros de punto otante, los exponentes deben ser u iguales. Si ellos, no son iguales, entonces ellos se deben hacer iguales, desplazando la mantisa del nmero con el exponente ms pequeo. Por ejemu a n plo, considere 10,375 + 6,34375 = 16,71875 o en binario: 1,0100110 23 + 1,1001011 22 Estos dos nmeros no tienen el mismo exponente as que se desplaza la u mantisa para hacer iguales los exponentes y entonces sumar: 1,0100110 23 + 0,1100110 23 10,0001100 23 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,000110023 (o 1,0000110024 ) es igual a 10000,1102 o 16.75. Esto no es igual a la respuesta exacta (16.71875) Es slo una aproximacin o o debido al error del redondeo del proceso de la suma. Es importante tener en cuenta que la aritmtica de punto otante en un e computador (o calculadora) es siempre una aproximacin. Las leyes de las o matemticas no siempre funcionan con nmeros de punto otante en un coma u putador. Las matemticas asumen una precisin innita que un computador a o no puede alcanzar. Por ejemplo, las matemticas ensean que (a+b)b = a; a n sin embargo, esto puede ser exactamente cierto en un computador.

6.2.2.

Resta

La resta trabaja muy similar y tiene los mismos problemas que la suma. Como un ejemplo considere 16,75 15,9375 = 0,8125: Subtraction works very similarly and has the same problems as addition. As an example, consider 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

126

CAP ITULO 6. PUNTO FLOTANTE

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

6.2.3.

Multiplicacin y divisin o o

Para la multiplicacin, las mantisas son multiplicadas y los exponentes o 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 ser redondeado a 8 bits para dar: a a 1,1010000 24 = 11010,0002 = 26 La divisin es ms complicada, pero tiene problemas similares con errores o a de redondeo.

6.2.4.

Ramicaciones para programar

El principal punto de esta seccin es que los clculos de punto otante o a no son exactos. El programador necesita tener cuidado con esto. Un error comn que el programador hace con nmeros de punto otante es comparau u rlos asumiendo que el clculo es exacto. Por ejemplo considere una funcin a o llamada f (x) que hace un clculo complejo y un programa est tratando de a a encontrar las ra ces de la funcin. 4 . Uno podr intentar usar la siguiente o a instruccin para mirar si x es una ra o z: if ( f (x) == 0.0 ) Pero si f (x) retorna 1 1030 ? Esto probablemente signica que x es una muy buena aproximacin a una ra verdadera; sin embargo, la igualdad o z ser falsa. Puede no haber ningn valor de punto otante IEEE de x que a u retorne cero exactamente, debido a los errores de redondeo en f (x). Un mtodo mucho mejor ser usar: e a if ( fabs( f (x)) < EPS ) Dnde EPS es un macro denido a ser un valor positivo muy pequeo (coo n mo 1 1010 ). Esto es cierto si f (x) est muy cercano a cero. En general, a comparar un valor de punto otante (digamos x) con otro (y) use: if ( fabs(x y)/fabs(y) < EPS )
4

Una ra de una funcin es un valor x tal que f (x) = 0 z o

6.3. EL COPROCESADOR NUMERICO

127

6.3.
6.3.1.

El coprocesador numrico e
Hardware

Los primeros procesadores Intel no ten soporte de hardware para las an operaciones de punto otante. Esto no signica que ellos no pod efectuan ar operaciones de punto otante. Esto slo signica que ellas se realizaban o por procedimientos compuestos de muchas instrucciones que no son de punto otante. Para estos primeros sistemas, Intel suministraba un circuito integrado adicional llamado coprocesador matemtico. Un coprocesador matemtico a a tiene instrucciones de mquina que realizan instrucciones de punto otante a mucho ms rpido que usando procedimientos de software (en los primeros a a procesadores se realizaban al menos 10 veces ms rpido). El coprocesador a a 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 matemtico o a en el 80486 en s mismo 5 . Desde el Pentium, todas las generaciones del 80X86 tienen un coprocesador matemtico empotrado; sin embargo l todav se a e a programa como si fuera una unidad separada. An los primeros sistemas u sin un coprocesador pueden instalar un software que emula el coprocesador matemtico. Estos emuladores se activan automticamente cuando un proa a grama ejecuta una instruccin del coprocesador y corre un procedimiento o que produce los mismos resultados que el coprocesador (mucho ms lento a claro est). a El coprocesador numrico tiene ocho registros de punto otante. Cada e registro almacena 80 bits. Los nmeros de punto otante se almacenan en u estos registros siempre como nmeros de 80 bits de precisin extendida. u o Los registros se llaman STO, ST1, . . . ST7. Los registros de punto otante se usan diferentes que los registros enteros en la CPU. Los registros de punto otante estn organizados como una pila. Recuerde que una pila es una lista a LIFO (Last In Firist Out). STO siempre se reere al valoren el tope de la pila. Todos los nmeros nuevos se aaden al tope de la pila. Los nmeros u n u existentes se empujan en la pila para dejarle espacio al nuevo nmero. u Hay tambin un registro de estado en el coprocesador numrico. Tiene e e varias banderas. Slo las 4 banderas usadas en comparaciones sern estudio a adas. C0 , C1 , C2 and C3 . El uso de ellas se discutir luego. a

6.3.2.

Instrucciones

Para distinguir fcilmente las instrucciones normales de la CPU de las a del coprocesador, todos los nemnicos del coprocesador comienzan por F. o
5

Sin embargo el 80486SX no tiene el un coprocesador integrado

128 Carga y almacenamiento

CAP ITULO 6. PUNTO FLOTANTE

Hay varias instrucciones que cargan datos en el tope de la pila de registro del coprocesador: FLD source Carga un nmero de punto otante de la memoria en el tope u de la pila. La source puede ser un nmero de precisin simple u o doble o extendida o un registro del coprocesador FILD source Lee un entero de memoria, lo convierte a punto otante y almacena el resultado en el tope de la pila. source puede ser una palabra, palabra doble o una palabra cudruple. a FLD1 almacena un uno en el tope de la pila. FLDZ almacena un cero en el tope de la pila. Hay tambin varias instrucciones que almacenan datos de la pila en e memoria. Algunas de estas instrucciones tambin sacan el nmero de la pila. e u FST dest Almacena el tope de la pila (ST0) en memoria. El destino puede ser un nmero de precisin simple o doble o un registro u o de coprocesador. FSTP dest Almacena el tope de la pila en la memoria tal como FST; sin embargo luego de que se almacena el nmero, su valor se saca u de la pila. El destino puede ser un nmero de precisin simple u o o doble o un registro del coprocesador. FIST dest Almacena el valor del tope de la pila convertido a entero en memoria. El destino puede ser una palabra o palabra doble. La pila no sufre ningn cambio. Cmo se convierte el nmero u o u de punto otante a entero depende de algunos bits de la palabra de control del coprocesador. Este es un registro especial (no de punto otante) que controla cmo trabaja el coproceo sador. Por defecto, la palabra de control se inicia tal que redondea al entero ms cercano cuando se convierte a entero. a Sin embargo las instrucciones FSTCW (Store Control Word) y FDLCW (load control word) se pueden usar para cambiar este comportamiento. FISTP dest Lo mismo que FIST, excepto por dos cosas. El tope de la pila se saca y el destino tambin puede ser una palabra cudruple. e a Hay otras dos instrucciones que pueden mover o quitar datos de la pila en s misma. FXCH STn intercambia los valores en ST0 y STn en la pila (donde n es el nmero del registro de 1 a 7). u FFREE STn libera un registro en la pila, marcando el registro como no usado o vac o.

6.3. EL COPROCESADOR NUMERICO


1 2 3 4 5 6 7 8 9 10 11 12 13

129

segment .bss array resq SIZE sum resq 1 segment .text mov ecx, SIZE mov esi, array fldz lp: fadd qword [esi] add esi, 8 loop lp fstp qword sum

; ST0 = 0 ; ST0 += *(esi) ; se mueve al prximo dobule o ; alamcena el resultado en sum

Figura 6.5: Ejemplo sumar arreglo

Suma y resta

Cada una de las instrucciones de suma calcula la suma de ST0 y otro operando, el resultado siempre se almacena en un registro del coprocesador. FADD src ST0 += src . src puede ser cualquier registro del coprocesador o un nmero de precisin simple o doble u o en memoria. FADD dest, ST0 dest += ST0. El destino puede ser cualquier registro del coprocesador FADDP dest o dest += ST0 entonces se saca de la pila. El destino FADDP dest, ST0 puede ser cualquier registro del coprocesador. FIADD src ST0 += (float) src . suma un entero con ST0. src debe ser una palabra o una palabra doble en memoria. Hay el doble de instrucciones de resta que de suma porque el orden de los operandos es importante. (a + b = b + a, pero a b = b a!). Por cada instruccin, hay una alterna que resta en el orden inverso. Esas instrucciones o al revs todas culminan con R o RP. La Figura muestra un pequeo cdigo e n o que suma los elementos de un arreglo de dobles. En las l neas 10 y 13, uno debe especicar el tamao del operando de memoria. De otra forma el n ensamblador no conocer si el operando de memoria era un oat (dword) o a double (qword).

130 FSUB src

CAP ITULO 6. PUNTO FLOTANTE ST0 -= src . src puede ser cualquier registro del coprocesador o un nmero de presicin doble o simple u o en memoria. ST0 = src - ST0. src puede ser cualquier registro del coprocesador o un nmero de presicin doble o u o simple en memoria. dest -= ST0. dest puede ser cualquier registro del coprocesador. dest = ST0 - dest . dest puede ser cualquier registro del coprocesador. dest -= ST0 entonces sale de la pila. dest puede ser cualquier registro del coprocesador. dest = ST0 - dest entonces sale de la pila. dest puede ser cualquier registro del coprocesador. ST0 -= (float) src . Resta un entero de ST0. src debe ser una palabra o palabra doble en memoria. ST0 = (float) src - ST0. Resta ST0 de un entero. src debe ser una palabra o palabra doble en memoria.

FSUBR src

FSUB dest, ST0 FSUBR dest, ST0 FSUBP dest o FSUBP dest, STO FSUBRP dest o FSUBRP dest, STO FISUB src FISUBR src

Multiplicacin y divisin o o

La instruccin de multiplicacin son totalmente anlogas a las instruco o a ciones de suma. FMUL src ST0 *= src . src puede ser cualquier registro del coprocesador o un nmero de precisin simple o doble u o en memoria. dest *= ST0. dest puede ser cualquier registro del coprocesador. dest *= ST0 entonces sale de la pila. dest puede ser cualquier registro del coprocesador. ST0 *= (float) src . Multiplica un entero con ST0. src debe ser una palabra o palabra doble en memoria.

FMUL dest, ST0 FMULP dest o FMULP dest, STO FIMUL src

No es sorprendente que las instrucciones de divisin son anlogas a las o a instrucciones de resta. La divisin por cero de un innito. o

6.3. EL COPROCESADOR NUMERICO FDIV src

131

FDIVR src

FDIV dest, ST0 FDIVR dest, ST0 FDIVP dest o FDIVP dest, STO FDIVRP dest o FDIVRP dest, STO FIDIV src FIDIVR src

ST0 /= src . src puede ser cualquier registro del coprocesador o un nmero de precisin simple o doble u o en memoria. ST0 = src / ST0. src puede ser cualquier registro del coprocesador o una nmero de precisin simple o u o doble en memoria. dest /= ST0. dest puede ser cualquier registro del coprocesador. dest = ST0 / dest . dest puede ser cualquier registro del coprocesador. dest /= ST0 entonces sale de la pila. dest puede ser cualquier registro del coprocesador. dest = ST0 / dest entonces sale de la pila. dest puede ser cualquier registro del coprocesador. ST0 /= (float) src . Divide ST0 por un entero. src debe ser una palabra o palabra doble en memoria. ST0 = (float) src / ST0. Divide un entero por ST0. src debe ser una palabra o palabra doble en memoria.

Comparaciones El coprocesador tambin realiza comparaciones de nmeros de punto e u otante. La frmula de instrucciones FCOM hacen esta operacin. o o compara ST0 y src . src puede ser un registro del coprocesador o un oat o double en memoria. puede ser un FCOMP src compara ST0 y src , luego sale de la pila. src puede ser un registro del coprocesador o un oat o double en memoria. FCOMPP compara ST0 y ST1, entonces saca de la pila dos veces. FICOM src compara ST0 y (float) src . src puede ser una palabra o palabra dobel en memoria. FICOMP src compara ST0 y (float)src , entonces saca de la pila. src puede ser una palabra o palabra doble entera en memoria. FTST compara ST0 and 0. Estas instrucciones cambian los bits C0 , C1 , C2 y C3 del registro de estado del coprocesador. Desafortunadamente no es posible que la CPU acceda a estos bits directamente. Las instrucciones de salto condicional usan el registro FLAGS, no el registro de estado del coprocesador. Sin embargo es relativamente fcil transferir los bits de la palabra de estado a los bits a correspondientes del registro FLGS usando algunas instrucciones nuevas. FCOM src

132
1 2 3 4 5 6 7 8 9 10 11 12 13 14

CAP ITULO 6. PUNTO FLOTANTE

; ;

if ( x > y ) fld fcomp fstsw sahf jna qword [x] qword [y] ax else_part ; ST0 = x ; compara STO and y ; mueve los bits C a FLAGS ; si x no es mayor que y, ; vaya a else_part

then_part: ; cdigo para parte de entonces o jmp end_if else_part: ; cdigo para parte si no o end_if:

Figura 6.6: Ejemplo de comparacin o Almacena la palabra de estado del coprocesador in memoria o en el registro AX. SAHF Almacena el registro AH en el registro FLAGS. LAHF Carga el registro AH con los bits del registro FLAGS. La Figura 6.6 muestra un ejemplo cortico. Las l neas 5 y 6 transeren los bits C0 , C1 , C2 y C3 de la palabra de estado del coprocesador al registro FLAGS. Los bits son transferidos tal que ellos son anlogos al resultado de a una comparacin de dos enteros sin signo. Este es el porque la l o nea 7 usa la instruccin JNA. o El Pentium Pro (y procesadores posteriores II y III) soportan 2 nuevos operadores de comparacin que directamente modican el registro FLAGS o de la CPU. FCOMI src compara ST0 y src . src debe ser un registro del coprocesador. FCOMIP src compara ST0 y src , entonces se saca de la pila. src debse ser un registro del coprocesador. La Figura 6.7 muestra una subrutina de ejemplo que encuentran el mximo a de dos nmeros tipo double usando la instruccin FCOMIP. No confundan esu o ta instruccin con la funcin de comparacin de enteros (FICOMP y FICOM). o o o FSTSW dest

Instrucciones miscelaneas Esta seccin cubre otras instrucciones que suministra el procesador: o

6.3. EL COPROCESADOR NUMERICO FCHS FABS FSQRT FSCALE

133

ST0 = - ST0 cambia el bit de signo de ST0 ST0 = |ST0| Toma el valor absoluto de ST0 ST0 = STO Toma la ra cuadrada de ST0 z ST0 = ST0 2 ST1 multiplica ST0 por una potencia de dos rpidamente. ST1 no se quita de la pila del coprocesador . La a Figure 6.8 muestra un ejemplo de cmo usar esta instruccin. o o

6.3.3. 6.3.4.

Ejemplos Frmula cuadrtica o a

El primer ejemplo muestra cmo se puede programar la frmula cuadrtica o o a en ensamblador. Recuerde que la frmula cuadrtica calcula la solucin de o a o una ecuacin cuadrticai: o a ax2 + bx + c = 0 La frmula en s misma da dos soluciones para x: x1 y x2 . o b b2 4ac x1 , x2 = 2a La expresin dentro de la ra cuadrada (b2 4ac) es llamada discriminante, o z su valor es util para determinar las 3 posibilidades para la solucin: o 1. Hay solo un real en la solucin degenerad. b2 4ac = 0 o 2. Hay dos soluciones reale. b2 4ac > 0 3. Hay dos soluciones complejas. b2 4ac < 0 Este es un pequeo programa en C que usa la subrutina en ensamblador. n quadt.c #include <stdio.h> int quadratic ( double, double, double, double , double ); int main() { double a,b,c , root1 , root2; printf (Enter a , b , c : ); scanf( %lf %lf % lf , &a, &b, &c); if ( quadratic ( a , b , c, &root1, &root2 ) )

134

CAP ITULO 6. PUNTO FLOTANTE

printf (roots : %.10g %.10g\n, root1 , root2 ); else printf (No real roots\n); return 0; } quadt.c A continuacin est la rutina en ensamblador. o a
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32

; ; ; ; ; ; ; ; ; ; ; ;

quad.asm funcin quadratic o Halla la solucin de la ecuacin cuadrtica: o o a a*x^2 + b*x + c = 0 Prototipo de C: int quadratic( double a, double b, double c, double * root1, double *root2 ) Parmetros: a a, b, c - Coeficientes de la ecuacin cuadrtica (ver arriba) o a root1 - Apuntador al double que almacena la primera raz root2 - Apuntador al double que almacena la segunda raz Valor de retorno: devuelve 1 si las races son reales si no 0 a b c root1 root2 disc one_over_2a qword qword qword dword dword qword qword [ebp+8] [ebp+16] [ebp+24] [ebp+32] [ebp+36] [ebp-8] [ebp-16]

%define %define %define %define %define %define %define

segment .data MinusFour segment .text global _quadratic: push mov sub push

dw

-4

_quadratic ebp ebp, esp esp, 16 ebx

; asigna 2 doubles (disc & one_over_2a) ; debe guardar el valor original de ebx

6.3. EL COPROCESADOR NUMERICO


33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74

135

fild fld fld fmulp fmulp fld fld fmulp faddp ftst fstsw sahf jb fsqrt fstp fld1 fld fscale fdivp fst fld fld fsubrp fmulp mov fstp fld fld fchs fsubrp fmul mov fstp mov jmp

word [MinusFour]; pila -4 a ; pila: a, -4 c ; pila: c, a, -4 st1 ; pila: a*c, -4 st1 ; pila: -4*a*c b b ; pila: b, b, -4*a*c st1 ; pila: b*b, -4*a*c st1 ; pila: b*b - 4*a*c ; prueba con 0 ax no_real_solutions ; disc ; ; a ; ; st1 ; one_over_2a ; b ; disc ; st1 ; st1 ; ebx, root1 qword [ebx] ; b ; disc ; ; st1 ; one_over_2a ; ebx, root2 qword [ebx] ; eax, 1 ; short quit ; if disc < 0, no hay soluciones reales pila: sqrt(b*b - 4*a*c) almacena y saca de la pila pila: 1.0 pila: a, 1.0 pila: a * 2^(1.0) = 2*a, 1 pila: 1/(2*a) pila: 1/(2*a) pila: b, 1/(2*a) pila: disc, b, 1/(2*a) pila: disc - b, 1/(2*a) pila: (-b + disc)/(2*a) almacena en *root1 pila: b pila: disc, b pila: -disc, b pila: -disc - b pila: (-b - disc)/(2*a) almacena en *root2 valor de retorno es 1

no_real_solutions: mov eax, 0 quit: pop mov ebx esp, ebp

; valor de retorno es 0

136
75 76

CAP ITULO 6. PUNTO FLOTANTE pop ret ebp quad.asm

6.3.5.

Leer arreglos de archivos

Este ejemplo, es una rutina en ensamblador que lee doubles de un archivo. Sigue un pequeo programa de prueba en C. n readt.c / Este programa prueba el procedimiento en ensamblador bit read doubles () . Lee doubles de stdin . ( use la redireccion para leer desde un archivo .) o / #include <stdio.h> extern int read doubles ( FILE , double , int ); #dene MAX 100 int main() { int i ,n; double a[MAX]; n = read doubles( stdin , a , MAX); for ( i =0; i < n; i ++ ) printf ( %3d %g\n, i, a[i ]); return 0; } readt.c A continuacin la rutina en ensamblador o
1 2 3 4 5 6 7 8

segment .data format db segment .text global extern

read.asm "%lf", 0 ; format for fscanf()

_read_doubles _fscanf 8

%define SIZEOF_DOUBLE

6.3. EL COPROCESADOR NUMERICO


9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50

137

%define %define %define %define ; ; ; ; ; ; ; ; ; ; ; ;

FP ARRAYP ARRAY_SIZE TEMP_DOUBLE

dword [ebp + 8] dword [ebp + 12] dword [ebp + 16] [ebp - 8]

funcin _read_doubles o prototipo de C: int read_doubles( FILE * fp, double * arrayp, int array_size ); Esta funcin lee dobles de un archivo de texto hasta EOF o hasta o que se llene el arreglo Parmetros: a fp - apuntador FILE pointer a leer desde (se debe abrir para entrada) arrayp - apuntador a un arreglo de doubles para leer en l e array_size - nmero de elementos en el arreglo u Valor de retorno: nmero de doubles almacenado en el arreglo (en EAX) u

_read_doubles: push mov sub push mov xor

ebp ebp,esp esp, SIZEOF_DOUBLE esi esi, ARRAYP edx, edx

; define un double en la pila ; guarda esi ; esi = ARRAYP ; edx = ndice del arreglo (inicialmente en 0)

while_loop: cmp edx, ARRAY_SIZE ; si edx < ARRAY_SIZE? jnl short quit ; si no, salir del bucle ; ; llama a fscanf() para leer un double en TEMP_DOUBLE ; fscanf() podra cambiar edx y as guardarlo ; push edx ; guarda edx lea eax, TEMP_DOUBLE push eax ; push &TEMP_DOUBLE push dword format ; push &format push FP ; push el apuntador al archivo call _fscanf add esp, 12 pop edx ; restaura edx

138
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73

CAP ITULO 6. PUNTO FLOTANTE cmp jne eax, 1 short quit ; fscanf retorn 1? o ; si no, salir del bucle

; ; copia TEMP_DOUBLE en ARRAYP[edx] ; (Los ocho bytes del dobule son copiados por las dos copias de 4 bytes) ; mov eax, [ebp - 8] mov [esi + 8*edx], eax ; primero copia los 4 bytes inferiores mov eax, [ebp - 4] mov [esi + 8*edx + 4], eax ; ahora copia los 4 bytes superiores inc jmp quit: pop mov mov pop ret esi eax, edx esp, ebp ebp read.asm ; restaura esi ; almacena el valor de retorno en eax edx while_loop

6.3.6.

Hallar primos

Este ultimo ejemplo halla nmeros primos una vez ms. Esta imple u a mentacin es ms eciente que la anterior. Almacena los primos que ha o a encontrado en un arreglo y solo divide por los primos que ha encontrado antes, en lugar de cada nmero impar, para encontrar nuevos primos. u Otra diferencia es que calcula la ra cuadrada del nmero para buscar z u el prximo primo a determinar en qu punto parar la bsqueda de factores. o e u Altera la palabra de control de coprocesador tal que cuando almacena la ra cuadrada como un entero, y la trunca en lugar de redondearla. Esto z es controlado por los bits 10 y 11 de la palabra de control. Estos bits se llaman los bits RC (Raunding Control). Si ellos son ambos 0 (el defecto) el coprocesador redondea cuando convierte a entero. Si ellos son ambos 1, el coprocesador trunca las conversiones enteras. Observe que la rutina se cuida de guardar la palabra de control original y restaurarla antes de retornar. A continuacin el programa en C: o fprime.c

6.3. EL COPROCESADOR NUMERICO #include <stdio.h> #include <stdlib.h> / funcin nd primes o Halla los unmeros primos indicados Parmetros: a a arreglo que almacena los primos n cuntos primos encontrar a / extern void nd primes ( int a , unsigned n ); int main() { int status ; unsigned i; unsigned max; int a; printf (Cuntos primos desea encontrar Ud.? ); a scanf( %u, &max); a = calloc ( sizeof (int ), max); if ( a ) { nd primes (a,max); / imprime los 20 ltimos primos encontrados / u for( i = ( max > 20 ) ? max 20 : 0; i < max; i++ ) printf ( %3d %d\n, i+1, a[i]); free (a ); status = 0;

139

} else { fprintf ( stderr , No se puede crera el arreglo de %u ints\n, max); status = 1; } return status ; }

140

CAP ITULO 6. PUNTO FLOTANTE fprime.c A continuacin la rutina en ensamblador: o

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38

prime2.asm segment .text global _find_primes ; ; function find_primes ; Encuentra el nmero indicado de primos u ; Parmetros: a ; array - arreglo que almacena los primos ; n_find - cuntos primos encontrar a ; Prototipo de C ;extern void find_primes( int * array, unsigned n_find ) ; %define array ebp + 8 %define n_find ebp + 12 %define n ebp - 4 ; nmero de primos a encontrar u %define isqrt ebp - 8 ; el piso de la raz cudrada de guess %define orig_cntl_wd ebp - 10 ; palabra original de control %define new_cntl_wd ebp - 12 ; nueva palabra de control _find_primes: enter push push fstcw mov or mov fldcw mov mov mov mov mov

12,0 ebx esi word [orig_cntl_wd] ax, [orig_cntl_wd] ax, 0C00h [new_cntl_wd], ax word [new_cntl_wd] esi, [array] dword [esi], 2 dword [esi + 4], 3 ebx, 5 dword [n], 2

; Hace espacio para las variables locales ; guarda las posibles variables registro

; toma la palabra de control actual ; fija el redondeo de los bits a 11 (truncar)

; ; ; ; ;

esi apunta a array array[0] = 2 array[1] = 3 ebx = guess = 5 n = 2

; ; Este bucle externo encuentra un nuevo primo en cada iteracin, o ; que se a~ade al final del arreglo. A diferencia del primer programa de n

6.3. EL COPROCESADOR NUMERICO


39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80

141

; ; ; ;

primos, esta funcin no determina los primos dividiendo por todos o los nmeros impares. Slo divide por los nmeros primos que ya se u o u han encontrado. (Esta es la razn por la cual ellos se han almacenado o en el arreglo)

while_limit: mov cmp jnb mov push fild fsqrt fistp

eax, [n] eax, [n_find] short quit_limit ecx, 1 ebx dword [esp] pop ebx dword [isqrt]

; while ( n < n_find )

; ecx se usa como ndice del arreglo ; almacena guess en la pila ; carga guess en la pila del coprocesador ; saca guess de la pila ; halla sqrt(guess) ; isqrt = floor(sqrt(quess))

; Este b1ucle interno divide guess por los nmeros primos ya encontrados u ; hasta que encuentre un factor primo de guess (que significa que guess ; no es primo) o hasta que nmero primo a dividir sea ms grande que u a ; floor(sqrt(guess)) ; while_factor: mov eax, dword [esi + 4*ecx] ; eax = array[ecx] cmp eax, [isqrt] ; while ( isqrt < array[ecx] jnbe short quit_factor_prime mov eax, ebx xor edx, edx div dword [esi + 4*ecx] or edx, edx ; && guess % array[ecx] != 0 ) jz short quit_factor_not_prime inc ecx ; intenta el prximo primo o jmp short while_factor ; ; found a new prime ! ; quit_factor_prime: mov eax, [n] mov dword [esi + 4*eax], ebx inc eax mov [n], eax

; suma al final de arreglo ; inc n

142
81 82 83 84 85 86 87 88 89 90 91 92 93

CAP ITULO 6. PUNTO FLOTANTE

quit_factor_not_prime: add ebx, 2 jmp short while_limit quit_limit: fldcw pop pop leave ret word [orig_cntl_wd] esi ebx

; intenta con el impar siguiente

; restaura la palabra de control ; restaura las variables de registro

prime2.asm

6.3. EL COPROCESADOR NUMERICO

143

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

global _dmax segment .text ; function _dmax ; retorna el mayor de dos argumentos double ; Prototipo de C ; double dmax( double d1, double d2 ) ; Parmetros: a ; d1 - primer double ; d2 - segundo double ; Valor de retorno: ; El mayor de d1 y d2 (en ST0) %define d1 ebp+8 %define d2 ebp+16 _dmax: enter 0, 0 fld fld fcomip jna fcomp fld jmp d2_bigger: exit: leave ret qword qword st1 short st0 qword short [d2] [d1] d2_bigger [d1] exit ; pop d2 de la pila ; ST0 = d1 ; si d2 is max, no hace nada

; ST0 = d1, ST1 = d2 ; ST0 = d2

Figura 6.7: Ejemplo de FCOMIP

144

CAP ITULO 6. PUNTO FLOTANTE

1 2 3 4 5 6 7 8

segment .data x dq five dw

2.75 5

; convertido a formato double

segment .text fild dword [five] fld qword [x] fscale

; ST0 = 5 ; ST0 = 2.75, ST1 = 5 ; ST0 = 2.75 * 32, ST1 = 5

Figura 6.8: Ejemplo de FSCALE

Cap tulo 7

Estructuras y C++
7.1.
7.1.1.

Estructuras
Introduccin o

Las estructuras se usan en C para agrupar datos relacionados en una variable compuesta. Esta tcnica tiene varias ventajas: e 1. Clarica el cdigo mostrando que los datos denidos en la estructura o estn relacionados a ntimamente. 2. Simplica el paso de datos a las funciones. En lugar de pasar muchas variables separadas, ellas se pueden pasar como uno una variable. 3. Incrementar la localidad del cdigo. o
1

Desde el punto de vista del ensamblador una estructura se puede considerar como un arreglo con elementos de diferente tamao. Los elementos de n los arreglos reales son siempre del mismo tipo y mismo tamao. Esta caracn ter stica es la que le permite a uno calcular la direccin de cualquier elemento o conociendo la direccin de inicio del arreglo, el tamao de los elementos y o n el ndice del elemento que se desea. A structures elements do not have to be the same size (and usually are not). Because of this each element of a structure must be explicitly specied and is given a tag (or name) instead of a numerical index. En ensamblador los elementos de una estructura se accedern de una a manera parecida que los elementos de un arreglo. Para acceder a un elemento uno debe conocer la direccin de inicio de la estructura y el desplazamiento o relativo desde el comienzo de la estructura, sin embargo, a diferencia de
1 vea la seccin de manejo de memoria virtual de cualquier libro de texto de sistemas o operativos para la discusin del trmino. o e

145

146

CAP ITULO 7. ESTRUCTURAS Y C++ Oset 0 2 6 z Figura 7.1: Estructura S Element x y

los arreglos donde este desplazamiento se puede calcular por el ndice del elemento de una estructura el compilador le asigna un desplazamiento. Por ejemplo considere la siguiente estructura: struct S { short int x ; int y; double z; }; / entero de 2 bytes / / entero de 4 bytes / / oat de 8 bytes /

La Figura 7.1 muestra cmo una variable de tipo S podr verse en la o a memoria del computador. La norma ANSI dicta que los elementos de una estructura son colocados en memoria en el mismo orden que ellos son denidos en la estructura. Tambin dicta que el primer elemento est al comienzo e a de la estructura (desplazamiento cero). Tambin dene otro macro util en e el archivo de cabecera stddef.h llamado offsetof(). Este macro calcula y retorna el desplazamiento de cualquier elemento de una estructura. Este macro toma dos parmetros, el primero es el nombre de tipo de la estructura, a el segundo del nombre del elemento al cual encontrarle el desplazamiento. As el resultado de offsetof(S,y) podr ser 2 segn la Figura 7.1. a u

7.1.2.

Alineamiento de la memoria

Si uno usa el macro offsetof para encontrar el desplazamiento de y usando el compilador gcc uno encontrar que retorna 4 y no 2. Por qu? a e Recuerde que una direccin Porque gcc (y muchos otros compiladores) alinean por defecto las variables o est en los l a mites de una a los l mites de palabras dobles. En el modo protegido de 32 bits, la CPU palabra doble si es divisible lee la memoria ms rpido si el dato condensa en el l a a mite de una palabra por cuatro. doble. La Figura 7.2 muestra cmo se ve realmente la estructura S usando o gcc. El compilador inserta dos bytes no usados en la estructura para alinear y(y z) en un l mite de palabra doble. Esto muestra por qu es buena idea e usar offsetof para calcular los desplazamientos en lugar de calcularlos uno mismo. Cuando se usan estructuras denidas en C.

7.1. ESTRUCTURAS Oset 0 2 4 8 z Figura 7.2: Estructura S Element x unused y

147

Claro est, sin la estructura solo se usa en ensamblador el programador a puede determinar el desplazamiento l mismo. Sin embargo si uno est ine a terfazando C y ensamblador muy importante que los dos cdigos estn de o e acuerdo en los desplazamientos de los elementos de la estructura. Una complicacin es que diferentes compiladores de C tienen desplazamientos difero entes de los elementos. Por ejemplo, como se ha visto, el compilador gcc crea una estructura S que se ve como la Figura 7.2; sin embargo, el compilador de Borland podr crear una estructura como la de la Figura 7.1. Los compia ladores de C suministran maneras de especicar el alineamiento usado para los datos. Sin embargo la norma ANSI C no espec ca cmo se hace esto y o as cada compilador lo har a su manera. , a El compilador gcc tiene un mtodo exible y complicado de especicar el e Alineamiento. El compilador le permite a uno especicar el alineamiento de cualquier tipo usando una sintaxis especial. Por ejemplo, la siguiente l nea: typedef short int unaligned int attribute (( aligned (1)));

Dene un nuevo tipo llamado unaligned int que se alinea en los l mites de los bytes (s el parntesis despus de attribute se necesitan). El , e e 1 en el parmetro de aligned se puede reemplazar con otras potencias de a dos para especicar otros alineamientos (2 para alineamiento de palabra, 4 para el alineamiento de palabras dobles). Si el elemento y de la estructura a fue cambiado para ser un tipo unaligned int, gcc podr colocar y en el desplazamiento 2. Sin embargo z podr estar a una distancia de 8 ya que las a palabras dobles tienen un alineamiento de palabra por defecto. La denicin o del tipo de z tendr que ser cambiada para colocarlos una distancia de 6. a El compilador gcc tambin permite embalar una estructura. Esto le dice e al compilador que use el m nimo de espacio para la estructura. La Figura 7.3 muestra cmo se podr escribir S de esta manera. De esta forma S usar o a a el menor nmero de bytes posibles, 14. u Los compiladores de Borland y Microsoft, soportan el mismo mtodo de e especicar el alineamiento usando la directiva #pragma.

148

CAP ITULO 7. ESTRUCTURAS Y C++

struct S { short int x ; / entero de 2 bytes / int y; / entero de 4 bytes / double z; / oat de 8 bytes / } attribute ((packed)); Figura 7.3: Estructura embalada usando gcc #pragma pack(push) / guarda el estado de alineamiento / #pragma pack(1) / establece el alieamiento de byte / struct S { short int x ; int y; double z; };

/ entero de 2 bytes / / entero de 4 bytes / / oat de 8 bytes /

#pragma pack(pop)

/ restaura el alineamiento original /

Figura 7.4: Estructura embalada usando Microsoft o Borland #pragma pack(1) La directiva anterior le dice al compilador que embale los elementos de estructuras en los l mites de los bytes (sin relleno extra). El uno se puede reemplazar con dos, cuatro, ocho o diez y seis para especicar el alineamiento en una palabra, palabra doble, palabra cudruple y prrafo respectivamente. a a Esta directiva tiene efecto hasta que se sobreescriba por otra directiva. Esto puede causar problemas ya que estas directivas se usan a menudo en los archivos de encabezamiento. Si el archivo de encabezamiento se incluye antes que otros archivos de encabezamiento con estructuras, esas estructuras pueden estar diferente que las que tendr por defecto. Esto puede an conducir un error muy dif de encontrar. Los mdulos de un programa cil o podr desordenar los elementos de las estructuras de otros mdulos. an o Hay una manera de evitar este problema. Microsoft y Borland tienen una manera de guardar el alineamiento y restaurarlo luego. La Figura 7.4 muestra cmo se har esto. o a

7.1.3.

Campos de bits

Los campos de bit le permiten a uno especicar miembros de una estructura que usa un nmero especicado de bits. El tamao de los bits no u n

7.1. ESTRUCTURAS struct S { unsigned unsigned unsigned unsigned };

149

f1 f2 f3 f4

: : : :

3; 10; 11; 8;

/ / / /

campo campo campo campo

de de de de

3 bits 10 bits 11 bits 8 bits

/ / / /

Figura 7.5: Ejemplo campo de bits tiene que ser mltiplo de 8. Un campo de bits miembro est denido cou a mo un unsigned int o un int con dos puntos y luego el tamao en bits. La n Figura 7.5 muestra un ejemplo. Esto dene una variable de 32 bits que se descompone en las siguientes partes: 8 bits f4 11 bits f3 10 bits f2 3 bits f1

El primer campo est asignado a los bits menos signicativos de esta palabra a doble. 2 Sin embargo, el formato no es tan simple, si uno mira cmo se almacenan o los bits en memoria. La dicultad ocurre cuando los campos de bits se salen de los l mites del byte. Porque los bytes en un procesador little endian se invertirn en memoria. Por ejemplo, los campos de la estructura S se vern a a en memoria. 5 bits 3 bits 3 bits 5 bits 8 bits 8 bits f2l f1 f3l f2m f3m f4 La etiqueta f2l se reere a los ultimos cinco bits (los 5 bits menos signica tivos) del campo f2. La etiqueta f2m se reere a los 5 bits ms signicativos a de f2. Las l neas verticales dobles muestran los l mites de los bytes. Si uno invierte todos los bytes, las partes de los campos f2 y f3 se colocarn en el a lugar correcto. La forma de la memoria f sica no es importante a menos que los datos se transeran a o fuera de nuestro programa (lo cual es muy poco comn u con campos de bits). es comn para dispositivos de hardware usar nmeros u u impares de bits donde los campos de bits son utiles para representarlos. Un ejemplo es SCSI: 3 Una orden de lectura directa para un dispositivo SCSI se especica enviando un mensaje de 6 bits al dispositivo con el formato especicado en la Figura 7.6. La dicultad de representar esto usando
2 Actualmente, la norma ANSI/ISO C le da al compilador alguna exibilidad de como se ordenan los bits exactamente. Sin embargo los compiladores populares (gcc, Microsoft y Borland ) usarn este orden en los campos. a 3 Small Computer System Interface, es una norma de la industria para discos duros y etc.

150 Byte \ Bit 0 1 2 3 4 5 7 6

CAP ITULO 7. ESTRUCTURAS Y C++ 5 4 3 2 1 0 Codigo de operacin (08h) o Unidad Lgica # o msb de LBA mitad de la direccin del bloque lgico o o lsb de direccin del bloque lgico o o Longitud de transferencia Control

Figura 7.6: Formato de la orden leer de SCSI campos de bits es la direccin del bloque lgico que abarca 3 bytes diferentes o o en la orden. De la Figura 7.6 uno ve que el dato se almacena en el formato big endian. La Figura 7.7 muestra una denicin que intenta trabajar con todos o los compiladores. Las dos primeras l neas denen un macro que retorna verdadero si el cdigo se compila con los compiladores de Borland o Microsoft. o La parte potencialmente confusa est en las l a neas 11 a 14. Primero uno podr sorprenderse de Por qu los bits lba mid y lba lsb estn denidos a e a separados y no en un solo campo de 16 bits? La razn es que el dato est en o a big endian. Un campo de 16 bits podr ser almacenado con el orden little a endian por el compilador. Ahora, los campos lba msb y logical unit parecen invertidos; sin embargo esto no es el caso. Ellos tienen que colocarse en este orden. La Figura 7.8 muestra cmo se colocan los campos en una entidad de o 48 bits (los l mites del byte estn una vez ms marcados con l a a neas dobles). Cuando esto se almacena en memoria en el orden little endian, los bits se colocan en el orden deseado.(Figura 7.6) Para complicar las cosas ms, la denicin para SCSI read cmd no traa o baja correctamente en el compilador de Microsoft. Si se evala la expresin u o sizeof (SCSI read cmpl), Microsoft C retornar 8 no 6. Esto es porque el coma pilador de Microsoft usa el tipo de campo de datos para determinar cmo o colocar los bits. Ya que los bits estn denidos como de tipo unsigned, el a compilador rellena dos bytes al nal de la estructura para tener un mltipu lo de palabras dobles. Esto se puede solucionar haciendo todos los campos unsigned short. Ahora el compilador de Microsoft no necesita agregar ningn relleno ya u que los 6 bytes son un mltiplo de una palabra 4 Los otros compiladores u tambin trabajarn correctamente con este cambio. La Figura 7.9 muestra e a otra denicin que trabaja con los tres compiladores. Se evitan todos los o problemas usando campos de tipo unsigned char. El lector no deber entristecerse si encuentra la divisin anterior cona o fusa. Es confusa! El autor a menudo encuentra menos confuso evitar los
4 Mezclar diferentes tipos de campos de bits produce un comportamiento muy confuso. Se invita al lector a experimentar al respecto

7.1. ESTRUCTURAS #dene MS OR BORLAND (dened( BORLANDC ) \ || dened ( MSC VER)) #if MS OR BORLAND # pragma pack(push) # pragma pack(1) #endif struct SCSI read cmd { unsigned opcode : 8; unsigned lba msb : 5; unsigned logical unit : 3; unsigned lba mid : 8; / bits de la mitad / unsigned lba lsb : 8; unsigned transfer length : 8; unsigned control : 8; } #if dened( GNUC ) attribute ((packed)) #endif ; #if MS OR BORLAND # pragma pack(pop) #endif Figura 7.7: Estructura del formato de la orden leer de SCSI

151

campos de bits y en su lugar usar las operaciones entre bits para examinar y modicar los bits manualmente.

7.1.4.

Usando estructuras en ensamblador

Como se discuti antes, acceder a una estructura en ensamblador es muy o parecido a acceder a un arreglo. Por ejemplo considere cmo uno podr o a escribir una rutina en ensamblador que iniciar con cero el elemento y de a una estructura S. Asumiendo que el prototipo de la rutina ser a: void zero y ( S s p ); La rutina en ensamblador ser a:

152 8 bits control 8 bits transfer length

CAP ITULO 7. ESTRUCTURAS Y C++ 8 bits lba lsb 8 bits lba mid 3 bits logical unit 5 bits lba msb 8 bits opcode

Figura 7.8: Ordenamiento de los campos de SCSI read cmd struct SCSI read cmd { unsigned char opcode; unsigned char lba msb : 5; unsigned char logical unit : 3; unsigned char lba mid; / bits de la mitad/ unsigned char lba lsb ; unsigned char transfer length ; unsigned char control; } #if dened( GNUC ) attribute ((packed)) #endif ; Figura 7.9: Estructura alterna del formato de la orden leer de SCSI %define _zero_y: enter mov mov leave ret y_offset 4

1 2 3 4 5 6 7 8

0,0 eax, [ebp + 8]

; toma s_p (apuntador a la estructura) ; de la pila dword [eax + y_offset], 0

C le permite a uno pasar estructuras por valor a una funcin; sin embargo o esto la mayor de las veces es mala idea. Cuando se pasa por valor, todos a los datos de la estructura se deben copiar en la pila y luego tra dos por la rutina. Es mucho ms eciente pasar un apuntador a una estructura. a C Tambin permite que un tipo de estructura se use como un valor de e retorno. Obviamente la estructura no se puede retornar en el registro EAX. Cada compilador maneja la situacin diferente. Una solucin comn es que o o u el compilador usa esto para internamente reescribir la funcin para que tome o una estructura como parmetro. El apuntador se usa para colocar el valor a de retorno en una estructura denida fuera de la rutina llamada. La mayor de los ensambladores (incluido NASM) tiene un soporte ema potrado para denir estructuras en su cdigo de ensamblador. Consulte su o

7.2. ENSAMBLADOR Y C++ #include <stdio.h> void f ( int x ) { printf ( %d\n, x); } void f ( double x ) { printf ( %g\n, x); } Figura 7.10: Dos funciones f() documentacin para los detalles. o

153

7.2.

Ensamblador y C++

El lenguaje de programacin C++ es una extensin del lenguaje C. o o Muchas de las reglas bsicas para interfazar C y ensamblador tambin son a e vlidas para C++. Sin embargo algunas reglas necesitan ser modicadas. a Tambin algunas de las extensiones de C++ son fciles de entender con e a conocimiento del lenguaje ensamblador. Esta seccin asume un conocimieno to bsico de C++. a

7.2.1.

Manipulacin de la sobrecarga de nombres o

C++ permite que se denan diferentes funciones (y funciones miembro de clases) con el mismo nombre. Cuando ms de una funcin comparte el a o mismo nombre, se dice que la funcin est sobrecargada. Si se denen dos o a funciones con el mismo nombre en C, el encadenador producir un error a porque l encontrar dos deniciones para el mismo s e a mbolo en los archivos objeto que est encadenando. Por ejemplo considere el cdigo de la Figua o ra 7.10. El cdigo en ensamblador equivalente denir 2 etiquetas f que o a obviamente ser un error. a C++ usa el mismo proceso de encademanmiento que C, pero evita este error haciendo una manipulacin de nombres o modicando el s o mbolo usado para etiquetar la funcin. De alguna manera C usa la manipulacin o o de nombres tambin. Aade un guin bajo a la etiqueta de la funcin de e n o o C cuando se crea la etiqueta para la funcin. Sin embargo C manipular el o a nombre de ambas funciones en la Figura 7.10 de la misma manera y produce

154

CAP ITULO 7. ESTRUCTURAS Y C++

un error. C++ usa un proceso de manipulacin ms sosticado que produce o a dos etiquetas diferentes para las funciones. Por ejemplo, a la primera funcin o de la Figura 7.10 le podr asignar DJGPP la etiqueta f Fi y a la segunda a funcin, f Fd. Esto evita errores de encadenamiento. o Desafortunadamente no est normalizado cmo se manejan los nombres a o en C++ y cada compilador trata los nombres a su manera. Por ejemplo Borland C++ podr usar las etiquetas @f$qi y @f$qd para las dos funciones a de la Figura 7.10. Sin embargo, las reglas no son completamente arbitrarias. El nombre manipulado codica la rma de la funcin. La rma de una o funcin est denida por el orden y el tipo de sus parmetros. Observe que o a a la funcin que toma un solo parmetro int tiene una i al nal de la etiqueta o a manipulada (para DJGPP y Borland) y la que toma un argumento double tiene una d al nal de la etiqueta manipulada. Si tuviera una funcin llamada o f con el prototipo. void f ( int x , int y , double z); DJGPP podr manipular el nombre para ser f Fiid y Borland a @f$qiid. a El tipo de retorno de una funcin no hace parte de la rma de la funo cin y no se codica en el nombre manipulado. Este hecho explica una regla o de sobre carga en C++. Slo funciones cuya rma es unica se pueden soo brecargar. Como uno puede ver, si dos funciones, con el mismo nombre y rma, estn denidas en C++ ellas producirn el mismo nombre manipua a lado y crearn un error de encadenamiento. Por defecto C++ manipula los a nombres de las funciones as no estn sobrecargadas. Cuando se compila un e archivo el compilador no tiene manera de saber si una funcin en particular o est sobrecargada o no, as que manipula todos los nombres. De hecho tama bin manipula los nombres de las variables globales codicando el tipo de e variable de manera similar a la rma de las funciones. As uno puede denir una variable global en un archivo como de cierto tipo y entonces intentar usarla en otro archivo como de un tipo errneo, se producir un error del o a encadenador. Esta caracter stica de C++ es conocida como encadenamiento seguro de tipos. Tambin se expone a otro tipo de error, prototipos incone sistentes. Esto ocurre cuando la denicin de una funcin en un mdulo no o o o concuerda con el prototipo usado por otro mdulo. En C esto puede ser o un problema muy dif de depurar. C no atrapa este error. El programa cil compilar y encadenar, pero tendr un comportamiento indenido cuando a a a se invoca el cdigo colocando diferentes tipos de datos en la pila de los que o la funcin espera. En C++, esto producir un error de encadenado. o a Cuando el compilador de C++ est examinando la sintaxis un llamado a a funcin, l busca una funcin donde emparejen los tipos de los argumentos o e o pasados a la funcin. 5 Si se empareja entonces crea un CALL a la funcin o o
5

El emparejamiento no tiene que ser exacto, el compilador considerar las conversiones a

7.2. ENSAMBLADOR Y C++

155

correcta usando las reglas de manipulacin del compilador. o Ya que cada compilador usa sus reglas de manipulacin de nombres, el o cdigo de C++ compilado con compiladores diferentes no es posible unirlo. o Este hecho es importante cuando se considera usar una biblioteca de C++ precompilada. Si uno desea escribir una funcin en ensamblador que ser uso a ada con cdigo C++, debe conocer las reglas de manipulacin de nombres o o del compilador (o usar la tcnica explicada abajo). e El estudiante astuto puede preguntar si el cdigo de la Figura 7.10 trao bajar como se espera. Ya que C++ manipula los nombres de todas las a funciones, entonces la funcin printf ser manipulada y el compilador no o a producir un CALL a la etiqueta printf. Esto es un vlido, si el prototipo a a para printf fue colocado simplemente al principio del archivo, esto podr a pasar. El prototipo es: int printf ( const char , ...); DJGPP podr manipular esto como printf FPCce (la F es por funcin, P a o por apuntador, C por constante, c por char y e por elipsis). Esto podr no a llamar la funcin printf de la biblioteca de C. Claro est, debe haber alguna o a forma para el cdigo de C++ llamar cdigo de C. Esto es muy importante o o ya que hay una gran cantidad de cdigo de C util. Adems permitirle a uno o a llamar cdigo de C heredado, C++ tambin le permite a uno llamar cdigo o e o de ensamblador usando las convenciones normales de llamado. C++ extiende la palabra clave extern para permitir especicar que la funcin o variable global que modica usa las convenciones de llamado de o C. En la terminolog de C++, las variables y funciones globales usan el a encadenamiento de C. Por ejemplo para declarar que printf tenga el encadenamiento de C, use el prototipo: extern C int printf ( const char , ... ); Esto le dice al compilador que no use las reglas de manipulacin de nombres o en esta funcin, pero en su lugar usar las reglas de C. Sin embargo, haciendo o esto, la funcin printf no se puede sobrecargar. Esto suministra una manera o fcil de interfazar C++ y ensamblador , denir la funcin para que use el a o encadenamiento de C y la convencin de llamado de C. o Por conveniencia C++ tambin permite el encadenamiento de un bloque e de funciones y variables globales a ser denidas. El bloque se enmarca por las t picas llaves: extern C { / encadenamiento tipo C a variables globales y prototipos de funciones / }
de tipos de los argumentos. Las reglas para este proceso estn ms all del alcance de este a a a libro. Consulte un libro de C++ para los detalles.

156 void f ( int & x ) { x++; } int main() { int y = 5; f (y );

CAP ITULO 7. ESTRUCTURAS Y C++ // el & denota una referencia

// se pasa la referencia a y , obeserve que // no hay & ac a printf ( %d\n, y); // imprime 6! return 0;

} Figura 7.11: Ejemplo de referencia

Si uno examina los archivos de encabezado de ANSI C que vienen con los compiladores de C/C++ de hoy d en ellos se encontr al principio de a, a cada archivo de cabecera: #ifdef cplusplus extern C { #endif Y una construccin parecida cerca del nal conteniendo el n de la llave. o Los compiladores de C++ denen el macro cplusplus (con dos guiones bajos adelante). La porcin de cdigo anterior encierra todo el archivo de o o cabecera en un bloque extern C si el archivo de cabecera es compilado como C++, pero no hace nada si es compilado como C (ya que el compilador de C dar un error de sintaxis para extern C). Esta misma tcnica puede ser a e usada por cualquier programador para crear un archivo de cabecera para las rutinas de ensamblador que pueden ser usadas con C o C++.

7.2.2.

Referencias

Las referencias son otra caracter stica nueva C++. Ellas le permiten a uno pasar parmetros a funciones sin usar apuntadores expl a citamente. Por ejemplo, considere el cdigo de la Figura 7.11. Actualmente los parmetros o a por referencia son muy simples, ellos realmente son tan solo apuntadores. El compilador solo le oculta esto al programador (tal como los compiladores de Pascal implementan los parmetros var como apuntadores). Cuando el a compilador genera el ensamblador para el llamado a la funcin en la l o nea 7, pasa la direccin de y. Si uno escribi la funcin f en ensamblador, ella o o o

7.2. ENSAMBLADOR Y C++ se deber comportar como si el prototipo fuera6 : a void f ( int xp);

157

Las referencias son slo una conveniencia que es especialmente util para o la sobrecarga de operadores. Esta es otra caracter stica de C++ que le permite a uno denir el signicado para los operadores en tipos de estructuras o clases. Por ejemplo, un uso comn es denir el operador ms (+) para unir u a objetos tipo cadena. As si a y b fueran cadenas, a + b deber retornar la , a unin de las cadenas a y b, C++ actualmente llama una funcin para hacer o o esto (de hecho, esta expresin podr ser reescrita en la notacin de funcin o a o o como operator +(a,b)). Por eciencia uno podr querer pasar la direccin a o de los objetos cadena en lugar de pasarlos por valor. Sin referencias esto se podr hacer como operator +(&a,&b), pero esto requerir escribir en la a a sintaxis del operador como &a + &b. Esto ser muy complicado y confuso. a Sin embargo, usando referencias, uno puede escribir a + b que se ve muy natural.

7.2.3.

Funciones inline

Las funciones inline son otra caracter stica de C++7 . Las funciones inline son hechas para reemplazar, los macros basados en el procesador que toman parmetros que son muy propensos a tener errores. Recuerde de C, a que escribir un macro que eleva al cuadrado un nmero podr verse como: u a #dene SQR(x) ((x)(x)) Porque el preprocesador no entiende C hace simples sustituciones, se requieren los parntesis para calcular la respuesta correcta en la mayor de e a los casos. Sin embargo, an esta versin no dar la respuesta correcta para u o a SQR(X++). Los macros se usan porque ellos eliminan el trabajo extra de hacer un llamad a funcin para una funcin elemental. Como se demostr en el cap o o o tulo de subprogramas realizar un llamado a funcin involucra varios pasos. Por o cada funcin elemental, el tiempo que se toma en llamar la funcin puede o o ser mayor que el tiempo necesario en realizar las operaciones en la funcin. o La funcin inline es una manera mucho ms amigable de escribir cdigo que o a o se vea como una funcin normal, pero que no hace el bloque de cdigo para o o CALL. En lugar de ello los llamados a funciones inline son reemplazados por el cdigo que realiza la funcin. C++ le permite a una funcin ser inline o o o colocando la palabra inline clave al frente de la denicin de la funcin. o o
Claro est ella podr esperar declarar la funcin con encadenamiento C para evitar a a o la manipulacin de nombres discutida en la seccin 7.2.1 o o 7 Los compiladores de C a menudo soportan esta caracter stica como una extensin de o ANSI C
6

158 inline int inline f ( int x ) { return xx; } int f ( int x ) { return xx; } int main() { int y , x = 5; y = f(x ); y = inline f (x ); return 0; }

CAP ITULO 7. ESTRUCTURAS Y C++

Figura 7.12: ejemplo inline Por ejemplo considere las funciones declaradas en la Figura 7.12. El llamado a la funcin f en la l o nea 10 hace un llamado normal a una funcin (en o ensamblador, asumiendo que x est en la direccin ebp-8 y y est en ebp-4: a o a
1 2 3 4

push call pop mov

dword [ebp-8] _f ecx [ebp-4], eax

Sin embargo, el llamado a la funcin inline f de la l o nea 11 podr verse a como:


1 2 3

mov imul mov

eax, [ebp-8] eax, eax [ebp-4], eax

En este caso hay dos ventajas de las funciones inline. Primero la funcin o en l nea es rpida. No se colocan los parmetros en la pila, no se crea el a a macro de la pila ni se destruye, no se hace el salto. Segundo el llamado de las funciones en l nea usa menos cdigo. Este ultimo punto es verdad para o este ejemplo pero no siempre es cierto. La principal desventaja de las funciones inline es que el cdigo en l o nea no se encadena y as el cdigo de una funcin en l o o nea debe estar disponible en todos los archivos que la usen. El ejemplo anterior prueba esto. El llamado de una funcin normal solo requiere el conocimiento de los parmetros, o a

7.2. ENSAMBLADOR Y C++ class Simple { public: Simple (); Simple (); int get data () const; void set data ( int ); private : int data ; }; Simple :: Simple() { data = 0; } Simple :: Simple() { / null body / } int Simple :: get data () const { return data ; } void Simple :: set data ( int x ) { data = x; } Figura 7.13: Una clase simple de C++

159

// Construcctor por defecto // destructor // funciones miembro

// datos miembro

el tipo de valor de retorno, la convencin de llamado y el nombre de la etio queta para la funcin. Toda esta informacin est disponible en el prototipo o o a de la funcin. Sin embargo al usar funciones inline se requiere conocer todo o el cdigo de la funcin. Esto signica que si cualquier parte de una funcin o o o en l nea se cambia, todos los archivos fuentes que usen la funcin deben o ser recompilados. Recuerde que para las funciones no inline, si el prototipo cambia, a menudo los archivos que usan la funcin no necesitan ser recompio lados. Por todas estas razones, el cdigo de las funciones en l o nea se colocan normalmente en los archivos de cabecera. Esta prctica es contraria a la a regla normal dura y rpida de C que las instrucciones de cdigo ejecutable a o nunca se colocan en los archivos de cabecera.

7.2.4.

Clases

Una clase en C++ describe un tipo de objeto. Un objeto tiene como miembros a datos y funciones8 . En otras palabras, es una estructura con
8

a menudo llamados en C++ funciones miembro o ms generalmente mtodos. a e

160

CAP ITULO 7. ESTRUCTURAS Y C++

void set data ( Simple object , int x ) { object>data = x; } Figura 7.14: Versin de C de Simple::set data() o
1 2 3 4 5 6 7 8 9 10

_set_data__6Simplei: push ebp mov ebp, esp mov mov mov leave ret eax, [ebp + 8] edx, [ebp + 12] [eax], edx

; nombre manipulador

; eax = apuntador al objeto (this) ; edx = parmetro entero a ; data tiene un desplazamiento de 0

Figura 7.15: Salida del compilador para Simple::set data( int ) datos y funciones asociadas a ella. Considere la clase denida en la Figura 7.13. Una variable de tipo Simple se podr ver tal como una estructura a normal en C con un solo int como miembro. Las funciones no son almacenadas en la memoria asignada a la estructura. Sin embargo las funciones miembro son diferentes de las otras funciones . Ellas son pasadas como un parmetro oculto. Este parmetro es un apuntador al objeto al cual la a a funcin pertenece. o Por ejemplo considere el mtodo set data de la clase simple de la Figue ra 7.13. Si se escribi en C se ver como una funcin que fue pasada expl o a o citamente un apuntador al objeto. Como muestra la Figura 7.14. La opcin -S o en el compilador DJGPP (y en los compiladores gcc y Borland tambin) le e dice al compilador producir un archivo ensamblador conteniendo el lenguaje ensamblador equivalente para el cdigo producido. Para DJGPP y gcc o el archivo ensamblador naliza con una extensin o .s y desafortunadao mente usa la sintaxis del lenguaje ensamblador AT&T que es diferente de la sintaxis de NASM y MASM. 9 (Los compiladores de Borland y Microsoft
9 El sistema de compilador gcc incluye su propio ensamblador llamado gas. El ensamblador gas usa la sintaxis AT&T y as el compilador produce el cdigo o en el formato de gas. Hay varias pginas web que discuten las diferencias ena tre los formatos INTEL y AT&T. Hay tambin un programa m e nimo llamado a2i (http://www.multimania.com/placr/a2i.html), que convierte del formato AT&T al for-

Ahora, C++ usa la palabra clave this para acceder al apuntador de un objeto que est dentro de la funcin a o miembro.

7.2. ENSAMBLADOR Y C++

161

generan un archivo con extensin .asm usando la sintaxis MASM). La Figuo ra 7.15 muestra la salida de DJGPP convertida a la sintaxis de MASM con comentarios aadidos para claricar el propsito de las instrucciones. En n o la primera l nea, observe que el mtodo set data se le asigna la etiqueta e manipulada que codica el nombre del mtodo, el nombre de la clase y los e parmetros. El nombre de la clase se codica porque otras clases podr a an tener un mtodo llamado set data y los dos mtodos deben tener etiqueta e e diferente. Los parmetros se codican tal que la clase pueda sobre cargar el a mtodo set data para tomar otros parmetros como es normal en las fune a ciones C++. Sin embargo, tal como antes, cada compilador codicar esta a informacin diferente en la etiqueta manipulada. o Luego en las l neas 2 y 3, aparece el prlogo t o pico de la funcin. En la o l nea 5 se almacena el primer parmetro de la pila en EAX. Este no es el a parmetro X, es el parmetro oculto. 10 que apunta al objeto sobre el cual a a acta. La l u nea 6 almacena el parmetro x en EDX y la l a nea 7 almacena EDX en la palabra doble a la que apunta EAX. Esto es el miembro data del objeto Simple sobre el cual acta, que es el unico dato en la clase, se almacena con u un desplazamiento cero en la estructura Simple. Ejemplo Esta seccin usa las ideas del cap o tulo para crear una clase de C++ que representa un entero sin signo de tamao arbitrario. Ya que el entero puede n ser de cualquier tamao, ser almacenado en un arreglo de enteros sin signo n a (palabras dobles). Se puede hacer de cualquier tamao usando memoria n dinmica. Las palabras dobles se almacenan en orden inverso. 11 (la palabra a doble menos signicativa es la palabra doble que est en la posicin 0). a o 12 . El tama o La Figura 7.16 muestra la denicin de la clase Big int. o n n de un Big int se mide por el tamao del arreglo sin signo que es usado para almacenar este dato. El dato miembro size de la clase se le asigna un desplazamiento de cero y el miembro number se le asigna un desplazamiento de 4. Para simplicar este ejemplo, slo instancias objeto con el mismo tamao o n se pueden sumar o restar la una de la otra. La clase tiene tres constructores: El primero (l nea 9) inicia la instancia de la clase usando un entero sin signo normal; el segundo (l nea 18) inicia la instancia usando una cadena que contiene un valor en hexadecimal. El
mato NASM 10 Como es normal nada est oculto en el cdigo ensamblador a o 11 Por qu? Porque las operaciones de adicin siempre se comenzarn a procesar al e o a inicio del arreglo y se mover hacia adelante. a 12 Vea el cdigo fuente example para el cdigo completo de este ejemplo. El texto solo o o se referir a alguna parte del cdigo. a o

162

CAP ITULO 7. ESTRUCTURAS Y C++

class Big int { public: / Parmetros: a size ntamao del entero expresado como el unmero de unsigned int normales initial value valor inicial de Big int como un unsigned int normal / explicit Big int ( size t size , unsigned initial value = 0); / Parmetros a size ntamao del entero expresado como el unmero de unsigned int normales initial value valor inicial de Big int como la cadena que almacena la representacin hexadecimal del valor . o / Big int ( size t size , const char initial value ); Big int ( const Big int & big int to copy ); Big int (); // retorna el ntamao de Big int (en trminos de unsigned int ) e size t size () const; const Big int & operator = ( const Big int & big int to copy ); friend Big int operator + ( const Big int & op1, const Big int & op2 ); friend Big int operator ( const Big int & op1, const Big int & op2); friend bool operator == ( const Big int & op1, const Big int & op2 ); friend bool operator < ( const Big int & op1, const Big int & op2); friend ostream & operator << ( ostream & os, const Big int & op ); private : size t size ; // ntamao del arreglo sin signo unsigned number ; // apuntador al arreglo sin singno que // almacena el valor }; Figura 7.16: Denicin de la clase Big int o

7.2. ENSAMBLADOR Y C++

163

// Prototipos para las rutinas en ensamblador extern C { int add big ints ( Big int & res , const Big int & op1, const Big int & op2); int sub big ints ( Big int & res , const Big int & op1, const Big int & op2); } inline Big int operator + ( const Big int & op1, const Big int & op2) { Big int result (op1. size ()); int res = add big ints ( result , op1, op2); if ( res == 1) throw Big int :: Overow (); if ( res == 2) throw Big int :: Size mismatch(); return result ; } inline Big int operator ( const Big int & op1, const Big int & op2) { Big int result (op1. size ()); int res = sub big ints ( result , op1, op2); if ( res == 1) throw Big int :: Overow (); if ( res == 2) throw Big int :: Size mismatch(); return result ; } Figura 7.17: Cdigo de la aritmtica de la clase Big int o e

164

CAP ITULO 7. ESTRUCTURAS Y C++

tercer constructor (l nea 21) es el constructor copia. Esta discusin se enfoca en cmo trabajan los operadores de la suma y de o o la resta ya que es all donde se usa el lenguaje ensamblador. La Figura 7.17 muestra las partes relevantes del archivo de encabezado para estos operadores. Ellas muestran cmo los operadores se usab para llamar las rutinas o en ensamblador. Ya que cada compilador usa sus reglas de manipulacin o de nombres radicalmente diferentes para las funciones de los operadores, las funciones en l nea de los operadores se usan para establecer el encadenamiento de rutinas en ensamblador. Esto hace relativamente fcil portar a a diferentes compiladores y es tan rpido como los llamados directos. Esta a tcnica tambin elimina la necesidad de lanzar una excepcin desde ensame e o blador. Por qu se usa el ensamblador ac despus de todo? Recuerde que realiza e a e aritmtica de precisin mltiple, el carry se debe mover de una palabra e o u doble para sumar la siguiente palabra doble. C++ (y C) no le permiten al programador acceder a la bandera de carry de la CPU. Realizar la suma podr solo hacerlo recalculando la bandera de carry y condicionalmente a sumar esto a la siguiente palabra doble. Es mucho ms eciente escribir a el cdigo en ensamblador donde se puede acceder a la bandera de carry y o usando la instruccin ADC que automticamente suma la bandera de carry. o a Por brevedad, slo se discutir ac la rutina de ensamblador add big ints. o a a Sigue el cdigo de esta rutina (de big math.asm): o big math.asm segment .text global add_big_ints, sub_big_ints %define size_offset 0 %define number_offset 4 %define EXIT_OK 0 %define EXIT_OVERFLOW 1 %define EXIT_SIZE_MISMATCH 2 ; Parmetros para las rutinas add y sub a %define res ebp+8 %define op1 ebp+12 %define op2 ebp+16 add_big_ints: push mov push push

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

ebp ebp, esp ebx esi

7.2. ENSAMBLADOR Y C++


20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61

165

push edi ; ; primero esi se~ala a op1 n ; edi se~ala a op2 n ; ebx se~ala a res n mov esi, [op1] mov edi, [op2] mov ebx, [res] ; ; Asegurarse que todos los 3 Big_Int tienen el mismo tama~o n ; mov eax, [esi + size_offset] cmp eax, [edi + size_offset] jne sizes_not_equal ; op1.size_ != op2.size_ cmp eax, [ebx + size_offset] jne sizes_not_equal ; op1.size_ != res.size_ mov ecx, ; ; ahora, los ; esi = ; edi = ; ebx = ; mov ebx, mov esi, mov edi, eax ; ecx = size of Big_ints

registrosse~alan a sus respectivos arreglos n op1.number_ op2.number_ res.number_ [ebx + number_offset] [esi + number_offset] [edi + number_offset] ; borra bandera de carry ; edx = 0

clc xor edx, edx ; ; addition loop add_loop: mov eax, [edi+4*edx] adc eax, [esi+4*edx] mov [ebx + 4*edx], eax inc edx loop add_loop jc ok_done: xor eax, eax overflow

; no altera la bandera de carry

; valor de retorno = EXIT_OK

166
62 63 64 65 66 67 68 69 70 71 72 73

CAP ITULO 7. ESTRUCTURAS Y C++

jmp done overflow: mov eax, EXIT_OVERFLOW jmp done sizes_not_equal: mov eax, EXIT_SIZE_MISMATCH done: pop edi pop esi pop ebx leave ret big math.asm Con la esperanza que la mayor del cdigo fuera claro al lector por a o ahora. Las l neas 25 a 27 almacenan los apuntadores a los objetos Big int pasados a la funcin en registro. Recuerde que las referencias realmente o son solo apuntadores. Las l neas 31 a 35 aseguran que los tamaos de los 3 n objetos de los arreglos sean iguales (Observe que el desplazamiento de size se aade al apuntador para acceder al dato miembro). Las l n neas 44 a 46 ajustan los registros para apuntar al arreglo usado por los objetos respectivos en lugar de los objetos en s mismos. (Una vez ms, el desplazamiento de a number se aade al apuntador del objeto). n El bucle en las l neas 52 a 57 suma los enteros almacenados en los arreglos sumando primero la palabra doble menos signicativa, luego la siguiente palabra doble y as sucesivamente. La suma se realiza en esta secuencia para la aritmtica de precisin extendida (vea la Seccin 2.1.5). La l e o o nea 59mexamina el desborde, si hay desborde la bandera de carry se deber a jar con la ultima suma de la palabra doble ms signicativa. Ya que las a palabras dobles en el arreglo se almacenan en el orden de little endian, el bucle comienza en el inicio del arreglo y se mueve adelante hasta el nal. La Figura 7.18 muestra un corto ejemplo del uso de la clase Big int. Observe que las constantes Big int se deben declarar expl citamente como en la l nea 16. Esto es necesario por dos razones. Primero, no hay un construcctor de conversin que convierta un unsigned int en un Big int. Segundo, o solo Big Int del mismo tamao se pueden sumar, esto hace la conversin n o problemtica ya que podr ser dif conocer qu tamao convertir a. Una a a cil e n implementacin ms sosticada de la clase deber permitir sumar objetos o a a de cualquier tamao. El autor no desea complicar este ejemplo implemenn tandolo ac. (sin embargo exhorta al lector a hacer esto). a

7.2.5.

Herencia y polimorsmo

La herencia le permite a una clase heredar los datos y mtodos de otra. e

7.2. ENSAMBLADOR Y C++

167

#include big int.hpp #include <iostream> using namespace std; int main() { try { Big int b(5,8000000000000a00b); Big int a(5,80000000000010230); Big int c = a + b; cout << a << + << b << = << c << endl; for ( int i =0; i < 2; i ++ ) { c = c + a; cout << c = << c << endl; } cout << c1 = << c Big int(5,1) << endl; Big int d (5, 12345678); cout << d = << d << endl; cout << c == d << (c == d) << endl; cout << c > d << (c > d) << endl; } catch( const char str ) { cerr << Caught: << str << endl; } catch( Big int :: Overow ) { cerr << Overow << endl; } catch( Big int :: Size mismatch ) { cerr << Size mismatch << endl; } return 0; } Figura 7.18: Uso simple de Big int

168

CAP ITULO 7. ESTRUCTURAS Y C++

#include <cstddef> #include <iostream> using namespace std; class A { public: void cdecl m() { cout << A::m() << endl; } int ad; }; class B : public A { public: void cdecl m() { cout << B::m() << endl; } int bd; }; void f ( A p ) { p>ad = 5; pem(); } int main() { A a; B b; cout << Tamao de a: << sizeof(a) n << Desplazamiento de ad: << osetof(A,ad) << endl; cout << Tamao de b: << sizeof(b) n << Desplazamiento de ad: << osetof(B,ad) << Desplazamiento de bd: << osetof(B,bd) << endl; f(&a); f(&b); return 0; } Figura 7.19: Herencia simple

7.2. ENSAMBLADOR Y C++


1 2 3 4 5 6 7 8 9 10 11

169 ; nombre de la funcin manipulador o

_f__FP1A: push mov mov mov mov push call add leave ret

ebp ebp, esp eax, [ebp+8] dword [eax], 5 eax, [ebp+8] eax _m__1A esp, 4

; eax apunta al objeto ; usar desplazamiento 0 para ad ; pasar la direccin del objeto a A::m() o ; nombre del mtodo manipulado para e A::m()

Figura 7.20: Cdigo en ensamblador para la herencia simple o class A { public: virtual void int ad; };

cdecl m() { cout << A::m() << endl; }

class B : public A { public: virtual void cdecl m() { cout << B::m() << endl; } int bd; }; Figura 7.21: Herencia polimrca o Por ejemplo consider el cdigo de la Figura 7.19. Ella muestra dos clases A o y B, donde la clase B hereda de A. La salida del programa es: Tama~o de a: 4 Desplazamiento de ad: 0 n Tama~o de b: 8 Desplazamiento de ad: 0 Desplazamiento de bd: 4 n A::m() A::m() Observe que los miembros ad de ambas clases (B hereda de A) estn en el a mismo desplazamiento. Esto es importante ya que la funcin f puede ser o pasada como un apuntador, a un objeto A de cualquier tipo derivado de A. La Figura 7.20 muestra el cdigo asm (editado) para la funcin (generada o o por gcc). Observe que en la salida del mtodo m de A fue llamado por los objetos e

170
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

CAP ITULO 7. ESTRUCTURAS Y C++

?f@@YAXPAVA@@@Z: push ebp mov ebp, esp mov mov mov mov mov push call add pop ret eax, [ebp+8] dword [eax+4], 5 ecx, [ebp + 8] edx, [ecx] eax, [ebp + 8] eax dword [edx] esp, 4 ebp

; p->ad = 5; ; ; ; ; ; ; ecx = p edx = apuntador a vtable eax = p empuja apuntador "this" llama primera funcin en vtable o limpia la pila

Figura 7.22: Cdigo en ensamblador para la funcin f() o o a y b. Desde ensamblador, uno puede ver que la llamada a A::m() est ema potrado en la funcin. Para programacin realmente orientada a objetos, el o o mtodo llamado depender de qu tipo de objeto se pasa a la funcin. Ese a e o to es conocido como polimorfismo. C++ tiene esta caracter stica apagada por defecto. Uno usa la palabra clave virtual para i habilitarla. La Figura 7.21 muestra cmo las dos clases ser cambiadas. Nada del otro cdio an o go necesita ser cambiado. El polimorsmo puede implementarse de muchas maneras. Desafortunadamente la implementacin de gcc est en transicin o a o al momento de escribir esto y es signicativamente ms complicada que la a implementacin inicial. Interesado en simplicar esta discusin, el autor soo o lo cubrir la implementacin del polimorsmo que usan los compiladores a o de Windows, Microsoft y Borland. Esta implementacin no ha cambiado en o muchos aos y probablemente no cambie en el futuro cercano. n Con estos cambios, la salida del programa cambia: Tama~o de a: 8 Desplazamiento de ad: 4 n Tama~o de b: 12 Desplazamiento de ad: 4 Desplazamiento de bd: 8 n A::m() B::m() Ahora la segunda llamada a f llama el mtodo B::m() porque es pasado e a un objeto B. Esto no es lo unico que cambia. El tamao de A es ahora n 8 (y B es 12). Tambin el desplazamiento de ad es 4 no es 0. Qu es un e e

7.2. ENSAMBLADOR Y C++ class A { public: virtual void virtual void int ad; };

171

cdecl m1() { cout << A::m1() << endl; } cdecl m2() { cout << A::m2() << endl; }

class B : public A { // B hereda de A m2() public: virtual void cdecl m1() { cout << B::m1() << endl; } int bd; }; / imprime la vtable de un objeto dado / void print vtable ( A pa ) { // p ve a pa como un arreglo de palabras dobles unsigned p = reinterpret cast<unsigned >(pa); // vt ve la vtable como un arreglo de apuntadores void vt = reinterpret cast<void >(p[0]); cout << hex << Direccin de la vtable = << vt << endl; o for ( int i =0; i < 2; i ++ ) cout << dword << i << : << vt[i] << endl; // llamado a funciones virtuales de una manera // EXTREMADAMENTE no portable void (m1func pointer)(A ); // function pointer variable m1func pointer = reinterpret cast<void ()(A)>(vt[0]); m1func pointer(pa ); // Llama al mtodo m1 a travs de un e e // apuntador a una funcin o void (m2func pointer)(A ); // function pointer variable m2func pointer = reinterpret cast<void ()(A)>(vt[1]); m2func pointer(pa ); // llamado al mtodo m2 a travs de e e // un apuntador a funcin o } int main() { A a; B b1; B b2; cout << a: << endl; print vtable (&a); cout << b1: << endl; print vtable (&b); cout << b2: << endl; print vtable (&b2); return 0; } Figura 7.23: Un ejemplo ms complicado a

172

CAP ITULO 7. ESTRUCTURAS Y C++

0 4 8

vtablep s ad bd b1

04

&B::m1() &A::m2() vtable

Figura 7.24: Representacin interna de b1 o desplazamiento de cero? La respuesta a esta pregunta est relacionada con a la implementacin del polimorsmo. o Una clase C++ que tiene cualquier mtodo virtual se le da un campo e oculto extra que es un apuntador a un arreglo de apuntadores a mtodos. 13 . e Esta tabla se llama a menudo vtable. Para las clases A y B este apuntador se almacena con un desplazamiento 0. Los compiladores de Windows siempre colocan este apuntador al inicio de la clase, en el tope del rbol de herencia. a Mirando en el cdigo ensamblador (Figura 7.22) generado para la funcin f o o (de la Figura 7.19) para el mtodo virtual del programa, uno puede ver que e la llamada al mtodo m no es a una etiqueta. La l e nea 9 encuentra la direccin o de la vtable de objeto. La direccin del objeto se empuja en la pila en la l o nea 11. La l nea 12 llama al mtodo virtual ramicndose en la primera direccin e a o en la vtable. 14 . Esta llamada no usa una etiqueta, salta a la direccin de o cdigo apuntada por EDX. Este tipo de llamadas es un ejemplo de v o nculo tard . El v o nculo tard retraza la decisin de qu mtodo llamar hasta o o e e que el cdigo se est ejecutando. Esto le permite al cdigo llamar el mtodo o e o e apropiado para el objeto. El caso normal (Figura 7.20) ja una llamada a cierto mtodo es llamado primer v e nculo ya que aqu el mtodo est atado e a en el tiempo de compilacin. El lector atento se sorprender por qu los o a e mtodos de la clase en la Figura 7.21 estn declarados expl e a citamente para usar la convecin de llamado de usando la plabra clave cdecl. Por defecto o Microsoft usa una convecin de llamado diferente a la convencin normal de o o pasa un apuntador al objeto C para los mtodos de las clases de C++. El e sobre el que acta el mtodo en el registro ECX en lugar de usar la pila. u e La pila se sigue usando para los otros parmetros expl a citos del mtodo. El e
13 Para las clases sin mtodos virtuales los compiladores de C++ siempre hacen la clase e compatible con una estructura normal de C con los mismos miembros. 14 Claro est, este valor ya est en el registro ECX l fue colocado en la l a a e nea 8 y la l nea 10 pod ser quitado y la prxima l a o nea cambiada para empujar ECX. El cdigo no es muy o eciente porque se genera sin tener activadas las optimizaciones del compilador.

7.2. ENSAMBLADOR Y C++ a: vtable address = 004120E8 dword 0: 00401320 dword 1: 00401350 A::m1() A::m2() b1: vtable address = 004120F0 dword 0: 004013A0 dword 1: 00401350 B::m1() A::m2() b2: vtable address = 004120F0 dword 0: 004013A0 dword 1: 00401350 B::m1() A::m2() Figura 7.25: Salida del programa de la Figura 7.23

173

modicador cdecl le dice usar la convencin de llamado estndar de C. o a Borland C++ usa la convencin de llamado de C por defecto. o Ahora miraremos un ejemplo un poco ms complicado (Figura 7.23). a En l, las clases A y B cada una tiene dos mtodos m1 y m2.Recuerde que e e ya que la clase B no dene su propio mtodo m2, hereda el mtodo de la e e clase A. La Figura 7.24 muestra como un objeto b se ve en memoria. La Figura 7.25 muestra la salida del programa. Primero observe la direccin de o vtable para cada objeto. Las dos direcciones de los objetos B son iguales y as ellas compartan la misma vtable. Una vtable es una propiedad de la clase , no un objeto (como un miembro static). Ahora mire en las direcciones en las vtables. Mirando la salida en ensamblador uno puede determinar que el apuntador al mtodo m1 es un desplazamiento cero (o palabra doble 0) y m2 e es un desplazamiento de 4 (palabra doble 1). Los apuntadores al mtodo m2 e son los mismos vtables de las clases A y B porque la clase B hereda el mtodo e m2 de la clase A. Las l neas 25 a 32 muestran como uno podr llamar a una funcin virtual a o leyendo su direccin de la vtable para el objeto. 15 . La direccin del mtodo o o e se almacena en un apuntador a funcin de tipo C con un apuntador expl o cito
15

Recuerde que este cdigo solo trabaja con los compiladores de Borland y MS, no para o

gcc.

174

CAP ITULO 7. ESTRUCTURAS Y C++

this. De la salida en la Figura 7.25, uno puede ver que trabaja. Sin embargo, por favor no escriba cdigo como este. Es solo para ilustrar cmo los mtodos o o e virtuales usan la vtable. Hay algunas lecciones prcticas para aprender de esto. Un hecho impora tante es que uno tendr que tener mucho cuidado cuando lee y escribe varia ables de una clase a un archivo binario. Uno no puede so usar una lectura o o escritura binaria en un objeto completo ya que esto podr leer o escribir a la apuntador vtable al archivo. Este es un apuntador a donde la vtable reside en la memoria del programa y variar de programa a programa. Este a mismo problema puede ocurrir con estructuras, pero en C, las estructuras slo tienen apuntadores en ellas si el programador las coloca expl o citamente en ellas. No hay apuntadores obvios denidos en las clases A o B. Otra vez, es importante tener en cuenta que cada compilador implementa los mtodos virtuales a su manera. En Windows, los objetos de las clases e COM (Component Object Model) usan las vtables para implementar interfaces COM16 . Slo los compiladores que implementan mtodos virtuales o e como Microsoft pueden crear clases COM. Esta es la razn por la cual Boro land usa la misma implementacin que Microsoft y una de las principales o razones por las cuales gcc no se puede usar para crear clases COM. El cdigo para el mtodo virtual parece exctamente como un mtodo o e a e no virtual. Slo el cdigo que llama es diferente. Si el compilador puede estar o o absolutamente seguro de qu mtodo virtual ser llamado, puede ignorar la e e a vtabley llamar el mtodo directamente, (usando el primer v e nculo).

7.2.6.

Otras caracter sticas de C++

Las otras caracter sticas de C++ (informacin en tiempo de ejecucin, o o manejo de excepciones y herencia mltiple) estn ms all del alcance de u a a a este texto. Si el lector desea avanzar ms, un buen punto de partida es The a Annotated C++ Reference Manual por Ellis and Stroustrup y The Design and Evolution of C++ por Stroustrup.

16 Las clases COM tambin usan la convencin de llamado code e o de C.

stdcall no la normal

Apndice A e

Instrucciones del 80x86


A.1. Instrucciones para enteros

Esta seccin lista y describe las acciones y formatos de las instrucciones o para enteros de la familia de Intel 80x86 Los formatos usan las siguientes abreviaturas: R R8 R16 R32 SR M M8 M16 M32 I registro general registro de 8 bits registro de 16 bits registro de 32 bits registro de segmento memoria byte palabra palabra doble valor inmediato

Estas se pueden combinar para las instrucciones con varios operandos. Por ejemplo el formato R,R signica que la instruccin toma dos operandos de o registro. Muchas de las instrucciones con dos operandos permiten los mismos operandos. La abreviacin 02 se usa para representar estos operandos: R,R o R,M R,I M,R M,I. Si se puede usar como operando un registro de 8 bits o la memoria, se usa la abreviacin R/M8 o La tabla tambin muestra cmo se afectan varias de las banderas del e o registro FLAGS. Si la columna est en blanco, el bit correspondiente no se a afecta. Si el bit siempre cambia a algn valore en particular, se muestra un 1 u o un 0 en la columna. Si el bit cambia a un valor que depende del operando de la instruccin se coloca un C en la columna. Finalmente, si el bit es o modicado de alguna manera no denida se coloca un ? en la columna. Ya 175

176

APENDICE A. INSTRUCCIONES DEL 80X86

que las unicas instrucciones que cambian las banderas de direccin son CLD o y STD, no se listan bajo la columna de FLAGS. Banderas S Z A P C C C C C C C C C C ? C

Nombre ADC ADD AND BSWAP CALL CBW CDQ CLC CLD CMC CMP CMPSB CMPSW CMPSD CWD CWDE DEC DIV ENTER IDIV IMUL

Descripcin o Suma con carry Suma enteros AND entre bits Byte Swap Llamado a rutina Convierta byte a palabra Convierte Dword a Qword Borra el Carry borra la bandera de direccin o Complementa el carry Compara enteros Compara Bytes Compara Words Compara Dwords Convierte Word a Dword en DX:AX Convierte Word a Dword en EAX Decrementa entero Divisin sin signo o Hace el marco de la pila Divisin con signo o Multiplicacin con sigo no

Formatos O2 O2 O2 R32 RMI

O C C 0

C C C 0

O2

C C C C

C C C C

C C C C

C C C C

C C C C

C C C C C

RM RM I,0

C ?

C ?

C ?

C ?

C ?

RM ? R M C R16,R/M16 R32,R/M32 R16,I R32,I


R16,R/M16,I R32,R/M32,I

? ?

? ?

? ?

? ?

? C

INC INT JA

Incremento entero Genera una interrupcin o Salta si est sobre a

RM I I

A.1. INSTRUCCIONES PARA ENTEROS

177 Banderas S Z A P

Nombre JAE JB JBE JC JCXZ JE JG JGE JL JLE JMP JNA JNAE JNB JNBE JNC JNE JNG JNGE JNL JNLE JNO JNS JNZ JO JPE JPO JS JZ

Descripcin o Salta si est sobre o es a igual Salta si est bajo a Salta si est bajo o es a igual Salta si hay carry Salta si CX = 0 Salta si es igual Salta si es mayor Salta si es mayor o igual Salta si es menor Salta si es menor o igual Salto incondicional Salta si no est sobre a Salta si no est sobre o a es igual Salta si no est bajo a Salta si no est bajo o a es igual Salta si no hay carry Salta si no es igual Salta si no es mayor Salta si no es mayor o es igual Salta si no es menor Salta si no es menor o igual Salta si no hay desborde Salta si es positivo Salta si no es cero Salta si hay desborde Salta si hay paridad par Salta si hay paridad impar Salta si hay signo Salta si hay cero

Formatos I I I I I I I I I I RMI I I i I I I I I I I I I I I I I I I

178

APENDICE A. INSTRUCCIONES DEL 80X86 Banderas S Z A P

Nombre LAHF LEA LEAVE LODSB LODSW LODSD LOOP LOOPE/LOOPZ LOOPNE/LOOPNZ MOV

Descripcin o Carga FLAGS en AH Carga direccin efectio va Abandona el marco de la pila Carga byte Carga palabra Carga palabra doble bocle Bucle si es igual Bucle si no es igual Mueve datos

Formatos R32,M

I I I O2 SR,R/M16 R/M16,SR

MOVSB MOVSW MOVSD MOVSX

Mueve Mueve Mueve Mueve

byte palabra palabra doble con signo

MOVZX

Mueve sin signo

MUL NEG NOP NOT OR POP POPA POPF PUSH PUSHA PUSHF RCL

Multiplicacin sin sigo no Negacin o No Opera Complemento a 1 OR entre bits Saca de la pila Saca todo de la pila Saca FLAGS Empuja en la pila Empuja todo en la pila Empuja FLAGS Rota a la izquierda con carry

R16,R/M8 R32,R/M8 R32,R/M16 R16,R/M8 R32,R/M8 R32,R/M16 RM C RM RM O2 R/M16 R/M32 C

? C

? C

? C

? C

C C

C R/M16 R/M32 I

R/M,I R/M,CL

A.1. INSTRUCCIONES PARA ENTEROS

179 Banderas S Z A P

Nombre RCR REP REPE/REPZ REPNE/REPNZ RET ROL ROR SAHF SAL SBB SCASB SCASW SCASD SETA SETAE SETB SETBE SETC SETE SETG SETGE SETL SETLE SETNA SETNAE SETNB SETNBE SETNC SETNE SETNG SETNGE SETNL SETNLE SETNO SETNS

Descripcin o Rota a la derecha con carry Repite Repite si es igual Repite si no es igual Retorno Rota a la izquierda Rota a la derecha Copia AH en FLAGS Dezplazamiento a la izquierda Resta con prstamo e busca un Byte Busca una palabra Busca una palabra doble ja sobre ja sobre o igual ja bajo ja bajo o igual ja el carry ja igual ja mayor ja mayor o igual ja menor ja menor o igual ja no sobre ja no sobre o igual ja no bajo ja no bajo o igual ja no carry ja no igual ja no mayor ja no mayor o igual ja no menor jan no menor o igual ja no desborde ja no signo

Formatos R/M,I R/M,CL

O C

C C

R/M,I R/M,CL R/M,I R/M,CL R/M,I R/M, CL O2

C C C C C C

C C C C C C C C

C C C C

C C C C

C C C C

C C C C

C C C C

R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M8

180

APENDICE A. INSTRUCCIONES DEL 80X86 Banderas S Z A P

Nombre SETNZ SETO SETPE SETPO SETS SETZ SAR SHR SHL STC STD STOSB STOSW STOSD SUB TEST XCHG XOR

Descripcin o ja no cero ja desborde ja paridad par ja paridad impar ja signo ja cero Desplazamiento aritmtico a la derecha e Desplazamiento lgico o a la derecha Desplazamiento lgico o a la izquierda ja el carry ja la bandera de direccin o almacena byte almacena palabra almacena palabra doble resta comparacin lgica o o Intercambio XOR entre bits

Formatos R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M,I R/M, CL R/M,I R/M, CL R/M,I R/M, CL

C C C 1

O2 R/M,R R/M,I R/M,R R,R/M O2

C 0

C C

C C

C ?

C C

C 0

A.2. INSTRUCCIONES DE PUNTO FLOTANTE

181

A.2.

Instrucciones de punto otante

En esta seccin, se describen muchas de las instrucciones del coproceo sador matemtico del 80x86. La seccin de descripcin describe brevemente a o o la operacin de la instruccin. Para ahorrar espacio, la informacin sobre si o o o la instruccin saca de la pila no se da en la descripcin. o o La columna de formato muestra que tipo de operandos se pueden suar con cada instruccin. Se usan las siguientes abreviaturas o STn F D E I16 I32 I64 Un registro del coprocesador nmero de precisin simple en memoria u o nmero de precisin doble en memoria u o nmero de precisin extendida en memoria u o palabra entera en memoria palabra doble entera en memoria palabra cudruple entera en memoria a

Las instrucciones que requieren un Pentium Pro o posterior estn mara * ). cadas con un asterisco ( Instruccin o FABS FADD src FADD dest, ST0 FADDP dest [,ST0] FCHS FCOM src FCOMP src FCOMPP src FCOMI* src FCOMIP* src FDIV src FDIV dest, ST0 FDIVP dest [,ST0] FDIVR src FDIVR dest, ST0 FDIVRP dest [,ST0] FFREE dest FIADD src FICOM src FICOMP src FIDIV src FIDIVR src Descripcin o ST0 = |ST0| ST0 += src dest += STO dest += ST0 ST0 = ST0 Compara ST0 y src Compara ST0 y src Compara ST0 y ST1 Compara en FLAGS Compara en FLAGS ST0 /= src dest /= STO dest /= ST0 ST0 = src /ST0 dest = ST0/dest dest = ST0/dest Marca como vac o ST0 += src Compara ST0 y src Compara ST0 y src STO /= src STO = src /ST0 Formato STn F D STn STn STn F D STn F D STn STn STn F D STn STn STn F D STn STn STn I16 I32 I16 I32 I16 I32 I16 I32 I16 I32

182 Instruccin o FILD src FIMUL src FINIT FIST dest FISTP dest FISUB src FISUBR src FLD src FLD1 FLDCW src FLDPI FLDZ FMUL src FMUL dest, ST0 FMULP dest [,ST0] FRNDINT FSCALE FSQRT FST dest FSTP dest FSTCW dest FSTSW dest FSUB src FSUB dest, ST0 FSUBP dest [,ST0] FSUBR src FSUBR dest, ST0 FSUBP dest [,ST0] FTST FXCH dest

APENDICE A. INSTRUCCIONES DEL 80X86 Descripcin o Push src on Stack ST0 *= src Inicia el coprocesador almacena ST0 almacena ST0 ST0 -= src ST0 = src - ST0 empuja src en la pila empuja 1.0 en la pila Load Control Word Register empuja en la pila empuja 0.0 en la pila ST0 *= src dest *= STO dest *= ST0 Redondea ST0 ST0 = 2 ST1 ST0 ST0 = STO almacena ST0 almacena ST0 Store Control Word Register Store Status Word Register ST0 -= src dest -= STO dest -= ST0 ST0 = src -ST0 dest = ST0-dest dest = ST0-dest Compare ST0 with 0.0 Intercambia ST0 y dest Formato I16 I32 I64 I16 I32 I16 I32 I16 I32 I64 I16 I32 I16 I32 STn F D E I16

STn F D STn STn

STn F D STn F D E I16 I16 AX STn F D STn STn STn F D STn STn STn

Indice alfabtico e
, 12 tipos de almacenamiento automatic, 96 register, 96 volatile, 96 ADC, 39, 56 ADD, 13, 39 AND, 52 archivo de listado, 2526 archivo esqueleto, 26 Aritmtica de complemento a dos, e 35 array1.asm, 101105 arreglos, 97118 acceso, 99105 deninir esttico, 97 a variable local, 98 denir, 9798 multidimensional, 108 dos dimensiones, 105107 parmetros, 108 a multidimensionales, 105 bandera de paridad, 41 binario, 12 suma, 2 BSWAP, 62 bucle do while, 45 bucle while, 45 byte, 4 C driver, 19 C++, 153174 183 clases, 159174 ejemplo Big int, 161166 encadenamiento seguro de tipos, 154 extern C, 155156 funciones inline, 157159 funciones miembro, vase mtoe e dos herencia, 166174 manipulacin de nombres, 153 o 156 polimorsmo, 169174 primer v nculo, 172 referencias, 156157 v nculo tard 172 o, vtable, 172174 CALL, 7172 CBW, 33 CDQ, 33 CLC, 39 CLD, 109 CMP, 40 CMPSB, 111, 112 CMPSD, 111, 112 CMPSW, 111, 112 codigo de operacin, 11 o COM, 174 Compilador Watcom, 86 compilador, 5, 12 Borland, 23, 24 DJGPP, 23, 24 gcc, 23 attibute , 86

184 attribute , 147, 148, 152 Microsoft, 23 embalado pragma, 147 pragma pack, 148, 152 compiler gcc attribute , 151 Microsoft pragma pack, 151 complemento a dos, 3032 aritmtica, 39 e constructor copia, 164 contando bits, 6266 mtodo dos, 6365 e mtodo tres, 6566 e mtodo uno, 6263 e convencin de llamado, 80, 8687 o cdecl, 87 stdcall, 87 C, 22, 74, 8387 etiquetas, 85 etiquetas , 84 parmetros, 85 a paramtros, 85 e registros, 84 retorno de valores, 86 valores de retorno, 86 llamado estandar, 87 Pascal, 74 registro, 87 stdcall, 87, 174 convenciones de llamado, 67, 72 stdcall, 74 coprocesador de punto otante, 127 142 carga y almacenamiento de datos, 128 cargar y almacenar datos, 128 comparaciones, 131132 hardware, 127 multiplicacin and divisin, 130 o o 131

INDICE ALFABETICO suma y resta, 129130 CPU, 57 80x86, 6 CWDE, 33 decimal, 1 depuracin, 1819 o direccionamiento indirecto, 6768 arreglos, 101105 directiva, 1416 %dene, 14 DX, 97 datos, 1516 equ, 14 extern, 80 global, 22, 83 RESX, 98 TIMES, 97 directive global, 81 DIV, 36, 50 ejecucin especulativa, 56 o encadenando, 24 encadenar, 24 endianess, hyperpage60, 26 62 invert endian, 62 ensamblador, 12 enteros, 2940 bit de signo, 29, 33 comparaciones, 40 con signo, 2932, 40 divisin, 3637 o extensin de signo, 35 o extensin del signo, 32 o multiplicacin, 3536 o precisin extendida, 39 o representacin, 2935 o complemento a dos, 30 magnitud y signo, 29 representacin de complemeno to a uno, 30

INDICE ALFABETICO representacin en complemeno to a dos, 32 sin signo, 29, 40 estructuras, 145153 alineamiento, 146148 campos de bit, 151 campos de bits, 148 osetof(), 146 etiqueta, 16 FABS, 133 FADD, 129 FADDP, 129 FCHS, 133 FCOM, 131 FCOMI, 132 FCOMIP, 132, 143 FCOMP, 131 FCOMPP, 131 FDIV, 131 FDIVP, 131 FDIVR, 131 FDIVRP, 131 FFREE, 128 FIADD, 129 FICOM, 131 FICOMP, 131 FIDIV, 131 FIDIVR, 131 FILD, 128 FIST, 128 FISUB, 130 FISUBR, 130 FLD, 128 FLD1, 128 FLDCW, 128 FLDZ, 128 FMUL, 130 FMULP, 130 FSCALE, 133, 144 FSQRT, 133 FST, 128 FSTCW, 128 FSTP, 128 FSTSW, 132 FSUB, 130 FSUBP, 130 FSUBR, 130 FSUBRP, 130 FTST, 131 FXCH, 128 gas, 160 GHz, 6 hexadecimal, 34

185

I/O, 1719 asm io library, 1719 dump math, 18 dump mem, 18 dump regs, 18 dump stack, 18 print char, 17 print int, 17 print nl, 17 print string, 17 read char, 17 read int, 17 IDIV, 37 immediato, 13 IMUL, 3536 instruccin if, 44 o instruccin if , 45 o instrucciones de cadena, 108118 Interfazando con C, 83 interfazando con C, 91 interrupcin, 10 o JC, 42 JE, 43 JG, 43 JGE, 43 JL, 43 JLE, 43 JMP, 4041 JNC, 42

186 JNE, 43 JNG, 43 JNGE, 43 JNL, 43 JNLE, 43 JNO, 42 JNP, 42 JNS, 42 JNZ, 42 JO, 42 JP, 42 JS, 42 JZ, 42 LAHF, 132 LEA, 8586, 105 lenguaje de mquina, 5, 11 a lenguaje ensamblador, 1213 localidad, 145 LODSB, 109 LODSD, 109 LODSW, 109 LOOP, 44 LOOPE, 44 LOOPNE, 44 LOOPNZ, 44 LOOPZ, 44 mtodos, 159 e MASM, 12 math.asm, 3739 memoria, 45 pginas, 10 a segmento, 10 segmentos, 9 virtual, 10 memoria:segmentos, 9 memory.asm, 113118 modo protegido 16 bits, 10 16-bits, 9 32-bits, 10 modo real, 89

INDICE ALFABETICO MOV, 13 MOVSB, 111 MOVSD, 111 MOVSW, 111 MOVSX, 33 MOVZX, 33 MUL, 3536, 50, 105 NASM, 12 NEG, 58 nibble, 4 NOT, 54 Operaciones con bits desplazamientos, 49 desplazamientos lgicos, 49 o operaciones con bits AND, 52 C, 5860 desplazamientos, 52 desplazamiento lgicos, 50 o desplazamientos aritmticos, e 5051 rotaciones, 51 ensamblador, 5455 OR, 53 XOR, 53 operciones con bits NOT, 53 OR, 53 pila, 7080 paramtros, 72 e variables locales, 7880, 8586 prediccin de ramicaciones, 56 o prediccion de ramicaciones, 55 o prime.asm, 4648 prime2.asm, 138142 programas multimdulo, 8083 o punto otante, 119142 aritmtica, 124126 e representacin, 119124 o desnormalizada, 123

INDICE ALFABETICO desnormalizado, 123 IEEE, 121124 precisin doble, 124 o precisin simple, 123 o presicin simple, 122 o uno oculto, 122 representacion o precisin doble, 124 o quad.asm, 133136 RCL, 51 RCR, 51 read.asm, 136138 recursin, 9194 o registro, 5, 78 ndice, 7 32-bit, 8 apuntador a la pila, 7, 8 apuntador base, 7, 8 EDI, 109 EDX:EAX, 36, 37, 39, 86 EFLAGS, 8 EIP, 8 ESI, 109 FLAGS, 8, 40 CF, 40 DF, 109 OF, 40 SF, 40 ZF, 40 IP, 8 segmento, 7, 8, 109 registros FLAGS PF, 41 registrp IP, 8 reloj, 6 REP, 110111 REPE, 112, 113 REPNE, 112, 113 REPNZ, vase REPNE, 112 e REPZ, vase REPE, 112 e RET, 7172, 74 ROL, 51 ROR, 51

187

SAHF, 132 SAL, 50 salto condicional, 4143 SAR, 50 SBB, 39 SCASB, 111, 112 SCASD, 111, 112 SCASW, 111, 112 SCSI, 149150 segmento bss, 22 segmento de cdigo, 22 o segmento de datos, 22 segmento text, vase segmento code e SETxx, 56 SETG, 57 SHL, 49 SHR, 49 stack parameters, 75 STD, 109 STOSB, 109 STOSD, 109 SUB, 13, 39 subprograma, 6896 llamado, 7180 reentrante, 9192 subrutina, vase subprograma e TASM, 12 TCP/IP, 61 TEST, 54 tipos de alamcenamiento global, 94 tipos de almacenamiento static, 94 UNICODE, 61 virtual, 170

188 word, 8 XCHG, 62 XOR, 53

INDICE ALFABETICO

You might also like