Lengueje Ensamblador para PC Paul A. Carter
HDLCD
NAMS
Copyright c 2001, 2002, 2003, 2004 by Paul Carter
Traducido al espa˜ nol nol por Leonardo Rodr´ıguez ıguez M´ ujica. Sus comentaros y suujica. gerencias acerca de la traducci´on on por favor a:
[email protected] Este documento puede ser reproducido y distribuido totalmente (incluida esta paternidad literaria, copyright y aviso de autorizaci´ on), on), no se puede cobrar por este documento documento en s´ı mismo, sin el consentim consentimient ientoo del autor. Esto incluye una “utilizaci´ on racional” de extractos como revisiones y anunon cios, y trabajos derivados como traducciones. Observe que esta restricci´on on no est´a prevista para prohibir el cobro por el servicio servicio de impresi´ impresi´ on o copia del documento on A los docentes se les recomienda usar este documento como recurso de clase; sin embargo el autor apreciar a preciar´´ıa ser notificado en este caso.
Prefacio Prop´ osito osito El prop´osito osito de este libro es dar al lector un mejor entendimiento de c´omo omo trabajan realmente los computadores a un nivel m´ as as bajo que los lenguajes de alto nivel como Pascal. Teniendo un conocimiento profundo de c´omo omo trabajan los computadores, el lector puede ser m´ as as productivo desarrollando 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 a´ un un ense˜ nan nan a programar el procesador 8086 que us´ o el PC original en 1981. El procesador 8086 s´ olo olo soporta el modo real . En este modo, cualquier programa puede acceder a cualquier direcci´on on de memoria o dispositivo en el computador. Este modo no es apropiado para un sistema operativo multitarea seguro. Este libro, en su lugar discute c´omo omo programar los procesadores 80386 y posteriores en modo protegido (el modo en que corren Windows y Linux). Este modo soporta las caracter´ısticas ısticas que los sistemas operativos modernos mo dernos esperan, como memoria virtual y protecci´ on de memoria. Hay varias razones para usar el on modo protegido protegido 1. Es m´as as f´acil acil de programar en modo protegido que en el modo real del 8086 que usan los otros libros. 2. Todos los sistemas operativos operativos de PC se ejecutan en modo protegido. protegido. 3. Hay disponible disponible software software libre libre que se ejecuta ejecuta en este modos. La carencia de libros de texto para la programaci´ on en ensamblador de PC on para modo protegido es la principal raz´ on por la cual el autor escribi´o este on libro. C´ omo lo dicho antes, este libro hace uso de Software Libre: es decir el omo ensamblador NASM y el compilador de C/C++ DJGPP. Ambos se pueden descargar descargar de Internet Internet.. El texto tambi´ tambi´ en en discute discute c´ omo o mo usar el c´ odigo odigo del ensamblador NASM bajo el sistema operativo Linux y con los compilado piladores res de C/C++ C/C++ de Borlan Borland d y Micros Microsoft oft bajo Windo Windows. ws. Todos los i
PREFACIO
ii
ejemplos de estas plataformas se pueden encontrar en mi sitio web: http: odigo odigo de los ejemplos, //www.drpaulcarter.com/pcasm. Debe descargar el c´ si desea ensamblar y correr los muchos ejemplos de este tutorial. Tenga en cuenta que este libro no intenta cubrir cada aspecto de la programaci´ on en ensamblador. El autor ha intentado cubrir los t´ on opicos opicos m´ as as importantes que todos los programadores program adores deber debe r´ıan tener
Reconocimientos El autor quiere agradecer a los muchos programadores alrededor del mundo que han contribuido al movimiento de Software Libre. Todos los programe y a´ un un este libro en s´ı mismo fueron fueron producidos producidos usando usando softwar softwaree libre. libre. El autor desear´ desear´ıa agradecerle especialmente esp ecialmente a John S. Fine, Simon Tatham, Julian Hall y otros por desarrollar el ensamblador NASM ya que todos los ejemplos de este libro est´an an basados basados en ´el; el; a DJ Delorie Delorie por desarrollar desarrollar el compilador usado de C/C++ DJGPP; la numerosa gente que ha contribuido al compilador GNU gcc en el cual est´a basado DJGPP; a Donald Knuth y otros por desarrollar los lenguajes de composici´ on on de textos TEX y LATEX 2ε que fueron usados para producir este libro; a Richar Stallman (fundador de la Free Software Fundation), Linus Torvalds (creador del n´ucleo ucleo de Linux) y 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 Hopkins Nick D’Imperio Jeremiah Lawrence Ed Beroset Jerry Gembarowski Ziqiang Peng Eno Compton Josh I Cates Mik Mifflin Luke Wallis
iii
Gaku Ueda Brian Heward Chad Gorshing F. Gotti Bob Wilkinson Markus Koegel Louis Taber Dave Kiddell Eduardo Horowitz S´ebastie ebas tien n Le Ray Nehal Mistry Jianyue Wang Jeremias Kleer Marc Janicki
Recursos en Internet P´ agina agina del autor P´agina agina de NASM en SourceForge DJGPP Ensamblador con Linux The Art of Assembly USENET Documentaci´ on on de Intel
http://www.drpaulcarter.com/ http://nasm.sourceforge.net/ http://www.delorie.com/djgpp http://www.linuxassembly.org/ http://webster.cs.ucr.edu/ comp.lang.asm.x86 http://www.intel.com/design/Pentium4/documentation.htm
Comentarios El autor agradece cualquier comentario sobre este trabajo. E-mail: WWW:
[email protected] http://www.drpaulcarter.com/pcasm
iv
PREFACIO
Cap´ıtulo 1
Introducci´ on on 1.1. 1.1.
Sist Sistema emass de numer umerac aci´ i´ on on
La memoria en un computador est´ a compuesta de n´ umeros. umeros. La memoria del computador no almacena estos n´ umeros en decimal (base 10). Porque umeros se simplifica mucho el hardware, los computadores almacenan toda la informaci´ on en binario (base 2). Primero haremos una revisi´on on on del sistema decimal.
1.1. 1.1.1. 1.
Deci Decima mall
Los n´ umeros con base 10 est´an umeros an compuestos de 10 posibles d´ıgitos (0-9). Cada d´ıgito de un n´ umero umero tiene una potencia p otencia de 10 asociada aso ciada con ´el, el, basada en su posici´on o n en el n´ umero. umero. Por ejemplo: 234 = 2
1.1. 1.1.2. 2.
2
× 10
+3
1
× 10
+4
0
× 10
Bina Binari rio o
Los n´ umeros umeros en base dos est´an an compuestos de dos posibles p osibles d´ıgitos (0 y 1). Cada d´ıgito de un u n n´ numero u´mero tiene una potencia pot encia de 2 asociada aso ciada con ´el el basada en su posici´on o n en el n´ umero. umero. Por ejemplo: 110012 = 1
4
×2
+1
= 16 + 8 + 1
3
×2
+0
2
×2
+0
1
×2
+1
×2
0
= 25 Esto muestra c´omo omo los n´ umeros binarios se pueden convertir a decimal. umeros El Cuadro 1.1 muestra c´omo omo se representan los primeros n´umeros umeros en binario. La Figura 1.1 muestra c´omo omo se suman los d´ıgitos binarios individuales (bits). A continuaci´ on on un ejemplo: 1
´ CAP ´ITULO ITULO 1. INTRODUCC INTRODUCCI I ON
2 Decimal 0 1 2 3 4 5 6 7
B Biinario 0000 0001 0010 0011 0100 0101 0110 0111
Decimal 8 9 10 11 12 13 14 15
B Biinario 1000 1001 1010 1011 1100 1101 1110 1111
Cuadro 1.1: Decimal de 0 a 15 en binario No hay carry antes 0 0 1 1 +0 +1 +0 +1 0 1 1 0 c
S´ı hay carry antes 0 0 1 1 +0 +1 +0 +1 1 0 0 1 c c c
Figura 1.1: Suma binaria (c es carry )
110112 +100012 1011002 Si uno considera la siguiente divisi´on on decimal: 1234
÷ 10 = 123 r 4
podemos ver que esta divisi´on on suprime suprime el d´ıgito del extremo extremo derecho derecho del n´umero umero y desplaza los l os otros d´ıgitos una posici´ on a la derecha. Dividiendo on por dos hacemos una operaci´on on similar, simi lar, pero p ero para los d´ıgitos ıgitos binarios de un 1 n´umero. umero. Consideremos la siguiente divisi´on on binaria : 11012
÷ 10
2
= 1102 r 1
Este hecho se puede usar para convertir un n´umero umero decimal a su representaci´ on equivalente en binario como muestra la Figura 1.2 on 1.2.. Este Est e m´etodo eto do encuentra primero el bit del extremo derecho, llamado bit menos significativo (lsb). El bit del extremo izquierdo es llamado bit m´ as significativo (msb). La unidad b´asica asica de memoria est´ a compuesta de 8 bits y es llamado byte 1
El sub´ındice ındice 2 se usa para mostrar que el n´ umero umero est´ a representado en binario no en decimal
´ 1.1. SISTEMAS SISTEMAS DE NUMER NUMERACI ACI ON
25
3
Decimal Binario 2 = 12 r 1 11001 10 = 1100 r 1
÷ 12 ÷ 2 = 6 r 0 6÷2=3r 0 3÷2=1r 1 1÷2=0r 1
÷ 1100 ÷ 10 = 110 r 0 110 ÷ 10 = 11 r 0 11 ÷ 10 = 1 r 1 1 ÷ 10 = 0 r 1
As´ı 2510 = 110012 Figura 1.2: Conversi´ on on a decimal
1.1.3. 1.1.3.
Hexade Hexadeci cimal mal
Los n´ umero umero hexadecimales hexadecimales tienen base 16. Los hexadecimales hexadecimales (o hex ) hex ) se pueden usar como una representaci´on on resumida de los n´ umeros umeros binarios. Los n´umeros umeros hexadecimale hexadecimaless tienen 16 d´ıgitos posibles. posibles. Esto crea un problema problema ya que no hay s´ımbolos para estos d´ıgitos adicionales despu´ es es del nueve. nueve. Por convenci´on on se usan letras para estos d´ıgitos adicionales. Los 16 d´ıgitos hexadecimal hexadecimales es son: 0-9 y luego A, B, C, D, E, F. El d´ıgito ıgito A equivale equivale a 10 en decimal, B es 11 etc. Cada d´ıgito de un n´ numero ´ hexadecimal tiene una potencia de 16 asociada con ´el. el. Por ejemplo: 2BD16 = 2
2
× 16
+ 11
1
× 16
= 512 + 176 + 13 = 701
+ 13
× 16
0
Para convertir de decimal a hex use la misma idea que la empleada para la conversi´on on binaria excepto que se divide por 16. Vea la Figura 1.3 para un ejemplo. La raz´on on por la cual los hexadecimales son ´utiles utiles es que hay una manera f´acil acil para convertir entre hex y binario. Los n´ umero binarios se tornan larumero gos y molestos r´ apidamente. La representaci´ apidamente. on hexadecimal es una manera on mucho m´ as compacta de representar los n´umeros as umeros binarios. Para convertir un n´ umero hexadecimal a binario simplemente convierta umero cada d´ıgito hexadecimal hexadeci mal a un n´ umero binario de 4 bits. Por ejemplo, 24D16 umero es convertido en 0010 0100 11012 . Observe que ¡los ceros delanteros son importantes! Si los ceros del d´ d´ıgito de la mitad de 24D16 no se usan el resultado es err´oneo. oneo. Convertir de binario a hex es igual de f´acil; acil; uno hace el proceso
´ CAP ´ITULO ITULO 1. INTRODUCC INTRODUCCI I ON
4
589 36
÷ 16 ÷ 16 2 ÷ 16
= 36 r 13 = 2r4 = 0r2
As´ı 589 58 9 = 24D 2 4D16 Figura 1.3: inverso, convierte cada segmento de 4 bits a hexadecimal comenzando desde el extremo derecho, no desde el izquierdo, del n´umero umero binario. Esto asegura asegura 2 que el segmento de 4 bits es correcto . Ejemplo: 110 6
0000 0
0101 5
1010 A
0111 7
11102 E16
Un n´ umero de 4 bits es llamado nibble . As´ umero As´ı cada cad a d´ıgito ıgi to hexadec hexa decima imall 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.2. 1.2.1. 1.
Orga Organi niza zaci ci´ ´ on on del computador La Memo Memori ria a
La unidad b´asica asica de memoria memoria es el byte. byte. Un computa computador dor con 32 Mega La memo memori ria a es medi medida da en unid unidad ades es de kilo kiloby byte tess bytes de memoria puede almacenar aproximadamente 32 millones de bytes ( 210 = 1, 024 bytes), me- de informaci´ on. on. Cada byte est´a etiquetado por un n´ umero umero unico u ´ nico conocido ga bytes ( 220 = 1, 048, 576 como su direcci´ on. Tal como lo muestra la Figura 1.4 on. 1.4.. bytes) y Gigabytes ( 230 = 1, 073, 741, 824 bytes). Direcci´ on Memoria
0 2A
1 45
2 B8
3 20
4 8F
5 CD
6 12
7 2E
Figura Figura 1.4: Direccione Direccioness de Memoria Memoria
A menudo la memoria se usa en trozos m´ as grandes que un byte. En la as arquitectura del PC, los nombres que se le han dado a estas secciones de 2
Si no es claro porque el punto de inicio hace la diferencia, intente convertir el ejemplo comenzando desde la izquierda
´ DEL COMPUTADOR 1.2. ORGANI ORGANIZA ZACI CI ON ingl´es word doub double le word ord quad word paragraph
unidad 2 bytes 4 bytes ytes 8 bytes tes 16 bytes
5
espanol n ˜ol palabra pala palabr braa dobl doblee palabra cuadruple a´druple parrafo a´rrafo
Cuadro Cuadro 1.2: Unidades Unidades de memoria memoria
memoria m´ as grandes se muestran en el Cuadro 1.2 3 . as Todos los datos en la memoria son num´ ericos. ericos. Los caracteres son almacenados usando un c´ odigo de caracteres caracteres que traduce un n´ umero umero en un car´acter. acter. Uno de los c´odigos odigos de caracteres m´as as conocido es el ASCII (American Standar Code for Information Interchange ). ). Un nuevo c´odigo, odigo, m´ as as completo, que est´a reemplazando al ASCII es el Unicode. Una diferencia clave entre los dos c´odigos odigos es que el ASCII usa un byte para codificar un car´acter, acter, pero Unicode usa dos bytes (o una palabra ) por car´acter. acter. Por ejemplo ASCII decodifica el byte 4116 (6510 ) como la A may´ uscula. Unicode la codifica con la palauscula. bra 004116 . Ya que ASCII usa un byte est´a limitado a s´ olo olo 256 caracteres 4 diferentes. Unicode ampl´ ampl´ıa los valores ASCII a palabras palabras y permite que se representen muchos m´as as caracteres. Esto es importante para representar los caracteres de todas las lenguas del mundo.
1.2. 1.2.2. 2.
La CPU CPU
La Unidad Central de Procesamiento (CPU) es el dispositivo f´ısico ısico que ejecuta las instrucciones. Las instrucciones que ejecuta la CPU son por lo general muy simples. simples . Las instruccion inst rucciones es pueden requerir re querir datos dat os que est´en en en un lugar especial esp ecial de almacenamiento de la CPU en s´ı misma llamados llama dos registros. registros . La CPU puede acceder a los datos en los registros mucho m´ as as r´apido apido que en la memoria. Sin embargo el n´ umero de registros en la CPU es limitado, umero as´ as´ı el programador debe tener cuidado de dejar en los registros s´olo olo los datos que est´e usando. usan do. Las instrucciones que un tipo de CPU ejecuta las hace en lenguaje de m´ aquina . Los programas en lenguaje de m´aquina aquina tienen una estructura mucho m´ as as b´asica asica que los lenguajes de alto nivel. Las instrucciones en lenguaje de m´ aquina son codificadas como n´ aquina umeros, no en formatos de texto amiumeros, gables. Una CPU debe estar en capacidad de decodificar una instrucci´on on muy r´apidamente apidamente para ejecutarse eficientemente. EL lenguaje de m´aquina aquina es dise˜ nado con este objetivo en mente, no para ser f´acilmente nado acilmente descifrados 3
N del T: En la traducci´ on on se usar´ an los nombres de las unidades de memoria en an espa˜ nol, nol, aunque en la literatura t´ecnica ecnica seguramente seguramente los encontrar´ an en ingl´ ing l´es es 4 De hecho ASCII s´olo olo usa los 7 bits m´ as as bajos y s´ olo tiene 128 valores diferentes olo
6
GHz significa Gigahertz o mil millones de ciclos por segundo. Una CPU de 1.5 GHz tiene mil quinientos quinientos millones de pulsos de reloj por segundo.
´ CAP ´ITULO ITULO 1. INTRODUCC INTRODUCCI I ON
por humanos. Los programas escritos en otros lenguajes deben ser convertidos en lenguaje de m´aquina aquina nativo de la CPU para que se ejecute en el computador. Un compilador es un programa que traduce programas escritos en un lenguaje de programaci´on on al lenguaje de m´aquina aquina de una arquitectura en particular de un computador. En general cada tipo de CPU tiene su propio y unico u ´ nico lenguaje de m´aquina. aquina. Esa es una de las razones por las cuales programas escritos para un Mac no corren en un PC tipo IBM Los computadores computadores usan un reloj para sincronizar la ejecuci´ on on de las instrucciones. El reloj pulsa a una frecuencia fija conocida como velocidad del reloj. Cuando Ud. compra un computador de 1.5 GHz, la frecuencia de su reloj es 1.5 GHz. Actualmente, los pulsos del reloj son usados por muchos componentes de un computador. Con frecuencia, los otros componentes usan unas velocidades de reloj diferentes que la CPU. El reloj no marca los minutos y los segundos, simplemente toca a una raz´ on constante. La electr´ on onica onica de la CPU usa los toques para realizar sus operaciones correctamente, como los toques de un metr´onomo onomo para la interpretaci´ on on de m´ usica usica al ritmo correcto. El n´ umero de toques (o como a ellos se les llama com´ umero unmente unmente ciclos) ciclos ) que una instrucci´on on requiere depende del modelo de la CPU, de la instrucci´ on on anterior y de otros factores.
1.2.3. 1.2.3.
La famil familia ia de CPU 80x86 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´ caracter´ısticas comunes incluyendo el lenguaje de m´ aquina aquina b´asico. asico. Sin embargo los miembros m´ as as recientes reci entes ampl amp l´ıan mucho las caract car acter er´´ıstica ıst icas. s. 8888,8086: Estas CPU desde el punto de vista de la programaci´ on o n son iguales. Ellas fueron las CPU usadas en las primeras PC. Ellas suministran varios registros de 16 bits: AX, BX, CX, DX, SI, DI, BP, SP, CS, DS, SS, ES, IP, FLAGS. Ellas solo soportan hasta 1 Mega byte de memoria memoria y s´ olo olo operan en modo real . En este modo un programa programa puede acceder a cualquier direcci´on on de memoria, ¡a´ un a la memoria de otros un programas! Esto hace la depuraci´ on on y segurid seg uridad ad muy dif´ıcil. ıci l. Tambi´en en la memoria del programa tiene que ser dividida en segmentos. segmentos. Cada segmento no puede ser m´as as largo que 64 KB 80286: Esta CPU se usa en los PC tipo AT. Agrega unas instrucciones nuevas al lenguaje de m´ aquina base del 8080/86. Sin embargo la caaquina racter´ racter´ıstica principal nueva nueva es el modo protegido de 16 bits. bits. En este modo puede acceder hasta 16 Mega bytes de memoria y proteger a los programas del acceso de otros. Sin embargo los programas todav to dav´´ıa est´an an divididos en segmentos que no pueden ser m´as as grandes de 64K.
´ DEL COMPUTADOR 1.2. ORGANI ORGANIZA ZACI CI ON
7
AX AH AL Figura 1.5: El registro AX
80386: Esta CPU es una gran ampliaci´ on del 80286. Primero extiende muon chos de los registros para almacenar 32 bits (EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP, EIP) y a˜ nade dos nuevos registros de 16 bits FS y nade GS. GS . Tambi´en en anade n˜ade un nuevo modo protegido de 32 bits. bits. En este modo pueden acceder hasta 4 Gigabyes. Los programas otra vez est´ an an divididos en segmentos, pero ahora cada segmento tambi´ en en puede tener hasta un tama˜ no no de 4 Gigabytes. 80486/Pentium/Pentium Pro: Estos miembros de la familia 80x86 a˜ naden muy pocas p ocas caracter´ caracter´ısticas nuevas. nuevas. Ellos principalmente aceleran la ejecuci´ on on de las instrucciones. Pentium MMX: Este procesador a˜ nade instrucciones MMX (eXtensiones nade MultiMedia) al Pentium. Estas instrucciones pueden acelerar instrucciones comunes gr´aficas. aficas. Pentium II: Este es el procesador Pentium Pro con las instrucciones MMX a˜ nadidas (El pentium III es esencialmente s´olo nadidas olo un Pentium II r´apido. apido.
1.2.4. 1.2.4.
Regis Registro tross de 16 16 bits bits del del 8086 8086
La CPU original 8086 suministra 4 registros de 16 bits de prop´ osito osito general AX, BX, CX y DX. Cada uno de esos registros puede ser descompuesto en dos registros de 8 bits. Por ejemplo el registro AX se puede descomponer en los registros AL y AH que muestra la Figura 1.5. El registro AH contiene los 8 bits superiores de AX y AL contiene los 8 bits inferiores de AX. A menudo AH y AL son usados como registros independientes de 8 bits; sin embargo es importante tener en cuenta que ellos no son independientes de AX. Cambiando el valor de AX cambiar´ a AH y AL y viceversa . Los registros de prop´osito osito general son usados en muchos movimientos de datos e instrucciones instruc ciones aritm´eticas. etica s. 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 prop´ositos ositos de los registros generales. Sin embargo, ellos no se pueden descomponer en registros de 8 bits. Los registros de 16 bits BP y SP son usados para se˜nalar nalar a los datos en la pila y son llamados Apuntador Base (Base (Base Pointer ) y apuntador a la pila (Stack Pointer ), ), respectivamente. Ellos se discutir´ an an luego.
8
´ CAP ´ITULO ITULO 1. INTRODUCC INTRODUCCI I ON
Los registros de 16 bits CS, DS, SS y ES son registros de segmento. segmento. Ellos especifican qu´ e memoria es usada por diferentes partes de un programa. CS significa segmento de c´ odigo odigo (Code (Code Segment), Segment), DS segmento de datos (Data (Data Segment), Segment), SS Segmento de la pila (Stack (Stack Segment) Segment) y ES segmento adicional (Extra Segment). Segment). ES es usado como un registro temporal. Los detalles de estos registros est´an an en las Secciones 1.2.6 y 1.2.7. 1.2.7. El registro IP, Apuntador a la instrucci´on on (Instruction Pointer ) es usado con el registro CS para obtener la direcci´on on de la siguiente instrucci´ on on a ser ejecutada por la CPU. Normalmente cuando se ejecuta una instrucci´on on IP avanza hasta se˜ nalar a la siguiente instrucci´ nalar on on en memoria. El registro FLAGS almacena informaci´ on importante sobre los resultados on de una instrucci´on on anterior. Estos resultados son almacenados como bits individuales en el registro. Por ejemplo, el bit Z es 1 si el resultado de la instrucci´ on anterior fue cero o 0 si el resultado no fue cero. No todas las on instruccion instrucciones es modifican bits en FLA FLAGS, GS, consulte consulte el cuadro cuadro en el ap´ endice endice para ver c´omo omo instrucciones espec´ espec´ıficas afectan el registro FLAGS
1.2.5. 1.2.5.
Regis Registro tross de 32 32 bits bits del del 80386 80386
El 80386 y los procesadores posteriores tienen registros extendidos. Por ejemplo el registro de 16 bits AX se extendi´o para ser de 32 bits. Para la compatibilidad con sus predecesores, AX se refiere 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 tambi´en. en. 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 prop´ osito general, en el modo osito protegido de 32 bits (discutidos luego) s´olo olo se usan las versiones extendidas de estos registros. Los registros de segmento contin´ uan siendo de 16 bits en el 80386. Hay uan tambi´ en en dos nuevos nuevos registros de segmento: FS y GS. Sus nombres no significan nada. Ellos son registros adicionales para segmentos temporales (como ES). Una de las definiciones definici ones del t´ermno ermno word se refiere a el tama˜ no no del registro de datos datos de la CPU. CPU. Para Para la familia familia 80x86, 80x86, el t´ ermino ermino es ahora ahora un poco confuso. En el Cuadro 1.2, uno ve que word est´a definida para ser 2 bytes (o 16 bits). Este fue el significado que se le dio, cuando se lanz´ o la primera vez el 8086. Cuando se desarroll´o el 80386, se decidi´ o dejar la definici´on on de word sin cambio, auque el tama˜ no no del registro cambi´o. o.
´ DEL COMPUTADOR 1.2. ORGANI ORGANIZA ZACI CI ON
1.2. 1.2.6. 6.
9
Modo Modo Real Real
En el modo real la memoria est´ a limitada a s´ olo olo 1 mega byte (220 bytes) Las direcciones v´alidas alidas est´ an desde 0000 hasta FFFFF. (en hexadecimal) an Estas direcciones requieren un n´umero umero de 20 bits Obviamente un n´ umero umero de 20 bits no cabr´a en ning´ un registro de 16 bits. Intel solucion´ un o este problema usando 2 valores de 16 bits para determinar una direcci´ on. on. El primer valor de 16 bits es llamado seleccionador selector . Los valores del seleccionador deben estar almacenados en registros de segmento El segundo valor de 16 bits es llamado desplazamiento (offset) offset) La direcci´on on f´ısica referenciada referenc iada por un par seleccinador:desplazamiento es calculada por la f´ormula: ormula: 16 seleccionador + desplazamiento
∗
multiplicar por 16 en hexadecimal es muy f´ acil, acil, es s´ olo olo a˜ nadir nadir un 0 a la derecha del n´ umero. Por ejemplo la direcci´ umero. on on f´ısica referenciada por 047C:0048 est´ a dada por: 047C0 +0048 04808 En efecto, el valor seleccionador es un n´ umero umero p´ arrafo arrafo (vea el Cuadro 1.2 1.2). ). direcciones reales segmentadas tienen desventajas: Un unico u ´ nico valor del seleccionador s´ olo puede hacer referencia a 64K de olo memoria memori a (el l´ımite superior superi or del de l desplazam des plazamiento iento de d e 16 bits). ¿Qu´e pasa pas a si un programa tiene m´ as as de 64K de c´odigo? odigo? Un solo valor en CS no se puede usar para toda la ejecuci´on on del programa. El programa se debe dividir en secciones (llamadas segmentos ) menores de 64K en tama˜ no. no. Cuando la ejecuci´ on se mueve de un segmento a otro, los valores de CS on se deben cambiar. Problemas similares ocurren con grandes cantidades de datos y el registro DS Esto puede ser muy inc´omodo omodo Cada byte de memoria no tiene una sola direcci´on on segmentada. La direcci´ on on f´ısica ısica 04804 048 04 puede ser referenciada por 047C:0048, 047D:0038, 0047E:0028 o 047B:0058. Esto puede complicar la comparaci´ on on de direcciones segmentadas.
1.2.7. 1.2.7.
Modo proteg protegid ido o de 16 bits bits
En el modo protegido del 80286 los valores del seleccionador son interpretados completamente diferente que en el modo real. En este modo, un valor del seleccionador es un n´ umero umero de p´arrafo arrafo de memoria f´ısica. En el modo protegido un valor seleccionador es un ´ındice en una tabla de descriptores. descriptores.
¿De d´ onde viene el infame l´ımite ımite de 640K de DOS? La BIOS requerida algunode 1M para el c´ odigo y para los dispositivos de hardware como la pantalla de video
´ CAP ´ITULO ITULO 1. INTRODUCC INTRODUCCI I ON
10
En ambos modos, los programas son divididos en segmentos. En modo real estos segmentos est´ an an en posiciones fijas en la memoria f´ısica y el seleccionador denota el n´ umero umero de p´arrafo arrafo de comienzo del segmento. En modo protegido los segmentos no est´ an an en posiciones fijas en la memoria f´ısica. ısica. De hecho no tiene que estar todo el segmento en memoria. El modo protegido protegido usa una t´ ecnica ecnica llamada llamada memoria virtual . La idea b´asica asica de un sistema de memoria virtual, es dejar s´ olo olo en la memoria los datos y el c´ odigo que los programas est´ odigo an usando en un momento dado. an Los otros datos y el c´ odigo odigo son alma almacen cenado adoss tempora temporalme lment ntee en el disco 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 area a´rea diferente de memoria en el que estuvo antes de ser enviada al disco. Todo esto es hecho transparemen transparementemen temente te por p or el sistema sistema operativo. 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 informaci´ on on que el sistema necesita conocer sobre el segmento. Esta informaci´on on incluye: si est´a actualemente en memoria, si es as´ as´ı d´ onde onde est´a, a, permiso de acceso (ejem: s´olo olo lectura). lectura). El ´ındice de la entrada entrada del segmento segmento es el valor del seleccionad seleccionador or que est´a almacendo en los registros de segmento. Una gran desventaja del modo protegido es que los desplazamientos est´an an Un conocido columnista de PC llam´ o al 286 “cerebro a´ un en cantidades de 16 bits Como una consecuencia de esto, los tama˜ un nos nos muerto.” de los segmentos est´an an todav´ todav´ıa limitados a un m´ aximo de 64K. Esto hace aximo problem´ atico el uso de arreglos grades. atico
1.2.8. 1.2.8.
Modo Modo prote protegid gido o de de 32 bits 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 desplazamient desplazamientos os se ampl´ ampl´ıan a 32 bits. Esto permite un rango de desplazamiento hasta 4 millardos. As´ As´ı los segmentos pueden tener tata ma˜nos nos hasta de 4 Gigabytes. 2. Los segmentos segmentos pueden ser divididos divididos en unidades unidades m´as as peque˜ nas nas de 4K llamadas p´ aginas. aginas . El sistema de memoria virtual trabaja ahora con p´aginas aginas en lugar de segmentos. Esto significa que partes de un segmento pueden estar en memoria. En el modo de 16 bits del 286 o todo el segmento est´ a en memoria o no est´ a. a . Esto no es pr´actico actico con los grandes segmentos que permite el modo de 32 bits. Windows 9x, Windows NT/200/XP, OS/2 y Linux todos se ejecutan en modo protegido de 32 bits
1.3. LENGUAJE LENGUAJE ENSAMBL ENSAMBLADOR ADOR
11
En Windows 3.x el modo standar se refiere al modo protegido de 16 bits del 286 y el modo ampliado se refiere al modo de 32 bits.
1.2.9. 1.2.9.
Inte Interru rrupcio pciones nes
Algunas veces el flujo ordinario de un programa debe ser interrumpido para procesar eventos que requieren una respuesta r´apida. apida. El hardware de un computador provee un mecanismo llamado interrupci´ on para manipular estos eventos. Por ejemplo cuando se mueve el rat´on on la interrupci´on on de hardware del rat´on on es el programa actual para manejar el movimiento del rat´ on (para mover el cursor del mouse, etc) Las interrupciones hacen que on el control se pase a un manipulador de interrupciones. interrupciones. Los manipuladores de interrupciones son rutinas que procesan la interrupci´on. on. A cada tipo de interrupci´ on on se le asigna un n´ umero entero. En el comienzo de la memoria umero f´ısica ısi ca una tabla tab la de vectores de interrupci´ on que contiene la direcci´on on del segmento de los manipuladores de la interrupci´ on. on. El n´ umero umero de la interrupci´on on es escencialmente un ´ındice en esta tabla. Las interrupciones externas son levantadas desde el exterior de la CPU (el rat´ on es un ejemplo de este tipo de interrupci´on). on on). Muchos dispositivos de E/S levantan interrupciones (teclado, temporizador, disco duro CD ROM y tarjetas de sonido) Las interrupciones internas son levantadas desde la CPU, por una instrucci´on on de error o por una instrucci´on on de interrupci´ interrupci´ on. on. Las instrucciones truccio nes de error er ror tambi´ t ambi´en en se llaman llama n trampas. trampas. Las interrupciones generadas desde la instrucci´on on de interrupci´on on son llamadas llamadas interrupciones de sofware. DOS usa estas interrupciones para implementar su API (Interfaz de programas de Aplicaci´ on). Sistemas operativos m´ on). as modernos (como Windows y as 5 Linux) usan una interfaz basada en C Muchos manipuladores de interrupci´on on devuelven el control al programa interrumpido cuando ella culmina. Ella restaura todos los registros con los mismos valores que ten´ ten´ıan antes que ocurriera la interrupci´ on. on. As´ As´ı el programa interrumpido se ejecuta como si nada hubiese pasado (excepto que se perdieron algunos ciclos de CPU) Las trampas generalmente no retornan. A menudo ellas acaban el programa.
1.3. 1.3.
Leng Lenguaje uaje ensa ensam mblad blador or
1.3.1.
Lenguaje de m´ aquina aquina
Cada tipo de CPU entiende su propio lenguaje de m´aquina. aquina. Las instrucciones en lenguaje de m´aquina aquina son n´ umeros almacenados como bytes en umeros memoria. Cada instrucci´ on o n tiene su propio y unico u ´ nico c´ odigo odigo llamado c´ odigo 5
Sin embargo, ellas pueden usar una interfaz de bajo nivel (a nivel del kernel)
´ CAP ´ITULO ITULO 1. INTRODUCC INTRODUCCI I ON
12
de operaci´ on u opcode. Las instruccione instruccioness del procesador procesador 80x86 var´ ar´ıan en tama˜ no. no. El opcode est´ a siempre al inicio de la instrucci´ on. on. Muchas instrucciones incluyen tambi´ en en datos (vgr. constantes o direcciones) usados por p or las instrucciones. El lenguaje de m´aquina aquina es muy dif´ıcil ıcil de programar progr amar directam di rectamente. ente. DesciDesci frar el significado de las l as instrucciones codificadas co dificadas num´ ericamente ericamente es tedioso para los humanos. Por ejemplo, la instrucci´ on que suma los registros EAX on y EBX y almacena el resultado en EAX est´a codificada por los siguientes c´ odigos odigos hexadecimales 03 C3 Esto no es obvio. Afortunadamente, un programa llamado ensamblador puede hacer este aburrido trabajo para el programador.
1.3.2. 1.3.2.
Lengu Lenguaje aje ensam ensambla blado dorr
Un programa Escrito en lenguaje ensamblador es almacenado como texto (tal como programas de alto nivel). Cada instrucci´ on representa exactamente on una instrucci´on on de la m´ aquina. Por ejemplo, la instrucci´ aquina. on on de suma descrita arriba podr´ podr´ıa ser representada en lenguaje ensamblador como: add eax, ebx
Ac´ a el significado de la instrucci´ on on es mucho m´ as as claro que el c´odigo odigo de la m´ aquina. aquina. La palabra add es el nem´ onico para la instrucci´ on o n de suma . La forma general de una instrucci´on on de ensamblador es: mnemonico mnemonico operando(s) operando(s)
Un ensamblador es un programa que lee un archivo de texto con instrucciones de ensamblador y convierte el ensamblador en c´odigo odigo de m´ aquina. aquina. Los compiladores son programas que hacen conversiones similares para lenguajes de programaci´ on de alto nivel. Un ensamblador es mucho m´as on as simple que un compilador. Cada instrucci´ instrucci´ on de lenguaje ensamblador representa una sola on Les tom´ tom´ o vari vario os a˜ nos a compilador. los cient´ıficos ıficos de la compu- instrucci´ on on de la m´aquina. aquina. Las instrucciones de un lenguaje de alto nivel son taci´ on imag imagin inar arse se c´ omo mucho m´ as complejas y pueden requerir muchas instrucciones de m´aquina. as aquina. 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 m´ aquina, aquina, tambi´ en en tiene su propio lenguaje ensamblador. Trasladar programas entre arquitecturas de computador diferentes es mucho m´ as as dif´ıcil ıci l que en un lenguaje de alto nivel. En los los ejem ejempl plos os de este este libro libro se usa usa Ne Netw twid idee Asse Assemb mble lerr o NA NASM SM . Est´a disponible libremente en Internet (vea el prefacio para la URL). Los ensambladores m´ as comunes son el ensamblador de Microsoft (MASM) y el as de Borland (TASM) . Hay algunas diferencias en la sintaxis del ensamblador de NASM, MASM y TASM .
1.3. LENGUAJE LENGUAJE ENSAMBL ENSAMBLADOR ADOR
1.3.3. 1.3.3.
13
Operand Operandos os de las las instru instrucc ccion iones es
Los c´odigos odigos de las instrucciones de m´ aquina tienen una variedad de tipos aquina y operandos; sin embargo, en general cada instrucci´on on en s´ı misma tiene un n´umero umero fijo de operandos (0 a 3). Los operandos pueden tener los siguientes tipos: registro: Estos operandos se refieren directamente al contenido de los registros de la CPU. memoria: Estos se refieren a los datos en la memoria. La direcci´on o n de los datos puede ser una constante escrita en la instrucci´ on o n o puede ser calculada usando los valores de los registros. Las direcciones son siempre siempre desplazamien desplazamientos tos relativos relativos al comienzo comienzo de un segmento. segmento. immediato: Estos son valores fijos que est´an an listados en la instrucci´on on en s´ı misma. Ellos son almacenados al macenados en la instrucci´ on on en si misma (en el segmento de c´odigo), odigo), no en el segmento de datos. impl´ im pl´ıcit ıc ito: o: Estos operandos no se muestran expl´ expl´ıcitamente. Por ejemplo, la instrucci´ on on de incremento a˜ nade uno a un registro o a memoria. El nade uno est´a impl´ im pl´ıcit ıc ito. o.
1.3.4.
instrucciones b´ asicas asicas
La instrucci´on on esencial es MOV . Ella translada datos de un lugar a otro (como el operador de asignaci´on on en un lenguaje de alto nivel). Toma dos operandos: mov dest, dest, src
El dato especificado por src es copiado a dest . Una restricci´on on es que los dos operandos no pueden ser operandos de memoria. Esto se˜ nala nala otra peculiaripeculiaridad del ensamblador. Hay a menudo algunas reglas arbitrarias sobre c´ omo omo se usan las instrucciones. Los operandos deben tener el mismo tama˜no. no. El valor de AX no puede ser almacenado en BL. Ac´ a hay un ejemplo(los ; inician un comentario) m mov ov mov
eax, eax, 3 bx, ax ax
; alma almace cena na 3 en en el reg regis istr tro o EAX EAX (3 es es el ope opera rand ndo o inme inmedi diat ato) o) ; al almacena el el va valor de de AX AX en en el el re registro BX BX
La instrucci´on on ADD se usa para sumar enteros. add add
eax, 4 al, ah
; eax = eax + 4 ; al = al + ah
La instrucci´on on SUB resta enteros.
´ CAP ´ITULO ITULO 1. INTRODUCC INTRODUCCI I ON
14 sub sub
bx, 10 ; bx = bx - 10 ebx, ed edi ; ebx = eb ebx - ed edi
Las instrucciones INC y DEC incrementan o decrementan valores en uno. Ya que el uno es un operando op erando impl i mpl´´ıcito, el c´ odigo odigo de de m´ aquina aquina para INC y el DEC es m´as as peque˜ no que los de las instrucciones ADD y SUB. no inc dec
ecx dl
1.3.5. 1.3.5.
; ecx++ ; dl--
Direc Directiv tivas as
Una directiva es un artificio del ensamblador no de la CPU. Ellas se usan generalmente para decirle al ensamblador que haga alguna cosa o informarle al ensamblador ensamblador de algo. Ellas no se traducen traducen en c´ odigo odigo de m´ aquina. aquina. Los usos comunes de las directivas son:
• Definir constantes • Definir memoria para almacenar datos en ella • Definir la memoria para almacenar datos en ella • Agrupar la memoria en segmentos In clu´ u´ır ır c´odigo odigo fuente condicionalmente • Incl Inc lu´ır ır otros otr os archivos archi vos • Inclu´ El c´ odigo odigo de NASM NASM pasa pasa a trav´ trav´ es es de un preproc preprocesa esador dor tal com comoo C. Tiene muchas de las ordenes o´rdenes del preprocesador tal como C. Sin embargo las directivas del preprocesador de NASM comienzan con un como en C. directiva equ La directi d irectiva va equ se puede p uede usar para definir un s´ımbolo. ımbolo . Los L os s´ımbolos ımbolo s son so n constantes con nombre que se pueden emplear en el programa ensamblador. El formato es: s´ ımbolo equ valor
Los valores de los lo s s´ımbolos ımbolos no se pueden redefinir posteriormente. La directiv directiva a %define %define Esta directiva es parecida a la #define de C. Se usa normalmente para definir macros tal como en C. %define %define SIZE 100 mov eax, SIZE
1.3. LENGUAJE LENGUAJE ENSAMBL ENSAMBLADOR ADOR
15
Unidad byte palabra palabra doble palabra palabra cu´ adruple diez bytes
Letra B W D Q T
Cuadro 1.3: Letras para las directivas RESX y DX El c´odigo odigo de arriba define un macro llamado size y muestra su uso en una instrucci´ on MOV. Los macros son m´as on as flexibles que los s´ımbolos de dos maneras. Los macros se pueden redefinir y pueden ser m´as as que simples constantes n´umericas. umericas. Directivas de datos Las directivas de datos son usadas en segmentos de datos para definir espacios de memoria. Hay dos formas en que la memoria puede ser reservada. La primera es solo definir el espacio para los datos; la segunda manera define el espacio y el valor inicial. El primer m´etodo etodo usa una de las directivas RESX. La X se reemplaza con una letra que determina el tama˜no n o del objeto (u objetos) que ser´a almacenados. El Cuadro 1.3 muestra los valores posibles. El segundo m´etodo etodo (que define un valor inicial tambi´ en) en) usa una de las directivas DX. Las X son las mismas que las de la directiva RESX. Es muy com´ un marcar lugares de memoria con etiquetas. Las etiquetas un le permiten a uno referirse f´acilmente acilmente a lugares de la memoria en el c´ odigo. odigo. Abajo hay varios varios ejemplos. ejemplos. L1 L2 L3 L4 L5 L6 L7 L8
db dw db db db dd resb db
0 1000 110101 101b 12h 17o 1A92h 1 "A"
; ; ; ; ; ; ; ;
byte etiquetado como L1 con valor inicial 0 palabra etiquetada como L2 con valor inicial de 1000 byte con va valor lor inic nicial bi binari ario de 11010 0101 (53 (53 en decim cimal) 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 codigo o´digo ASCII ASCII para para A (65) (65)
Las comillas dobles o simples se interpretan igual. Las definiciones consecutivas de datos se almacenar´an an secuencialmente en memoria. As´ As´ı que, la palabra L2 se almacenar´a inmediatamente despu´es es que la L1. Se pueden definir tambi´en en secuencia se cuenciass de memoria. memori a. L9
db
0, 1, 2, 3
; define 4 bytes
´ CAP ´ITULO ITULO 1. INTRODUCC INTRODUCCI I ON
16 L10 L11
db db
"w", "o "o", "r "r", ’d ’d’, 0 ’word’, 0
; de define un una ca cadena ti tipo C = "w "word" ; igual que L10
La directiva directiva DD se puede usar para definir o enteros o constantes de punto flotante de presici´on on simple.6 Sin embargo DQ solo se puede usar para definir constantes de punto flotante de doble precisi´on. on. Para secuencias largas la directiva TIMES de NASM es a menudo util. u ´ til. Esta directiva repite su operando un n´umero umero especificado de veces por ejemplo: 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 c´odigo. odigo. Si se usa una etiqueta etiqueta ´esta esta es interpre interpretada tada como la direcci´ direcci´ on (o desplazamien desplazamiento) to) del dato. Si la etiqueta etiqueta es colocada dentro dentro de par´ par´entesis entesis cuadrados ([]), se interpreta como el dato en la direcci´on. on. En otras palabras, uno podr´ p odr´ıa ıa pensar p ensar de d e una etiqueta etiquet a como com o un apuntador al dato da to y los par´ p ar´enteentesis cuadrados como la des referencia al apuntador tal como el asterisco lo hace en C (MASM y TASM siguen una convenci´on on diferente). En el modo de 32 bits las direcciones son de 32 bits. A continuaci´on, on, algunos algunos ejemplos. ejemplos. 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 esta ´ en en L1 en en AL AL EAX = direccion o ´n del byte en L1 copia AH en el byte en L1 copia la palabra doble en L6 en EAX EA EAX = EA EAX + la pa palabra doble en L6 la palabra doble en L6 += EAX co copia el el primer by byte de de la palabra do doble en L6 L6 en AL AL
La l´ınea 7 de los ejemplos ejemplos muestra muestra una propiedad propiedad importante importante de NASM. NASM. El ensamblador no recuerda el tipo de datos al cual se refiere la etiqueta. De tal forma que el programador debe estar seguro que usa la etiqueta correctamente. Luego ser´a com´ un almacenar direcciones de datos en registros un y usar los registros como una variable apuntador en C. Una vez m´as a s no se verifica que el apuntador se use correctamente. De este modo el ensamblador es mucho m´ as propenso a errores a´ as un un que C. Considere la siguiente instrucci´on: on: mov
[L6], 1
; almacena 1 en
L6
Esta instrucci´on on produce un error de tama˜ no no no especificado espec ificado.. ¿Por ¿ Por qu´e? e? Porque el ensamblador no sabe si almacenar el 1 como byte, palabra o palabra doble. Para definir esto, se a˜ nade un especificador de tama˜ nade no no . 6
Punto flotante de presici´ on simple es equivalente a la variable float en C. on
1.3. LENGUAJE LENGUAJE ENSAMBL ENSAMBLADOR ADOR mov
dword [L6], 1
17
; almacena 1 at L6
Esto le dice al ensamblador que almacene un 1 en la palabra doble que comienza en L6. Otros especificadores son: BYTE, WORD, QWORD Y TWORD. 7
1.3.6. 1.3.6.
Entra En trada da y Salid Salida a
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 interfaz de programaci´on on simple y uniforme para la E/S. Los lenguajes ensamblador no disponen de bibliotecas normalizadas. Ellos deben acceder directamente al hardware (que es una operaci´on on privilegiada en el modo protegido) o usar las rutinas de bajo nivel que provea el sistema operativo. Es muy com´ un que se interfacen rutinas de ensamblador con C. Una de un las ventajas de esto es que el c´odigo odigo en ensamblador puede usar las rutinas E/S de las bibliotecas estandar de C. Sin embargo uno debe conocer las reglas que usa C para pasar informaci´on on entre entre rutina rutinas. s. Estas Estas reglas reglas son muy complicadas para cubrir ac´a (ellas se ver´an an luego). Para simplificar la E/S el autor ha desarrollado sus propias rutinas que ocultan las complejas reglas de C y provee una interfas mucho m´as as simple. El Cuadro 1.4 describe las rutinas suministradas. Todas las rutinas preservan el valor de todos los registros, registros, excepto las rutinas de lectura. lectura. Estas rutinas modifican el valor del registro EAX. Para usar estas rutinas uno debe incluir un archivo con la informaci´ on que el ensamblador necesita para poder usarlas. Para incluir un on archivo en NASM use la directiva del preprocesador %include. La siguiente l´ınea incluye i ncluye el archivo ar chivo necesario necesari o para las l as rutinas ruti nas de E/S E/ S hechas por p or el autor au tor8 : %include "asm_io.inc" "asm_io.inc"
Para usar una de las rutinas print, uno carga EAX con el valor correcto y usa la instrucci´on on CALL para invocarla. La instrucci´ on on CALL es equivalente a un llamado de funci´on on en un lenguaje de alto nivel. Hace un salto en la ejecuci´ on on hacia otra secci´ on on de c´odigo odigo pero despu´ es es retorna al origen luego que la rutina a culminado. El programa muestra varios ejemplos de llamadas de estas rutinas de E/S. 7
TWORD define un ´area area de memoria de 10 bytes. El coprocesador de punto flotante usa este tipo de dato. 8 El archivo archivo asm io.inc (y el archiv archivo o objeto objeto asm io.o io.o archiv archivo o que asi io.inc necesita) cesita) est´ an a n en los los ejem ejempl plos os que que se encu encuen enta tan n en la p´ agina agina web web del autor: autor: http: //www.drpaulcarter.com/pcasm
´ CAP ´ITULO ITULO 1. INTRODUCC INTRODUCCI I ON
18 print int print char print string
print nl read int read char
imprime en la pantalla el valor del entero almacendo en EAX imprime en la pantalla el caracter cuyo c´ odigo odigo ASCII est´e alma a lmacend cendo o en e n AL AL imprime en la pantalla el contenido de la cadena en la direcci´ on almacenada en EAX. La cadena debe ser tipo C, (terminada en NULL). imprime en pantalla el caracter de nueva nueva l´ınea. lee un entero del teclado y lo almacena en el registro. lee un solo caracter del teclado y almacena el c´odigo odigo ASCII en el registro EAX.
Cuadro 1.4: Rutinas de E/S en ensamblador
1.3.7. 1.3.7.
Depu Depurac raci´ i´ on on
La biblioteca del autor tambi´ en en contiene algunas rutinas utiles ´ para depurar los programas. Estas rutinas de depuraci´ on muestran informaci´ on on on sobre el estado del computador sin modificar su estado. Estas rutinas son en realidad macros que muestran el estado de la CPU y luego hacen un llamado a una subrutina. subrutina. Los macros est´ an definidos en el archivo asm io.inc discuan tido antes. Los matros se usan como instrucciones normales. Los operandos de los macros se separan con comas. Hay cuatro rutinas de depuraci´on on llamadas dump regs, dump mem, dump stack and dump math; Ellas muestran los valores de los registros, memoria, pila y el coprocesador coprocesador matem´ atico atico respctivamente dump regs Este macro imprime los valores de los registros (en hexadecimal) del computador stdout (la pantalla). pantalla ). Tambi´en en imprime im prime el estado es tado 9 de los bits del registto FLAGS . Por ejemplo si la bandera cero es 1 se muestra ZF. Si es cero no se muestra nada. Torma un solo entero como par´ ametro que luego se imprime. Este entero se puede usar para ametro distinguir la salida de diferentes ordenes o´rdenes dump regs. dump mem Este macro imprime los valores de una regi´on on de memoria (en hexadecimal) y tambi´ en en como caracteres ASCII. ASCI I. Toma tres argumentos delimitados por comas. El primero es un entero que es usado para identificar la salida (tal cual como el argumento de dump regs). El segundo argumento es la direcci´ on on a mostrar (´esta esta puede ser una etieti queta). El ultimo u ´ ltimo argumento es un n´ umero umero de p´arrafos arrafos de l6 bytes para mostrar luego de la direccci´on. on. La memoria memoria mostrada mostrada comenzar´ comenzar´ a en el primer pri mer l´ımite ımi te de p´arrafo arrafo antes de la direcci´ on on solicitada. solicitada. 9
El Cap´ıtulo ıtulo 2 discute di scute este registro re gistro
1.4. CREANDO CREANDO UN PRO PROGRAM GRAMA A
19
dump stack Este macro imprime los valores de la pila de la CPU (la pila se ver´a en el Cap´ Cap´ıtulo 4). La pila est´ a organizada como palabras dobles y est´a rutina las mostrar´a de esta forma. Toma tres par´ ameametros separados por comas. El primero es un identificador entero (como umero de palabras dobles para mostrar umero dump regs). El segundo es el n´ antes de la direcci´on on que tenga almacenada el registro EBP, y el tercer argumento es el n´ umero de palabras dobles a imprimir luego de la umero direcci´ on on de EBP. dump math Este macro imprime los valores de los registros del coprocesador matem´ atico. Toma un solo par´ atico. ametro entero como argumento que ametro se usa para identificar la salida tal como el argumento de dump regs lo hace.
1.4. 1.4.
Crea Creando ndo un prog progra rama ma
Hoy d´ıa no es com´un un crear todo un programa independiente escrito totalmente en lenguaje ensamblador. El ensamblador es usado para desarrollar cierta cie rtass ruti r utinas nas cr´ıtica. ıti ca. ¿Por qu´e? e? Es mucho m´ as as f´acil acil programar en un lenguaje de alto nivel nivel que en ensamblador ensamblador.. Tambi´ ambi´en en al usar ensamblador ensamblador es muy dif´ dif´ıcil transportar transp ortar el programa a otras plataformas. De hecho, es raro usar el ensamblador en cualquier circunstancia. As´ As´ı, ¿Por qu´e alguien algui en quisiera aprender aprende r ensamblador? ensamblad or? 1. Algunas Algunas veces veces el c´odigo odigo escrito en ensamblador puede ser m´as as r´apido apido y peque˜ no no que el c´odigo odigo generado por un compilador. 2. El ensamblador permite p ermite acceder directamente a caracter´ caracter´ısticas del hardware dware del sistema sistema que puede ser dif´ dif´ıcil o imposible de usar desde un lenguaje de alto nivel. 3. Aprend Aprender er a progra programar mar en ensam ensamblad blador or le ayuda ayuda a uno a ganar ganar un entendimiento profundo de c´omo omo trabaja el computador. 4. Aprender Aprender a programar programar en ensamblador ensamblador ayuda a entender mejor c´ omo omo trabajan los compiladores y los lenguajes de alto nivel como C. Los ultimos u ´ ltimos dos puntos demuestran que aprender ensamblador puede ser util u ´ til a´ un un si uno nunca nunca programa programa en ´el el posteriormen posteriormente. te. De hecho, hecho, el autor raramente raramente programa en ensamblador ensamblador pero p ero usa las ideas aprendidas aprendidas de ´el el todos to dos los d´ıas. ıas .
´ CAP ´ITULO ITULO 1. INTRODUCC INTRODUCCI I ON
20 int main()
{
int ret status ;
ret statu statuss = asm asm main(); main(); return ret status ;
} Figura 1.6: c´ odigo odigo de driver.c
1.4.1. 1.4.1.
Primer Primer progr programa ama
Los primeros programas en este texto comenzar´an an todos con un programa sencillo de C mostrado en la Figura 1.6 1.6.. Simplemente llama otra funci´ on on llamada asm main. Esta es la rutina escrita en ensamblador. Hay varias ventajas de usar este programa en C. Primero dejamos que C fije todos los par´ametros ametros para que el programa se ejecute correctamente en el modo protegido. Todos los segmentos y sus correspondientes registros de segmento ser´ an iniciados por C. El c´odigo an odigo en ensamblador ensamblador no necesita necesita preocuparse preocuparse de nada de esto. Segundo, las bibliotecas de C estar´ an disponibles para ser usaan das en el c´odigo odigo de ensamblador. Las rutinas de E/S del autor aprovechan esto. Ellas usan las funciones de E/S de C ( printf, etc.). Ahora se muestra un programa elemental en ensamblador. 1 2 3
first.asm ; Archivo Archivo: : first.a first.asm sm ; Pr Prime imer r progr programa ama en en ensam sambl blado ador. r. Este Este prog program rama a pide pide dos ; en enter teros os co como mo en entr trada ada e im impri prime me su suma suma
4 5 6 7 8
; Pa Para ra cr crear ear el ej ejecu ecuta table ble usand usando o djgp djgpp: p: ; ; na nasm sm -f co coff ff fi firs rst.a t.asm sm ; gc gcc c -o first first fi firs rst.o t.o drive driver.c r.c asm_i asm_io.o o.o
9 10 11 12 13 14 15 16 17 18 19
%include %include "asm_io.inc" "asm_io.inc" ; ; Lo Los s da datos tos in inici iciad ados os se co coloc locan an en el segm segment ento o .data .data ; segment .data ; ; Es Estas tas etiqu etiqueta etas s se re refie fiere ren n a las caden cadenas as usad usadas as para para la salid salida a ; prompt1 pt1 db "Di "Digite un nu ´mero: ", 0 ; no olvide el fin de cadena prompt2 pt2 db "Di "Digite otro numer u ´mero: o: ", 0
1.4. CREANDO CREANDO UN PRO PROGRAM GRAMA A 20 21 22
outm utmsg1 db db outmsg2 db outmsg3 db db
21
"Ud. ha di digitad tado ", ", 0 " y ", 0 ", la la su suma es es ", ", 0
23 24 25 26 27 28 29 30 31 32 33
; ; Lo Los s datos datos no inici iniciado ados s se co coloc locan an en el se segme gment nto o .bss .bss ; segment segment .bss .bss ; ; Es Esta tas s et etiqu iquet etas as se se~ nalan n ~alan a pa palab labra ras s do doble bles s usada usadas s para para alma almacen cenar ar los los dato datos s ; de entra entrada da ; inpu input1 t1 resd resd 1 inpu input2 t2 resd resd 1
34 35 36 37 38 39 40 41 42
; ; El c´ odigo o digo se coloc coloca a en el se segme gment nto o .t .text ext ; segment .text gl globa obal l _asm_ _asm_mai main n _asm_main: enter 0,0 ; setup routine pusha
43 44 45
mov call call
eax, prompt1 prin print_ t_st stri ring ng
; print out prompt
call mov
read_int [input1], eax
; lee un entero ; lo almacena en input1
mov call call
eax, prompt2 prin print_ t_st stri ring ng
; print out prompt
call mov
read_int [input2], eax
; lee un entero ; lo almacena en input2
mov add mov
eax, [input1] eax, [input2] ebx, eax
; eax = dword en input1 ; eax += dword en input2 ; ebx = eax
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
dump_regs 1 dump dump_m _mem em 2, outm outmsg sg1, 1, 1
; imprime los valores de los registros ; imp impri rime mer r la la mem memor oria ia
´ CAP ´ITULO ITULO 1. INTRODUCC INTRODUCCI I ON
22 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
; ; ah ahora ora, , se im impri prime men n lo los s re resul sulta tados dos ; mov eax, outmsg1 call print_string ; mov eax, [input1] call print_int ; mov eax, outmsg2 call print_string ; mov eax, [input2] call print_int ; mov eax, outmsg3 call print_string ; mov eax, ebx call print_int ; call print_nl ;
en una una serie serie de pasos pasos
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
78 79 80 81 82
popa mov leave ret
eax, 0
; retorna a
C
first.asm
La l´ınea 13 del programa define una secci´ on del programa que especifica on la memoria del segmento de datos (cuyo nombre es .data ). Solo los datos iniciados inici ados se s e deber deb er´´ıan definir d efinir en este est e segmento. segm ento. En E n las l´ıneas 17 a 21 se declaran varias cadenas. Ellas ser´an an impresas con las bibliotecas de C y como tal deben estar terminadas con el caracter null (el c´ odigo ASCII 0). Recuerde odigo que hay una gran diferencia entre 0 y ’0’. Los datos no iniciados inicia dos deber´ıan ıan declararse en el segmento bss (llamado ınea 26). Este Este segmen segmento to toma toma su nombre nombre de un operado operadorr de .bss en la l´ınea ensamblador basado en UNIX que significa “block “ block started by simbol ”. ”. Existe tambi´ en en el segmento de la pila. Ser´ a discuti disc utido do despu´ desp u´es. es. El segmento de c´odigo odigo es llamado .text por razones hist´oricas. oricas. Ac´a es donde se colocan las instrucciones. Observe que la etiqueta de la rutina principal (l´ (l´ınea 38) tiene un prefijo de gui´ on bajo. Esto es parte de las conon venciones de llamado de C . Esta convenci´on on especifica las reglas que usa C cuando cuando compila compila el c´ odigo. Es muy importante conocer esta convenci´on odigo. on cuando se interfaza C con ensamblador. Luego se presentara toda la convenci´on; on; sin embargo por p or ahora uno solo necesita conocer que todos los s´ımbolos ımbolos de C (funciones y variables globales) tienen un gui´ on bajo como prefijo anexado a on ellos por p or el compilador de C. (Esta regla es espec´ espec´ıfica para DOS/Windows, el compilador de C de Linux no antepone nada a los nombres de los s´ımbolos). ımbolos).
1.4. CREANDO CREANDO UN PRO PROGRAM GRAMA A
23
La directiva global en la l´ınea 37 le dice al ensamblador ensamblador que tome la etiqueta asm main como universal. A diferencia de C, las etiquetas tienen un alcance alcance interno por omisi´ on. Esto significa que solo el c´odigo on. odigo en el mismo m´ odulo puede usar la etiqueta. La directiva global da a la(s) etiqueta(s) odulo espec´ esp ec´ıficada ıfic ada(s) (s) un alcance externo. externo. Este tipo de etiqueta puede ser conocido por cualquier m´odulo odulo en el programa. El m´ odulo odulo asm io declara las etiquetas p or qu´e uno puede usarlas en el m´ odulo odulo print int etc., globales. Este es el por first.asm.
1.4.2. 1.4.2.
Dependen Dependencias cias del compilad compilador or
El c´ odigo odigo de ensamblador ensamblador de arriba arriba es espec´ espec´ıfico del compilador compilador libre 10 11 GNU C/C++ DJGPP . 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 (iCommon (iCommon Objet File Format). Format). Para ensamblar este formato use la opci´ on on -f coff odigo). odigo). coff con nasm (como se muestra en los comentarios del c´ La extensi´on on del archivo objeto resultante ser´a o. El compilador compil ador de C de Linux tambi´en en es GNU. Para convertir convertir el c´ odigo odigo para que corra bajo Linux simplemente quita los gui´ on on bajos de prefijos en las l´ıneas ıneas 37 y 38. Linux usa el formato ELF (Excecutable an Linkable Format) mat) para los archivos objetos. Use la opci´ on on -f elf Linux . Tambi´en en elf para Linux. produce un objeto con una extensi´on on o. Borland C/C++ es otro compilador popular. Usa el formato de OMF de microsoft para los archivos objeto. Use la opci´ on on -f obj obj para los compiladores de Borland. La extensi´on on del archivo objeto ser´a obj. El formato OMF utiliza unas directivas de segmento diferentes que los otros formatos de objetos. ob jetos. El segmento de datos (l´ (l´ınea 13) se debe cambiar a: segment segment DATA public public align=4 align=4 class=DA class=DATA TA use32
el segmento bss (line 26) se debe cambiar a: se segme gment nt BS BSS S pu publi blic c align align=4 =4 cl class ass=B =BSS SS use32 use32
El segmento text (line 36) se debe cambiar a: segment segment TEXT public public align=1 align=1 class=CO class=CODE DE use32
Adem´ as as se debe a˜nadir nadir una nueva l´ınea ınea antes de la l´ınea 36. grou group p DGR DGROU OUP P BSS BSS DATA DATA 10 11
GNU es un proyecto de la Free Software Foundation (http://www.fsf.org) http://www.delorie.com/djgpp
Los archivos de un compilador lador dado dado est´ an disponidisponibles en la p´ agina web del autor ya modific modificados ados para para que trabajen con el compilador apropiado.
´ CAP ´ITULO ITULO 1. INTRODUCC INTRODUCCI I ON
24
El compilador de Microsoft C/C++ puede usar el formato OMF o el Win32 para los archiv archivos os objeto (si le dan un formato formato OMF, ´el el convierte convierte la informaci´ informaci´ on internamente en Win32). El formato Win32 permite que los on segmentos se definan tal como DJGPP y Linux. Use la opci´on on -f win32 win32 para este formato. La extensi´ on del archivo objeto ser´a obj. on
1.4.3. 1.4.3.
Ensam En sambla bland ndo o el el c´ odigo odigo
El primer paso es ensamblar el c´odigo. odigo. Desde la l a l´ınea de orden digite: dig ite: nasm nasm -f formato-de-objeto first.asm
Donde el formato del objeto es coff , coff ,elf , elf , obj o obj o win32 dependiendo win32 dependiendo que compilador de C ser´a usado. usado . (Recuerde (Recue rde que tambi´ t ambi´en en se deben deb en cambiar cambia r los archivos ar chivos fuente para el caso de Linux y Borland).
1.4.4. 1.4.4.
Compil Compilan ando do el c´ odigo odigo de C
Compile el archivo driver.c usando un compilador de C. Para DJGPP, digite: gcc -c dr drive iver. r.c c
La opci´ on on -c significa que solo compile, no intente encadenar a´ un. un. Esta misma opci´ on trabaja en los compiladores de Linux, Borland, y Microsoft on tam ambi bi´´en. en .
1.4.5. 1.4.5.
encaden encadenand ando o los los arc archiv hivos os objeto objeto
El encadenamiento es un proceso de combinar el c´ odigo odigo de m´ aquina aquina y los datos en archivos objeto con archivos de biblioteca para crear un archivo ejecutable. Como se ver´a adelante, este proceso es complicado. El c´ odigo de C requieren la biblioteca estandar de C y un c´ odigo odigo de inicio especial para ejecutarse. Es mucho m´ as as f´acil acil dejar que el compilador de C llame al encadenador con los par´ametros ametros correctos que intentar llamar al encadenador directamente. Por ejemplo encadenar el c´odigo odigo para el primer programa utilizando DJGPP, digite: gcc -o fi first rst dr drive iver.o r.o fi first rst.o .o asm asm io.o io.o
Esto crea un ejecutable llamado first.exe (o solo first bajo Linux). Linux). Con Borland Borlan d uno usar´ usar´ıa: bcc32 bcc32 first.o first.obj bj driver. driver.obj obj asm io.obj io.obj
1.4. CREANDO CREANDO UN PRO PROGRAM GRAMA A
25
Borland usa el nombre del primer archivo en la lista para determinar el nombre del ejecutable. As´ As´ı en el caso anterior el programa deber´ deber´ıa llamarse first.exe. Es posible combinar el paso de compilar y encadenar. Por ejemplo: gc gcc c -o first first driver.c first.o first.o asm io.o
Ahora gcc compilar´ a driver.C y entonces lo encadenar´a. a.
1.4.6. 1.4.6.
Entend En tender er un archiv archivo o de listado listado de ensamb ensamblado ladorr
La opci´ on on -l archivo-de-listado se puede usar para decirle a nasm que cree un archivo de listado con un nombre dado. Este archivo muestra c´ omo omo se ensambl´ o el c´ odigo. odigo. Se muest muestra ra c´ omo las l´ıneas omo ıneas 17 y 18 (en el segmento data) aparecen en el archivo de listado. Los n´ umeros umer os de las l´ıneas ıne as est´an an en el archivo de listado; sin embargo observe que los n´umeros umeros de las l´ıneas en el archivo fuente pueden no ser los mismas que las del archivo de listado. 48 49 50 51 52
0000 000000 0000 00 00000009 00000009 0000 000000 0011 11 0000001A 0000001A 0000002 00000023 3
456E 456E74 7465 6572 7220 2061 6120 206E 6E756D6265723A20 756D6265723A2000 00 456E 456E74 7465 6572 7220 2061 616E 6E6F 6F74686572206E75 74686572206E756D626D6265723A2 65723A2000 000
prom prompt pt1 1 db
"Ent "Enter er a numb number er: : ", 0
prom prompt pt2 2 db
"Ent "Enter er anot anothe her r numb number er: : ", 0
La primera columna en cada l´ınea ınea es el n´ umero umero de l´ınea y la segunda es el desplazamiento (en hex) de los datos en el segmento. La tercera columna muestra los valores en hexadecimal que ser´an an almacenados. En este caso el dato hexadecimal corresponde a c´ odigos odigos ASCII. Finalmente en la l´ınea se muestra el texto del c´odigo odigo fuente. fuente. Los desplazamiento desplazamientoss mostrados mostrados en la segunda columna es muy probable que no sean los desplazamie desplazamientos ntos reales, de los datos que ser´an an colocados en el programa completo. Cada m´ odulo odulo puede definir sus propias etiquetas en el segmento de datos ( y en los otros segmentos tambi´en). en). En el paso de encadenamiento vea la l a Secci´ on on 1.4.5, todas estas definiciones de segmentos y etiquetas son combinadas para formar un solo segmento de datos. El encadenador entonces calcula el desplazamiento definitivo. Se muestra una peque˜na na secci´on on (l´ (l´ı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
26
´ CAP ´ITULO ITULO 1. INTRODUCC INTRODUCCI I ON
La tercera columna muestra el c´ odigo odigo de m´ aquina generado por el ensamblaaquina dor. A menudo el c´odigo odigo completo de una instrucci´ on on no se puede calcular a´un. un. Por ejemplo, en la l´ınea ınea 94 el desplazamiento (o direcci´ on) on) de input1 no se conoce hasta que el c´odigo odigo se encadene. El ensamblador puede calcular el c´odigo odigo de la instrucci´on on mov (que del listado es A1), pero escribe el desplazamiento en par´ entesis entesis cuadrados porque el valor exacto no se puede calcular en este momento. En este caso se utiliza un desplazamiento temporal de 0 porque input1 est´a al inicio de la parte del segmento bss definido en este archivo. Recuerde que esto no significa que estar´a al comienzo del segmento bss definitivo del programa. Cuando el c´odigo odigo es encadenado, el encadenador insertar´a el desplazamiento en la posici´ on on correcta. Otras instrucciones como la l´ınea ınea 96 no hacen referencia a ninguna etiqueta. En este caso el ensamblador puede calcular el c´odigo odigo de m´ aquina aquina completo. Representaciones Big y Little Endian Si uno mira de cerca en la l´ınea 95 hay algo muy extra˜ no sobre el desplazamiento plaza miento en los lo s par´entesis entesis cuadrados cuadrado s del c´ odigo odigo de m´ aquina. aquina. La etiqueta a definido en este archivo); sin input2 tiene un desplazamiento de 4 (como est´ embargo, el desplazamiento que aparece en la memoria no es 0000004, pero 04000000. ¿Por qu´e? e? Diferentes procesadores almacenan enteros de varios varios bytes en ordenes diferentes en la memoria. Existen dos m´etodos etodos populares B ig endian e ndian es el m´etodo eto do que qu e Endian se pronuncia como de almacenar enteros: big endian y littel endian . Big indian. indian. se ve m´as as natural. El byte mayor (m´ (m´ as significativo) significativo ) se almacena primero, y luego los siguiente siguientes. s. Por ejemplo, la palabra palabra doble 00000004 se deber´ deber´ıa almacenar como los cuatro bytes 00 00 00 04. Los mainframes IBM, la mayor´ yor´ıa de los procesadores pro cesadores RISC y los procesadores pro cesadores Motorola todos ellos usan el m´etodo etodo de Big endian. Sin embargo los procesadores Intel us´ an an el m´eto et o do little endian. De tal manera que se almacena primero el byte menos significativo. As´ As´ı 00000004 se almacenar´ a en memoria como 04 00 00 00. Este formato est´ a cableado en la CPU y no se puede cambiar. Normalmente el programador no necesita preocuparse sobre que formato est´a usando. Sin embargo embargo hay circunstancias circunstancias donde esto es importante. importante. 1. Cuando un dato binario es transfiere transfiere entre entre computadores computadores diferentes diferentes (o de archivos o a trav´es es de d e una un a red) re d) 2. Cuando un dato binario es escrito escrito fuera de la memoria memoria como un entero entero multibyte y luego se vuelve a leer como bytes individuales o vice versa Lo Endian no se aplica al orden de los elementos elementos de un arreglo. arreglo. El primer primer elemento de un arreglo est´ a siempre en la direcci´on on menor. Esto se aplica a cadenas (que s´olo olo son arreglos de caracteres). Lo Endian s´ olo olo se aplica a los elementos elementos individuales individuales de un arreglo. arreglo.
1.5. AR ARCHIV CHIVO O ESQUELETO ESQUELETO 1 2 3 4 5 6
27
skel.asm %include "asm_io.inc" "asm_io.inc" segment .data ; ; lo los s datos datos in inic iciad iados os se coloc colocan an en el segme segmento nto de ; dato datos s ac´ aca ´ ;
7 8 9 10 11 12 13 14 15 16
segment segment .bss .bss ; ; Da Dato tos s no inici iniciado ados s se co coloc locan an en el se segme gment nto o bss bss ; segment .text gl globa obal l _asm_ _asm_mai main n _asm_main: enter 0,0 ; rutina de pusha
17 18 19 20 21 22 23 24 25
; ; El c´ odigo odigo est´ esta ´ co colo locad cado o en el se segme gment nto o de texto texto. . No modif modifiqu ique e el ; c´ o odigo digo an antes tes o de desp spu´ u´ es e s de es este te co comen menta tario rio ; popa mov eax, 0 ; retornar a C leave ret skel.asm
Figura Figura 1.7: Programa Programa esqueleto esqueleto
1.5. 1.5.
Arc Archiv hivo esqu esquel elet eto o
La Figura 1.7 muestra un archivo esqueleto que se puede usar como punto de partida para escribir programas en ensamblador
28
´ CAP ´ITULO ITULO 1. INTRODUCC INTRODUCCI I ON
Cap´ıtulo 2
Leng Lengua uaje je ensa ensambla mblado dorr b´ asico asico 2.1. 2.1.
Trabajan rabajando do con con en enteros teros
2.1.1. 2.1.1.
Repre Represen sentac taci´ i´ on on de enteros
Hay dos tipos de enteros: sin signo y con signo. Los enteros sin signo (que son no negativos) est´an an representados de una manera muy directa en binario natural. El n´ umero umero 200 como un byte byte entero entero sin signo ser´ ser´ıa represen representado tado como 11001000 (o C8 en hex). Los enteros con signo (que pueden ser positivos o negativos) se representan de maneras m´as as complejas. Por ejemplo considere 56 +56 como byte ser´ıa ıa representado por p or 00111000. En el papel uno podr´ podr´ıa representar 56 como 111000, pero ¿C´ omo podr´ omo podr´ıa representarse esto en un byte en la memoria del computador? ¿C´ omo se almacenar´ omo almacenar´ıa el signo menos? Hay 3 t´ecnicas ecnicas que se han usado para representar n´umeros umeros enteros en la memoria del computador. Todos estos m´etodos etodos usan el bit m´ as as significativo como un bit de signo. signo. Este bit es 0 si el n´ umero es positivo y 1 si es negativo. umero
−
−
−
Magnitud y signo El primer m´etodo etodo es el m´ as elemental y es llamado magnitud y signo. as signo. Representa los enteros como dos partes. La primera es el bit de signo y la segunda es la magnitud del entero. As´ As´ı 56 ser´ ser´ıa representado como el byte 00111000 (el bit de signo est´ a subrayado) y 56 ser´ ser´ıa 10111 10111000. 000. El mayor valor de un byte ser´ ser´ıa 01111111 o +127 y el menor valor ser´ ser´ıa 11111111 o 127. Para negar un valor, se cambia el bit del signo. Este m´etodo etodo es directo, 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 representac iones podr´ p odr´ıan ıan servir servi r igual. Esto complica com plica la l´ ogi ca de ogica d e la aritm´ ari tm´etica eti ca para la CPU. Segundo, la aritm´etica etica general tambi´ en en es complicada. Si se
−
−
−
29
´ CAP ´ITULO 2. LENGUAJE LENGUAJE ENSAMBL ENSAMBLADOR ADOR BASICO
30
a˜nade nade 10 a 56, esta operaci´ on debe transformarse en la resta de 10 y 56. on Una vez m´as, as, esto complica la l´ ogica ogica de la CPU.
−
Complemento a uno El segundo m´etodo etodo es conocido cono cido como complemento a uno. El complemento a uno de un n´ umero se encuentra invirtiendo cada bit en el n´umero. umero umero. (Otra 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 notaci´on on de complemento a uno calcular el complemento a uno es equivalente a la negaci´ on. on. As´ As´ı 11 1100011 0001111 es la representaci´ representac i´ on on de 56. Observe que el bit de signo fue cambiado autom´aticamente aticamente por el complemento a uno y que como se esperar´ esperar´ıa al aplicar aplicar el complemen complemento to a 1 dos veces produce el n´ umero umero original. Como el primer m´etodo, etodo, hay dos representaciones del cero 00000000 (+0) y 11111111 ( 0). La aritm´etica etica con n´ umeros en complemento umeros a uno es complicada. Hay un truco ´util util para encontrar el complemento a 1 de un n´ umero umero en hexadecimal sin convertirlo convertirlo a binario. El truco es restar el d´ıgito hexadecimal de F (o 15 en decimal). decim al). Este Es te m´etodo etod o supone supo ne que el n´ numero ume ´ ro de d´ıgitos ıgi tos bina b inario rioss en el n´ umero u mero es un m´ ultiplo de 4. Un ejemplo: +56 se representa por 38 ultiplo en hex. Para encontrar encontrar el complemen complemento to a uno reste F de cada d´ d´ıgito para obtener C7 en hexadecimal. Esto es coherente con el resultado anterior.
−
−
−
Complemento a dos Los dos primeros pri meros m´etodos etod os descritos descri tos fueron fuero n usados en e n los primeros prim eros compucomp utadores. Los computadores modernos usan un tercer m´etodo etodo llamado la representaci´ on en complemento a dos. El complemento a dos de un n´umero on umero se halla con los dos pasos siguientes: 1. Hallar el complemento a uno del n´ umero. umero. 2. Sumar uno al resultado del paso 1. Ac´ a est´ a un ejemplo usando 00111000 (56). Primero se calcula el complemento a uno: 11000111. Entonces se a˜ nade nade uno: 11000111 + 1 11001000 En la notaci´ on de complemento a dos, calcular el complemento a dos es on equivalente a negar el n´ umero. umero. As´ As´ı 11 1100100 001000 0 es la representaci rep resentaci´ on o´n en complemento a dos de 56. Dos Do s negaciones negaci ones deber deb er´´ıan repro ducir el n´ umero umero original.
−
2.1. TRABAJAN TRABAJANDO DO CON ENTEROS ENTEROS N´ umero umero 0 1 127 -128 -127 -2 -1
31
Repres Represen entac taci´ i´ on on Hex 00 01 7F 80 81 FE FF
Cuadro 2.1: Representaci´on on de complemento a dos Sorprendentemente el complemento a dos no re´ une este requisito. Tome el une complemento a dos de 11001000 a˜ nadiendo uno al complemento a uno. nadiendo 00110111 + 1 00111000 Cuando realizamos la suma en una operaci´on on en complemento a dos, la 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 tama˜ no no fijo (en t´erminos erminos de n´ umeros de bits). Sumar dos bytes siempre produce como umeros resultado un byte (tal como sumar dos palabras produce otra palabra, etc.). Esta propiedad es importante para la notaci´ on en complemento a dos. Por on ejemplo, considere el cero como un n´umero umero en complemento a dos de un byte (00000000). Calcular el complemento a 2 produce la suma: 11111111 + 1 c 00000000 donde c representa un carry (luego se mostrar´a como detectar este carry, pero no se almacena en el resultado). As´ As´ı en la notaci´ on on de complemento a dos existe solo un cero. Esto hace la aritm´ aritm´etica etica de complement complementoo a dos m´ as as simple que los l os m´etodos eto dos anteriores. anteriore s. Usando la notaci´ on en complemento a dos, un byte con signo se puede on usar para representar los n´umeros umeros desde 128 hasta +127. El Cuadro 2.1 muestra algunos valores seleccionados. Si se usan 16 bits, se pueden representar los n´ umeros umeros con signo desde 32,768 hasta 32,767 que est´ a representado por 7FFF, 32,768 por 8000, 128 como FF80 y -1 como FFFF. Los n´ umeumeros de 32 bits en complemento a dos est´ an an en el rango de 2 mil millones a +2 mil millones aproximadamente. La CPU no tiene ni idea que supuesta representaci´on on tiene un byte en particular (palabra, o palabra doble). El ensamblador no tiene ni idea de los
−
−
−
−
−
´ CAP ´ITULO 2. LENGUAJE LENGUAJE ENSAMBL ENSAMBLADOR ADOR BASICO
32
tipos de datos que tienen los lenguajes de alto nivel. C´omo omo se interpretan los datos depende de qu´ e instrucci´ o n se usa con el dato. Si el valor FF on representa 1 o +255 depende del programador. El lenguaje C define 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.1.2. 2.
Exte Extens nsi´ i´ on on del signo
En ensamblador, todos los datos tienen un tama˜ no no determinado. No es raro necesitar cambiar el tama˜ no del dato para usarlo con otro dato. Reducir no el tama˜ no no es f´acil. acil. Reduciendo el tama˜ no no de los datos Para reducir el tama˜ no del dato simplemente quite los bits m´as no as significativos 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´ a, a , si el n´ umero no se puede representar correctamente en el umero tama˜ no n o m´ as as peque˜ no, no, la reducci´ on on de tama˜ no no no funcionar´ a. a. Por ejemplo si AX era 0134h (o 308 en decimal) entonces el c´ odigo odigo anterior almacenar almac enar´´ıa en CL 34h. Este m´ etodo etodo trabaja con n´ umeros con o sin signo. Considere umeros n´ umeros umeros con signo, si AX era FFFFh (-1 como palabra), entonces code CL ser se r´ıa FFh FFh ( 1 como byte). Sin embargo, observe que ¡esto no es correcto si el valor en AX era sin signo! La regla para n´ umeros sin signo es que todos los bits al ser quitados umeros deben ser 0 para que la conversi´on on sea correcta. La regla para los n´ umeros umeros con signo es que los bits que sean quitados deben ser o todos 1 o todos 0. Adem´ as el primer bit no se debe quitar pero debe tener el mismo valor que as los bits quitados. Este bit ser´a el nuevo bit de signo del valor m´ as as peque˜ no. no. Es importante que sea el bit del signo original.
−
Aumentando el tama˜ no no de los datos Incrementar el tama˜ no no de los datos es m´ as as complicado complicado que disminuirlo. disminuirlo. Considere el byte hex FF. Si se extiende a una palabra, ¿Qu´ e valor deber´ deber´ıa tener la palabra? Depende de c´omo omo se interprete la palabra. Si FF es un byte sin signo (255 en decimal), decimal), entonces entonces la palabra palabra deber´ deber´ıa ser 00FF; sin embargo, si es un byte con signo ( 1 en decimal), decima l), entonces e ntonces la palabra pa labra deber´ıa ıa ser FFFF. En general, para extender un n´umero umero sin signo, uno hace cero todos los bits nuevos del n´ umero umero extendido. As´ As´ı FF se convierte convierte en 00FF. Sin
−
2.1. TRABAJAN TRABAJANDO DO CON ENTEROS ENTEROS
33
embargo, para extender un n´ umero con signo uno debe extender el bit de umero signo. Esto significa 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 n´umero umero con signo 5A (90 en decimal) fue extendido, extendi do, el resultad re sultado o ser´ıa ıa 005A. 0 05A. Existen varias instrucciones que suministra el 80386 para la extensi´ on on de los n´ umeros. Recuerde que el computador no conoce si un n´ umeros. umero umero est´a con o sin signo. Es responsabilidad responsabilidad del programador programador usar la instrucci´ instrucci´ on on adecuada. Para n´ umeros sin signo, uno puede simplemente colocar ceros en los bits umeros superiores usando una instrucci´on on MOV. Por ejemplo, para extender el byte en AL a una palabra sin signo en AX: mov
ah, 0
; cero los
8-bits superiores
Sin embargo, no es posible usar la instrucci´on on MOV para convertir la palabra sin signo en AX a una palabra doble en EAX. ¿Por qu´e no? No hay manera de referirse a los 16 bits superiores de EAX con una instrucci´on MOV. El 80386 resuelve este problema con una nueva instrucci´on on MOVZX. Esta instrucci´on on 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 restricci´on on es que el destino debe ser mayor que la fuente. (La mayor´ mayor´ıa de instrucciones i nstrucciones requieren que la fuente y el destino sean del mismo tama˜no). no). Algunos ejemplos: 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 n´ umeros con signo, no hay una manera f´acil umeros acil de usar la instrucci´on on umeros umeros con MOV. EL 8086 suministra varias instrucciones para extender n´ signo. La instrucci´ on on CBW (Convert Byte to Word) Word ) extiende el registro AL en AX. Los operandos son impl´ impl´ıcitos. La instrucci´ i nstrucci´ on on CWD (Convert (Convert Word to Double Word) Word) extiende AX en DX:AX. La notaci´on on DX:AX implica interpretar 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´ ten´ıa ning´ un registro de 32 bits). El 80386 a˜ nadi´ nadi´ o varias instrucciones nuevas. La instrucci´on on CWDE (Convert Word to Double word Extended) Extended) extiende AX en EAX. La instrucci´on on CDQ (Convert Double word to Quad word) word) extiende EAX en EDX:EAX (¡64 bits!). Finalmente, la instrucci´ on on MOVSX trabaja como MOVZX excepto que usa las reglas para n´ umeros umeros con signo.
´ CAP ´ITULO 2. LENGUAJE LENGUAJE ENSAMBL ENSAMBLADOR ADOR BASICO
34
unsigned unsigned char uchar = 0xFF; schar = 0xFF; 0xFF; signed char schar 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: Aplicaci´ on on a la programaci´ on o n en C ANSI C no define si el tipo char es con signo o no, es resp respons onsabi abilid lidad ad de cada compilador decidir esto. Esta es la raz´ on por la cual el tipo est´ a expl´ıcitame ıcit amente nte definido en la Figura 2.1. 2.1.
Extender enteros con y sin signo tambi´en en ocurre o curre en C. Las variables variables en C se pueden declarar como int signed o unsigned (int es signed). Considere el c´ odigo odigo de la Figura 2.1. En la l´ınea 3, la variable a se extiende usando las reglas para valores sin signo (usando MOVZX), pero en la l´ınea 4 se usan las l as reglas con signo para b (usando MOVSX). Hay un error muy com´ un un en la programaci´on o n en C que tiene que ver con esto directamen directamente. te. Considere el c´ odigo odigo de la Figura 2.2. El prototipo de fgetc() es: int int fget fgetc( c( FILE FILE * );
Uno podr po dr´´ıa pregunta preg untarr ¿Por ¿ Por qu´e la l a funci´ func i´on on retorna un int siendo que lee caracteres? La raz´on on es que normalmente retorna un char (extendido a un valor entero usando la extensi´on on cero). Sin embargo hay un valor que puede retornar que no es un car´acter, acter, EOF. Este es un macro que normalmente se define como 1. As´ı fgetc() o retorna un car´acter acter extendido extendido a entero entero (que es como 000000xx 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´a los bits de mayor peso para que el entero quepa en el caracter. El ´unico unico problema es que los n´ umeros umeros (en hex) 000000FF y FFFFFFFF ambos se truncar´an a n al byte FF. As´ As´ı el ciclo while no puede distinguir distinguir entre entre el byte FF y el fin de archivo (EOF). Lo que sucede exactamente en este caso, depende de si el char es con signo o sin signo ¿por qu´e? e? Porque en la l´ınea 2 ch es comparada con EOF. Ya que EOF es un valor int1 , ch ser´ a extendido a un int de modo que los
−
1
Es un concepto err´oneo oneo pensar que los archivos tienen un car´ acter EOF al final. ¡Esto acter
2.1. TRABAJAN TRABAJANDO DO CON ENTEROS ENTEROS
35
dos valores comparados sean del mismo tama˜ no no2 . como se muestra en la Figura 2.1 2.1,, donde si la variable es con signo o sin signo es muy importante. Si char es unsigned, unsigned, FF se extender´a a 000000FF. Esto es comparo con a que no es igual. As´ As´ı, ¡el bucle nunca terminar´ a! a! EOF (FFFFFFFF) y encontrar´ Si char es signed se extender´a a FFFFFFFF. Esto se compara como igual y el bucle finaliza. Sin embargo, ya que el byte FF puede haber sido le´ le´ıdo de un archivo, archivo, el bucle podr´ podr´ıa terminar prematuramente. La soluci´ on a este problema es definir la variable ch como un int no on como un char. Cuando esto se hace no se truncar´ a o extender´a en la l´ınea ıne a 2. Dentro del bucle es seguro truncar el valor ya que ah´ ah´ı ch debe ser un simple byte.
2.1.3.
Aritm´ etica etica de complemento complemento a dos
Como se vio al principio, la instrucci´ on on add efect´ ua ua una suma y la instrucci´ on on sub efect´ ua una resta. Dos de los bits en el registro FLAGS, que ua se alteran con estas instrucciones son las banderas de desborde y carry . La bandera de desborde se fija si el resultado verdadero de la operaci´on on es muy grande para caber cab er en el destino para aritm´etica etica con signo. La bandera de carry se fija si hay carry en el bit m´as as significat si gnificativo ivo de una u na suma sum a o un pr´estamo estamo en el bit m´as as significativo de una resta. As´ As´ı, ´el el se puede usar para detectar un desborde para aritm´etica etica sin signo. Los usos de la bandera de carry para aritm´etica etica con signo se ver´ an dentro de poco. Una de las grandes ventajas an del complemento a 2 es que las reglas para la adici´on on y sustracci´on on son exactamente las mismas que para los lo s enteros sin signo. As´ As´ı add y sub se pueden usar con enteros con o sin signo. 002C + FF FFFF 002B
44 + ( 1) 43
−
Hay un carry generado, pero no es parte de la respuesta. Hay dos instrucciones diferentes para multiplicar y dividir. Primero para multiplicar use la instrucci´on on MUL o IMUL. La instrucci´ instruccion o´n MUL se emplea para multiplicar n´ umeros sin signo e IMUL se usa para multiplicar enteros con umeros signo. ¿Por qu´e se necesitan dos instrucciones diferentes? Las reglas para la multiplicaci´ on son diferentes para n´umeros on umeros en complemento a dos con signo o sin signo. ¿C´ omo as´ omo as´ı? Considere Conside re la l a multiplic mult iplicaci´ aci´ on on del d el byte by te FF F F con co n s´ı m mism ismoo dando como resultado una palabra. Usando una multiplicaci´ on on sin signo es 255 veces 255 o 65025 (o FE01 en hex). Usando la multiplicaci´on on con signo es 1 veces 1 (o 0001 en hex).
−
−
no es verdad! 2
La raz´ on para este requerimiento se mostrar´a luego. on
´ CAP ´ITULO 2. LENGUAJE LENGUAJE ENSAMBL ENSAMBLADOR ADOR BASICO
36 dest
reg16 reg32 reg16 reg32 reg16 reg32 reg1 reg166 reg3 reg322 reg1 reg166 reg3 reg322
fuente1 reg/mem8 reg/mem16 reg/mem32 reg/mem16 reg/mem32 immed8 immed8 immed16 immed32 reg/m reg/mem em16 16 reg/m reg/mem em32 32 reg/m reg/mem em16 16 reg/m reg/mem em32 32
fuente2
imme immed8 d8 imme immed8 d8 imme immed1 d166 imme immed3 d322
Acci´ on on AX = AL*fuente1 DX:AX = AX*fuente1 EDX:EAX = EAX*fuente1 dest *= fuente1 dest *= fuente1 dest *= immed8 dest *= immed8 dest *= immed16 dest *= immed32 dest dest = fuen fuente te1* 1*fu fuen ente te22 dest dest = fuen fuente te1* 1*fu fuen ente te22 dest dest = fuen fuente te1* 1*fu fuen ente te22 dest dest = fuen fuente te1* 1*fu fuen ente te22
Cuadro 2.2: instrucciones imul Hay varias formas de las instrucciones de multiplicaci´on. on. La m´ as as antigua es: mul
fuent ente
fuente es un registro o una referencia a memoria. No puede ser un valor inmediato. inmedi ato. Exactamente Exacta mente qu´ q u´e multiplic mult iplicaci´ aci´ on se realiza depende del tama˜ on no no del operando operando fuente. fuente. Si el operando operando es de un byte, byte, ´este este es multiplicad multiplicadoo por 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 almacena en DX:AX. DX:AX. Si la fuente fuente es de 32 bits ´este este se multiplica multiplica por EAX y el resultado de 64 bits se almacena en EDX:EAX. La instrucci´on on IMUL tiene la misma forma de MUL, pero tambi´en en tiene algunos otros formatos. Hay dos y tres formas de operandos. imul imul imul imul
dest dest, , fuen fuente te1 1 dest dest, , fue fuent nte1 e1, , fue fuent nte2 e2
El Cuadro 2.2 muestra las posibles combinaciones. Los dos operadores para la divisi´on on son DIV e IDIV. Ellas efect´ uan uan la divisi´on on sin signo y con signo respectivamente. El formato general es: div
fuente
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
2.1. TRABAJAN TRABAJANDO DO CON ENTEROS ENTEROS
37
operando y el cociente se almacena en EAX y el residuo en EDX. La instrucci´ on on IDIV trabaja de la misma manera. No hay instrucciones especiales 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 com´ un es olvidar iniciar DX o EDX antes de la divisi´on. un on. La instrucci´on on NEG niega su operando calculando su complemento a dos. El operando puede ser cualquier registro de 8, 16 o 32 bits o un lugar de memoria.
2.1.4. 2.1.4. 1 2 3 4 5 6 7 8 9
Progra Programa ma de ejempl ejemplo o
%include "asm_io.inc" "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
math.asm ; Cadenas de salida "Digite un numer u ´mero: o: ", 0 "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 negacion o ´n del residuo es ", 0
10 11 12
segment segment .bss .bss input resd 1
13 14 15 16 17 18
segment .text gl globa obal l _asm_main: enter pusha
_asm_ _asm_mai main n 0,0
; rutina de inicio
19 20 21
mov call call
eax, prompt prin print_ t_st stri ring ng
call mov
read_int [input], eax
imul mov mov call call mov call
eax ebx, eax eax, square_msg prin print_ t_st stri ring ng eax, ebx print_int
22 23 24 25 26 27 28 29 30 31
; edx:eax = eax * eax ; guarda la respuesta en
ebx
´ CAP ´ITULO 2. LENGUAJE LENGUAJE ENSAMBL ENSAMBLADOR ADOR BASICO
38 32
call
print_nl
mov imul mov call call mov call call
ebx, eax ebx, [input] eax, cube_msg prin print_ t_st stri ring ng eax, ebx print_int print_nl
imul mov call call mov call call
ecx, ebx, 25 eax, cube25_msg prin print_ t_st stri ring ng eax, ecx print_int print_nl
mov cdq mov idiv mov mov call call mov call call mov call call mov call call
eax, ebx
neg mov call call mov call call
edx eax, neg_msg prin print_ t_st stri ring ng eax, edx print_int print_nl
; niega el residuo
popa mov
eax, 0
; retorna a
33 34 35 36 37 38 39 40
; ebx *= [input]
41 42 43 44 45 46 47
; ecx = ebx*25
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
ecx, 100 ecx ecx, eax eax, quot_msg prin print_ t_st stri ring ng eax, ecx print_int print_nl eax, rem_msg prin print_ t_st stri ring ng eax, edx print_int print_nl
; ; ; ;
inicia edx con la extension o ´n de sign signo o no puede dividirse por el valor inmediato edx:eax / ecx guarda el cociente ecx
64 65 66 67 68 69 70 71 72 73
C
2.2. ESTRUCTUR ESTRUCTURAS AS DE CONTROL CONTROL 74 75
leave ret
2.1.5.
39
math.asm
Aritm´ etica etica de precisi´ precisi´ on on extendida
El lenguaje ensamblador tambi´en en suministra instrucciones que le permitan a uno hacer operaciones de suma y resta de n´umeros umeros m´ as as grandes que palabras dobles. Como se vio antes las instrucciones ADD y SUB modifican la bandera de carry si se ha generado un carry o un pr´estamo estamo respectivamente. Esta informaci´ on almacenada en la bandera de carry se puede usar para on sumar o restar n´ umeros grandes dividiendo la operaci´ umeros on on en piezas peque˜ nas nas de palabras dobles (o menores). Las instrucciones ADC y SBB usan esta informaci´on on en la bandera de carry. La instrucci´on on ADC hace la siguiente operaci´ on: on: operando1 = operando1 + band bander era a de carr carry y + operando2
La instrucci´on on SBB realiza: operando1 = operando1 - band bander era a de flag flag - operando2
¿C´ omo se usan? Considere la suma de enteros de 64 bits en EDX:EAX y omo EBX:ECX . El siguiente c´odigo odigo pod´ıa ıa almacenar la suma en EDX:EAX 1 2
add adc
eax, ecx edx, ebx
3
; suma los 32-bits inferiores ; suma los 32-bits superiores y el carry ; de la suma suma ante anteri rior or
La resta es muy similar. El siguiente c´odigo odigo resta EBX:ECX de EDX:EAX 1 2
sub sbb
eax, ecx edx, ebx
; resta los 32-bits inferiores ; resta los 32-bits superiores y el prestamo e´stamo
Para n´ umeros umeros realmente grandes, se puede usar un bucle (vea secci´on on 2.2). 2.2 ). Para Para el bucle suma ser´ ser´ıa conven convenien iente te usar la instrucci´ instrucci´ on on ADC para cada iteraci´ iteraci´ on (en todas menos la primera). Esto se puede hacer usando la on instrucci´ on on CLC (CLear Carry ) antes que comience el bucle para iniciar la 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 tambi´en en para la resta.
2.2. 2.2.
Estr Estruc uctu tura rass de de con contr trol ol
Los lenguajes de alto nivel suministran estructuras del alto nivel (vgr instrucciones if while while), ), que controlan el flujo de ejecuci´on. on. El lenguaje ensamblador no suministra estas complejas estructuras de control. En lugar
´ CAP ´ITULO 2. LENGUAJE LENGUAJE ENSAMBL ENSAMBLADOR ADOR BASICO
40
de ello usa el difamado goto y su uso inapropiado puede resultar en un ¡c´ odigo spaghetti! Sin embargo es posible escribir programas estructurados odigo en ensamblador. El procedimiento b´ asico asico es dise˜ nar n ar la l´ ogica ogica del programa usando las estructuras de control de alto nivel y traducir el dise˜ no n o en lenguaje ensamblador apropiado (parecido a lo que har´ har´ıa el compilador).
2.2.1. 2.2.1.
Compar Comparaci acione oness
Las estructuras de control deciden que hacer basados en la comparaci´on on de datos. En ensamblador, el resultado de una comparaci´ on on se almacenan en el registro FLAGS para usarlas luego. El 80x86 suministra la instrucci´on on CMP para realizar comparaciones. El registro FLAGS se fija basado en la diferencia de los dos operandos de la instrucci´ on on CMP. Los operandos se restan y se fija el registro FLAGS basado en el resultado, pero el resultado no se almacena en ninguna parte. Si necesita el resultado use la instrucci´on on SUB en lugar de la instrucci´on on CMP. Para enteros sin signos hay dos banderas (bits en el registro FLAGS) que son importante: cero (ZF) y carry (CF). La bandera cero se fija (1) si el resultado de la resta es cero. La bandera carry se usa como bandera de pr´estamo estamo para la resta. Considere una comparaci´ on on como: cmp
¿Por qu´ e hace hace SF = OF vleft > vright vright? Si no si vleft hay desbor desborde, de, entonces entonces la diferencia diferencia tendr´ a el valo valor r correcto y debe ser no negativo. As´ı, ı, SF = OF O F = 0. Sin Sin emb embargo argo,, si hay hay un desbor desborde, de, la difere diferencia ncia no tendr´ a el valor correcto (y de hech hecho o ser´ ser´ a negativ negativo). o). As´ As´ı SF = OF = 1.
vleft, vright
Se calcula la diferencia de vleft vleft - vrigh vright t y las banderas se fijan de acuerdo al resultado. Si la diferencia de CMP es cero, vlef vleft t = vrigh vright t, entonces ZF se fija (ZF=1) y CF se borra (CF=0). Si vleft vleft > vrigh vright t, entonces ZF se borra borr a y CF tambi´en en (no hay pr´estamo). estamo ). Si vleft vleft < vrigh vright t, entonces ZF se borrar´a y CF se fijar´a (hay pr´estamo est amo). ). 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 fija si el resultado de una operaci´on on se desborda (o underflows). La bandera de signo se fija si el resultado de una operaci´on on es negativo. Si vl vleft eft = vr vrig ight ht, la ZF se fija (tal como para los enteros sin signo). Si vl vlef eft t > vrigh vright t, ZF es cero y SF = OF. Si vleft vleft < vrig vright ht, ZF es cero y SF = OF. No olvide que otras instrucciones tambi´en en cambian el registro FLAGS, no solo CMP.
2.2.2. 2.2.2.
Instrucc Instruccione ioness de ramifica ramificaci´ ci´ on on
Las instrucciones de ramificaci´ on pueden transferir la ejecuci´on on on del programa a un punto arbitrario. En otras palabras funcionan como goto. goto. Hay dos tipos de ramificaciones: condicionales e incondicionales. Una ramificaci´on on incondicional es tal cual como goto, siempre hace el salto. Una ramificaci´on on
2.2. ESTRUCTUR ESTRUCTURAS AS DE CONTROL CONTROL
41
condicional puede o no hacer el salto dependiendo de las banderas del registro FLAGS. Si una ramificaci´on on condicional no hace el salto, el control pasa a la siguiente instrucci´on. on. La instrucci´on on JMP (acr´ onimo onimo de jump) jump ) hace ramificaciones incondicionales. Su argumento normalmente es la etiqueta de c´ odigo de la instrucci´on on a la cual debe saltar. El ensamblador o el encadenador reemplazar´a la etiqueta etiqueta con la direcci´ on correcta de la instrucci´on. on on. Esta es otra de las labores aburridas que realiza el ensamblador para hacer la vida del programador m´as as f´acil. acil. Es importante tener en cuenta que la instrucci´on on inmediatamente inmedia tamente despu´es es de la instrucci´on on JMP nunca se ejecutar´a a menos que otra instrucci´on on salte a ella. Hay variaciones de la instrucci´on on de salto. SHORT Este salto es de tama˜ no muy limitado, solo se puede mover arrino ba 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 cu´antos antos bytes se mueve adelante o atr´ as. as. (El desplazamiento se a˜ nade a EIP). Para especifinade car un salto corto, use la palabra SHORT inmediatamente antes de la etiqueta en la instrucci´on on JMP. NEAR Este salto es el tipo por omisi´on on en las ramificaciones 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 c´ o digo. El tipo de 4 bytes es el odigo. de defecto en el modo protegido del 386. El tipo de 2 bytes se puede especificar colocando la palabra WORD antes de la etiqueta en la instrucci´ on on JMP. FAR Este salto permite moverse a otro segmento de c´ odigo. odigo. Este es una cosa muy rara para hacerla en el modo protegido del 386. Las etiquetas de c´odigo odigo v´ alidas siguen las mismas reglas que las etiquealidas tas de datos. Las etiquetas de c´odigo odigo est´ an definidas para colocarlas en el an segmento de c´ odigo al frente de la instrucci´on odigo on sus etiquetas. Dos puntos se colocan al final de la etiqueta en este punto de la definici´ on. on. Los dos puntos no son parte del nombre. Hay muchas instrucciones de ramificaci´ on condicional diferentes. Ellas on tambi´ en en toman to man una etiqueta como su operando. op erando. Las m´ as as sencillas solo ven una bandera en el registro FLAGS para determinar si salta o no. Vea el Cuadro 2.3 para una lista de estas instrucciones (PF es la bandera de paridad
´ CAP ´ITULO 2. LENGUAJE LENGUAJE ENSAMBL ENSAMBLADOR ADOR BASICO
42
JZ JNZ JNZ JO JNO JNO JS JNS JNS JC JNC JNC JP JNP JNP
salt saltaa solo olo si ZF es uno uno salta salta solo solo si ZF es cero cero salt saltaa solo olo si OF es uno salta salta solo solo si si OF OF es es cero cero salt saltaa solo olo si SF es uno uno salta salta solo solo si SF es cero cero salt saltaa solo olo si CF es uno uno salta salta solo solo si CF es cero cero salt saltaa solo olo si PF es uno uno salta salta solo solo si PF es cero cero
Cuadro 2.3: Saltos condicionales simples que indica si el n´ umero de unos en los 8 bit inferiores del resultado de la umero operaci´on on es par o impar). El siguiente siguiente pseudoc´ odigo: odigo: if ( EAX == 0 ) EBX = 1; else EBX = 2;
Se podr´ podr´ıa escribir en ensamblador como: 1
cmp
eax, 0 thenblock ebx, 2 next
; establece las banderas ; ( ZF ZF s e fi fija s i eax - 0 = 0) ; si ZF es 1 salta a thenblock ; parte ELSE del if ; salta sobre la parte THEN del IF
ebx, 1
; parte THEN del IF
2 3 4 5 6 7 8
jz mov jmp thenblock: mov next:
Las otras comparaciones no son f´ aciles usando las ramificaciones conaciles dicionales de el Cuadro 2.3 Para ilustrar esto considere el siguiente pseudoc´ odigo: odigo: if ( EAX >= 5 ) EBX = 1; else EBX = 2;
Si EAX es mayor que o igual a 5, ZF debe estar fija o borrada y SF ser´a igual a OF. A continuaci´on on est´ a el c´odigo odigo en ensamblador que prueba estas condiciones (asumiendo que EAX es con signo):
2.2. ESTRUCTUR ESTRUCTURAS AS DE CONTROL CONTROL
JE JNE JL, JL, JNGE JNGE JLE, JLE, JNG JNG JG, JG, JNLE JNLE JGE, JGE, JNL JNL
Signed salta si vl vlef eft t salta si vleft salta salta si vleft salt saltaa si vleft salta salta si vleft salt saltaa si vleft
= vrigh vright t = vright
vright vright
≤ ≥
43
JE JNE JB, JB, JNAE JNAE JBE, JBE, JNA JNA JA, JA, JNB JNBE E JAE, JAE, JNB JNB
Unsigned salta si vleft vleft salta si vleft salt saltaa si vleft salt saltaa si vleft salt saltaa si vleft salt saltaa si vleft
= vrigh vright t = vright vright vright
≤ ≥
Cuadro Cuadro 2.4: Instruccione Instruccioness de comparaci´ comparaci´ on on con y sin signo 1 2 3 4 5 6 7 8 9 10 11 12
cmp js jo jmp signon: jo elseblock: mov jmp thenblock: mov next:
eax, 5 signon elseblock thenblock
; salta a signon si SF = 1 ; salta a elseblock si OF = 1 y SF = 0 ; salta a thenblock si SF = 0 y OF = 0
thenblock
; salta a thenblock si SF = 1 y OF = 1
ebx, 2 next ebx, 1
El c´ odigo anterior es muy complicado. Afortunadamente, el 80x86 suodigo ministra otras instrucciones de ramificaci´ on que hace este tipo de pruebas on mucho m´ as as f´acil. acil. Hay versiones con y sin signo para cada tipo. El Cuadro 2.4 muestra estas instrucciones. Las ramificaciones igual y no igual (JE y JNE) son id´enticas enticas para enteros con y sin signo. s igno. (De hecho h echo JE y JNE son id´enticas enticas a JZ y JNZ respectivamente). Cada una de las otras instrucciones de ramificaci´ on on tienen dos sin´onimos. onimos. Por ejemplo observe que JL (Jump ( Jump Less than) than) y JNGE (Jump (Jump Not Greater than or Equal to). to ). Son la misma instrucci´ on on porque: not(x y) x < y = not(
⇒
≥
Las ramificaciones sin signo usan A por above y B por below en lugar de L (Less ) y G(Greater G(Greater ). ). Usando estas nuevas instrucciones de ramificaci´on, on, el pseudoc´odigo odigo de arriba puede ser traducido a ensamblador mucho m´ as as f´acil. acil. 1 2 3 4
cmp jge mov jmp
eax, 5 thenblock ebx, 2 next
´ CAP ´ITULO 2. LENGUAJE LENGUAJE ENSAMBL ENSAMBLADOR ADOR BASICO
44 5 6 7
thenblock: mov next:
2.2.3. 2.2.3.
ebx, 1
Instru Instrucc ccion iones es de de buc bucle le
El 80x86 suministra varias instrucciones para implementar bucles del tipo for . Cada Cada una de esas esas instru instrucci ccione oness tiene tiene una etiqueta etiqueta como unico u´nico operando. LOOP Decrementa ECX, si ECX = 0, salta a la etiqueta
LOOPE, LOOPZ Decrementa ECX (el registro FLAGS no se modifica), si ECX = 0 y ZF = 1, salta
LOOPNE, LOOPNZ Decrementa ECX (FLAGS sin cambio), si ECX = 0 y ZF = 0, salta
Las dos ultimas u ´ ltimas instrucciones de bucle son utiles ´utiles para bucles de b´ usqueda usqueda secuencial. secuencial. El siguiente siguiente pseudoc´ odigo: odigo: sum = 0; for ( i=10 i=10; i >0; i sum += i;
−− )
Podr´ıa ıa ser se r traducido tra ducido a ensamblador ens amblador como: 1 2 3 4 5
mov mov loop_start: add loop loop
2.3.
eax, 0 ecx, 10
; eax es sum ; ecx es i
eax, ecx loop loop_s _sta tart rt
Traducir estructuras de control control est´ andares andares
Esta secci´ on on muestra c´omo omo las estructuras de control est´andares andares de los lenguajes de alto nivel se pueden implementar en lenguaje ensamblador.
2.3.1. 2.3.1.
instr instruc ucci cione oness if
El siguiente siguiente pseudoc´ odigo: odigo: condici´ i´ on on ) if ( condic bloque entonces; else
bloque else ;
´ 2.3. TRADUCIR TRADUCIR ESTRUCTUR ESTRUCTURAS AS DE CONTROL CONTROL EST ANDARES
45
podr po dr´´ıa implementarse implem entarse como: ; code code to set FLAG FLAGS S jxx jxx else_b e_block
1 2 3 4 5 6 7 8
; selec leccion iona xx tal que salta si la ; condici condici´ on o ´ n es fals falsa a ; c´ odigo odigo para bloque bloque entonce entonces s jmp endif else_block: ; c´ o odigo digo para para bloqu bloque e el else se endif:
Si no hay else, entonces el salto al else block puede ser reemplazado por un salto a endif. 1 2 3 4
; C´ odigo odigo para establec establecer er FLAGS FLAGS jxx endif ; selecciona xx tal que salta si la condicion o´n es fals falsa a ; c´ o odigo digo para para el bl bloqu oque e enton entonces ces endif:
2.3. 2.3.2. 2.
bucl bu cles es whil while e
El bucle while se prueba al inicio del bucle: while( condici´ condici´ on on )
cuerpo cuerpo del bucle bucle ;
{
} Esto podr po dr´´ıa traducirse traduci rse en: 1 2 3 4 5 6
while: ; c´ o odigo digo que fi fija ja FL FLAGS AGS basad basado o en la condi condici ci´ on o´n jxx endwhile ; selecciona xx tal que salte si es falso ; Cu Cuerp erpo o del bucle bucle jmp while endwhile:
2.3. 2.3.3. 3.
bucl bu cles es do whil while e
El bucle do while se prueba al final del bucle: do
}
{
cuerpo cuerpo del bucle bucle ; while( condici´ condici´ on );
Esto podr po dr´´ıa traducirse traduci rse en:
´ CAP ´ITULO 2. LENGUAJE LENGUAJE ENSAMBL ENSAMBLADOR ADOR BASICO
46
unsigned guess; unsigned factor; unsigned limit;
/ La conje conjetur turaa actual actual para para el primo primo / el posib posible le fact factoor / / encontra encontrarr primos primos hasta hasta este valor valor /
∗ ∗ ∗
∗
∗/
∗
print printff (”Fin (”Find d primes primes up to: ”); scanf scanf (” %u”, &limit); &limit); printf (”2 n”); / trata trata los dos prime primeros ros primos primos / printf (”3 n”); / como caso especial / guess = 5; / conjet conjetura ura inicia inicial l / while ( guess <= limit ) / busca busca un factor factor / facto factorr = 3; while ( facto factorr factor < guess && guess guess % facto factorr != 0 ) facto factorr += 22;; if ( gues guesss % fact facto or != 0 ) print printff (” %d n”, guess); guess += 2; / s´ olo olo busc buscaa en en los los n´ umeros impares /
\ \
∗ ∗ ∗
∗
∗
∗
{
∗ ∗
∗
\
}
∗
∗
Figura 2.3: 1 2 3 4
do: ; cuerp cuerpo o de del l bucle bucle ; c´ odigo o digo pa para ra fi fija jar r FL FLAGS AGS basad basado o en la condi condici ci´ on o´n jxx do ; seleccionar xx tal que salte si es verdadero
2.4. 2.4.
Ejem Ejempl plo: o: hall hallar ar n´ umeros umeros primos
Esta secci´on on muestra un programa que encuentra n´ umeros umeros primos. Recuerde que un n´umero umero primo es divisible s´ olo olo por 1 y por s´ı mismo. No hay f´ormula ormula para hacer esto. El m´etodo eto do b´asico asico de este programa programa es encontrar encontrar los 3 factores de todos los n´ umeros umeros impares bajo un l´ımite ımite dado. Si no se puede encontrar un factor para un n´umero umero impar, es primo La Figura 2.3 muestra el algoritmo b´ asico asico escrito en C. A continuaci´ on on la versi´on on en ensamblador 1 2
%include %include "asm_io.inc" "asm_io.inc" segment .data 3
2 es el unico u ´ nico n´ umero umero par.
prime.asm
´ 2.4. EJEMPLO: EJEMPLO: HALLA HALLAR R N UMEROS PRIMOS 3
Message
db
"Halle primos hasta: ", 0
segment segment .bss .bss Limit Guess
resd resd
1 1
47
4 5 6 7
; halle primos hasta este lımite ı´mite ; la conjetura actual para el primo
8 9 10 11 12 13
segment .text gl globa obal l _asm_main: enter pusha
_asm_ _asm_mai main n 0,0
; rutina de inicio
14 15 16 17 18
mov call call call mov
eax, Message prin print_ t_st stri ring ng read_int [Limit], eax
mov call call mov call call
eax, 2 print_int print_nl eax, 3 print_int print_nl
; printf("2\n");
mov while_limit: mov cmp jnbe
dword [Guess], 5
; Guess = 5; ; while ( Guess <= Limit )
eax,[Guess] eax, [Limit] end_while_limit
; se usa jnbe ya que los numero u´meros s son son sin signo signo
mov while_factor: mov mul jo cmp jnb mov mov div cmp je
ebx, 3
; ebx es factor = 3;
; scanf("%u", & limit );
19 20 21 22 23 24 25
; printf("3\n");
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
eax,ebx eax end_while_factor eax, [Guess] end end_whil hile_fac factor eax,[Guess] edx,0 ebx edx, 0 end_while_factor
; edx:eax = eax*eax ; Si la respuesta no cabe en eax ; if !(fact actor*fa *factor < gue guess)
; edx = edx:eax % ebx ; if !(guess % factor != 0)
´ CAP ´ITULO 2. LENGUAJE LENGUAJE ENSAMBL ENSAMBLADOR ADOR BASICO
48 45 46 47 48 49 50 51 52 53 54 55 56
add ebx,2 jmp while_factor end_while_factor: je end_if mov eax,[Guess] call print_int call print_nl end_if: add dword [Guess], 2 jmp while_limit end_while_limit:
; factor += 2;
; if !(guess % factor != 0) ; printf("%u\n")
; guess += 2
57 58 59 60 61
popa mov leave ret
eax, 0
; retorna a C prime.asm
Cap´ıtulo 3
Operaciones con bits 3.1. 3.1.
Operacio Operaciones nes de despla desplazam zamien ientos tos
El lenguaje ensamblador ensamblador le permite al programador programador manipular manipular bits individuales de los datos. Una operaci´on on com´ un un es llamada un desplazamiento. desplazamiento. Una operaci´on on de desplazamiento mueve la posici´ on o n de los bits de alg´ un un dato. Los desplazamientos pueden ser hacia la izquierda (hacia el bit m´ as as significativo) o hacia la derecha (el bit menos significativo).
3.1.1. 3.1.1.
Despl Desplaza azamie mien ntos l´ ogicos ogicos
Un desplazamiento l´ ogico ogico es el tipo m´ as as simple de desplazamie desplazamiento. nto. Desplaza de una manera muy directa. La Figura 3.1 muestra un ejemplo del desplazamiento de un byte. Original Desplazado a la izquierda Desplazado a la derecha
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: Desplazamie Desplazamientos ntos l´ ogicos ogicos Observe que los nuevos bits que entran son siempre cero. Se usan las instrucciones SHL y SHR para realizar los desplazamientos a la izquierda y derecha respectivamente. Estas instrucciones le permiten a uno desplazar cualquier n´ umero de posiciones. El n´ umero umero de posiciones puede ser o una umero constante o puede estar almacenado en el registro CL. El ultimo u ´ ltimo bit desplazado se almacena en la bandera de carry. A continuaci´ on, on, algunos ejemplos: 1 2
mov shl
ax, 0C123H ax, 1
shr
ax, 1
3 4
; desplaza un bit a la izquierda, ; ax = 8246H, CF = 1 ; desplaza un bit a la derecha,
49
CAP ´ITULO ITULO 3. OPERACI OPERACIONES ONES CON CON BITS
50 5 6
shr
ax, 1
mov shl
ax, 0C123H ax, 2
mov shr
cl, 3 ax, cl
7 8 9 10 11 12 13
3.1.2. 3.1.2.
; ax = 4123H, CF = 0 ; desplaza un bit a la derecha, ; ax = 2091H, CF = 1 ; desplaza dos bit a la izquierda, ;ax = 048CH, CF = 1 ; desplaza tres bit a la derecha, ; ax = 0091H, CF = 1
Uso de de los los despl desplaz azami amien entos tos
Los usos m´as as comunes de las operaciones de desplazamientos son las multiplicaciones y divisiones r´apidas. apidas. Recuerde que en el sistema decimal la multiplicaci´ on on y divisi´on on por una potencia de 10 es s´olo olo un desplazamiento de los d´ıgitos. Lo mismo se aplica para las potencias potencias de dos en binario. Por ejemplo para duplicar el n´ umero umero binario 10112 (o 11 en decimal), al desplazar una vez a la izquierda obtenemos 101102 (o 22). El cociente de una divisi´on on 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 m´ as as r´apidas apidas que las instrucciones correspondientes MUL y DIV . Los desplazamientos l´ogicos ogicos se pueden usar para multiplicar y dividir valores sin signo. Ellos no funcionan, en general, para valores con signo. Considere el valor de dos bytes FFFF ( 1). Si ´este este se desplaza d esplaza l´ ogicamente ogicamente a la derecha 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 aritm´ eticos eticos
Estos desplazamientos est´ an an dise˜ nados para permitir que n´ nados umeros umeros con signo se puedan multiplicar y dividir r´ apidamente por potencias de 2. Ellos apidamente aseguran que el bit de signo se trate correctamente. SAL (Shift aritmetic left ). left ). Esta instrucci´ on on es solo sin´onimo onimo para SHL. Se ensambla con el mismo c´odigo odigo de m´ aquina que SHL. Como el bit de aquina signo no se cambia por el desplazamiento, el resultado ser´a correcto. SAR SAR (Shift Arithmetic Right ). Right ). Esta es una instrucci´ on on nueva que no desplaza el bit de signo (el bit m´as as significativo) de su operando. Los otros bits 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
3.1. OPERACIONES OPERACIONES DE DE DESPLAZAMIENTOS DESPLAZAMIENTOS
51
bit de signo es 1, los nuevos nuevos bits son tambi´ tambi´en en 1). As´ As´ı, si un byte byte se desplaza con esta instrucci´on, on, s´ olo los 7 bits inferiores se desplazan. olo Como las otras instruccione instruccioness de desplazamiento desplazamiento,, el ultimo u´ltimo bit que sale se almacena en la bandera de carry. 1 2 3 4
mov sal sal sar
3.1.4. 3.1.4.
ax, ax, ax, ax,
0C123H 1 1 2
; ax = 8246H, CF = 1 ; ax = 048CH, CF = 1 ; ax = 0123H, CF = 0
Desplaza Desplazamien mientos tos de rotaci´ rotaci´ on on
Los desplazamientos de rotaci´ on trabajan como los desplazamientos l´ on ogiogicos excepto que los bits perdidos en un extremo extremo del dato se desplazan al otro lado. As´ As´ı, el dato es tratado tratado como si fuera fuera una estructura estructura circular. circular. Las dos rotaciones m´ as as simples son ROL y ROR , que hacen rotaciones a la izquierda y a la derecha respectivamente. Tal como los otros desplazamientos, estos desplazamientos dejan una copia del ultimo u ´ ltimo 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 rotaci´on on adicionales que desplazan los bits en 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
3.1.5. 3.1.5.
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
Aplic Aplicaci aci´ ´ on on simple
A continuaci´ on on est´ a un fragmento de c´odigo odigo que cuenta el n´ umero umero de bits que est´an an “encendidos” (1) en el registro EAX.
CAP ´ITULO ITULO 3. OPERACI OPERACIONES ONES CON CON BITS
52 X 0 0 1 1
X AND Y 0 0 0 1
Y 0 1 0 1
Cuadro 3.1: La operaci´ on on AND AND
1 1 1
0 1 0
1 0 0
0 0 0
1 1 1
0 0 0
1 0 0
0 1 0
Figura 3.2: ANDdo un byte
1 2 3 4 5 6 7 8
mov mov count_loop: shl jnc inc skip_inc: loop loop
bl, 0 ecx, 32
; bl contendra ´ el n´ umero umero de bits prendidos prendidos ; ecx es el contador del bucle
eax, 1 skip_inc bl
; desplaza los bits en la bandera de carry ; si CF == 0, va a skip_inc
coun count_ t_lo loop op
El c´ odigo anterior destruye el valor original de EAX (EAX es cero al final odigo del bucle). Si uno desea conservar conservar el valor de EAX, la l´ınea 4 deber´ deber´ıa ser reemplazada con rol eax,1 eax,1.
3.2. 3.2.
Operac Operacio ione ness boolean booleanas as en entre tre bits bits
Hay cuatro operadores booleanos b´ asicos asicos AND, AND, OR, OR, XOR y NOT . Una tabla de verdad muestra el resultado de cada operaci´on on por cada posible valor de los operandos.
3.2. 3.2.1. 1.
La opera operaci ci´ ´ on on
AND
El resultado del AND de dos bits es 1 solo si ambos bits son 1, si no el resultado es cero como muestra el Cuadro 3.1 Los procesadores tienen estas operaciones como instrucciones que act´uan uan independientemente en todos los bits del dato en paralelo. Por ejemplo, si el contenido de AL y BL se les opera con AND, AND, la operaci´ on on se aplica a cada uno de los 8 pares de bits correspondientes en los dos registros como muestra la Figura 3.2 3.2.. A continuaci´on on un c´ odigo odigo de ejemplo:
3.2. OPERACI OPERACIONES ONES BOOLEANA BOOLEANAS S ENTRE BITS X 0 0 1 1
Y 0 1 0 1
53
X OR Y 0 1 1 1
Cuadro 3.2: La operaci´ on on OR X 0 0 1 1
Y 0 1 0 1
X XOR Y 0 1 1 0
Cuadro 3.3: La operaci´ on on XOR 1 2
mov and
3.2. 3.2.2. 2.
ax, 0C123H ax, 82F6H
La opera operaci ci´ ´ on on
; ax = 8022H OR
El O inclusivo entre dos bits es 0 solo si ambos bits son 0, si no el resultado es 1 como se muestra en el Cuadro 3.2 . A continuaci´ on on un c´odigo odigo de ejemplo: 1 2
mov or
3.2. 3.2.3. 3.
ax, 0C123H ax, 0E831H
La opera operaci ci´ ´ on on
; ax = E933H XOR
El O exclusivo entre 2 bits es 0 si y solo si ambos bits son iguales, sino el resultado es 1 como muestra el Cuadro 3.3. Sigue un c´odigo odigo de ejemplo: 1 2
mov xor
3.2. 3.2.4. 4.
ax, 0C123H ax, 0E831H
La opera operaci ci´ ´ on on
; ax = 2912H NOT
La operaci´ on on NOT es unaria (act´ ua sobre un solo operando, no como ua las operaciones binarias como AND). AND ). El NOT de un bit es el valor opuesto del bit como se muestra en el Cuadro 3.4. Sigue un c´odigo odigo de ejemplo: 1 2
mov not
ax, 0C123H ax
; ax = 3EDCH
CAP ´ITULO ITULO 3. OPERACI OPERACIONES ONES CON CON BITS
54
X 0 1
NOT X 1 0
Cuadro 3.4: La operaci´ on on NOT Prende el bit i Apaga el bit i
Complementa el bit i
OR el n´ umero umero con 2i (es el n´ umero umero con uniu ´ nicamente el bit i -´ -´esimo esi mo prendid pren dido) o) AND el n´ umero umero binario con s´olo olo el bit i apagado . Este operando es a menudo llamado m´ ascara XOR el n´ umero umero con 2i
Cuadro 3.5: Usos de las operaciones booleanas Observe que NOT halla el complemento a 1. A diferencia de las otras operaciones entre bits, la instrucci´on on NOT no cambian ninguno de los bits en el registro registro FLAGS.
3.2.5. 3.2.5.
La instr instruc ucci´ ci´ on on
TEST
La instrucci´on on TEST realiza una operaci´on on AND, AND, pero no almacena el resultado. Solo fija las banderas del registro FLAGS dependiendo del resultado obtenido (muy parecido a lo que hace la instrucci´ on on CMP con la resta que solo fija las banderas). Por ejemplo, si el resultado fuera cero, ZF se fijar´ fij ar´ıa. ıa .
3.2.6. 3.2.6.
Usos Usos de las las opera operacio ciones nes con con bits bits
Las operaciones con bits son muy utiles u ´tiles para manipular bits individuales sin modificar los otros bits. El Cuadro 3.5 muestra los 3 usos m´as as comunes de estas operaciones. Sigue un ejemplo de c´omo omo implementar estas ideas. 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 operaci´ on on AND se puede usar tambi´en en para hallar el residuo de una divisi´on on por una potencia de dos. Para encontrar el residuo de una divisi´on on i i por 2 , efect´ ua un AND entre el dividendo y una m´ascara ua ascara igual a 2 1,AND 1,AND
−
3.3. EVITANDO EVITANDO SALTOS SALTOS CONDICIONALES CONDICIONALES
55
el n´ umero umero con una m´ ascara ascara igual a 2i 1. Esta m´ ascara ascara contendr´a unos desde el bit 0 hasta el bit i 1. Son solo estos bits los que contienen el residuo. El resultado de la AND conservar´a estos bits y dejar´a cero los otros. A continuaci´ on on un fragmento de c´odigo odigo que encuentra el cociente y el residuo de la divisi´on on de 100 por 16.
−
−
1 2 3
mov mov and
eax, 100 ebx, 0000000FH ebx, eax
; 100 = 64H ; macara a ´ cara = 16 - 1 = 15 or F ; ebx = residuo = 4
Usando el registro CL es posible modificar arbitrariamente bits. El siguiente es un ejemplo que fija (prende) un bit arbitrario en EAX. El n´ umero umero del bit a prender se almacena en BH. 1 2 3 4
mov mov shl or
cl, bh ebx, 1 ebx, cl eax, ebx
; ; se desplaza a la derecha cl veces ; prende el bit
Apagar un bit es solo un poco m´ as as dif di f´ıcil ıc il.. 1 2 3 4 5
mov mov shl not and
cl, bh ebx, 1 ebx, cl ebx eax, ebx
; ; se desplaza a la derecha cl veces ; invierte los bits ; apaga el bit
El c´ odigo para complementar un bit arbitrario es dejado como ejercicio al odigo lector. Es com´ un un ver esta instrucci´on on en un programa 80x86. xor
eax, eax
; eax = 0
Un n´ umero XOR con sigo mismo, el resultado es siempre cero. Esta instrucumero ci´ on on se usa porque su c´odigo odigo de m´ aquina aquina es m´ as as peque˜ no no que la instrucci´on on MOV equivalente. equivalente.
3.3. 3.3.
Evitan Evitando do saltos saltos condic condicion ionale aless
Los procesadores pro cesadores modernos mo dernos usan t´ecnicas ecnicas muy sofisticadas para ejecutar el c´ odigo odigo tan r´ apido apido como sea posible. Una t´ecnica ecnica com´ un un se conoce como ejecuci´ on especulativa . Esta t´ecnica ecnica usa las capacidades de procesamiento paralelo de la CPU para ejecutar m´ultiples ultiples instrucciones a la vez. Las instrucciones condicionales tienen un problema con esta idea. El procesador, en general, no sabe si se realiza o no la ramificaci´on.Si on.Si se efect´ ua, ua, se ejecutar´ a un conjunto de instrucciones diferentes que si no se efect´ua ua (el salto).
CAP ´ITULO ITULO 3. OPERACI OPERACIONES ONES CON CON BITS
56 1 2 3 4 5 6
mov mov count_loop: shl adc loop loop
bl, 0 ecx, 32
; bl contendra ´ el n´ umero umero de bits prendidos prendidos ; ecx es el bucle contador
eax, 1 bl, 0 coun count_ t_lo loop op
; se desplaza el bit en la bandera de carry ; anade n ~ ade solo solo la band bander era a de carr carry y a bl
Figura 3.3: Contando bits con ADC El procesador trata de predecir predecir si ocurrir´ ocurrira´ la ramificaci´ on on o no. Si la predicci´ on on fue err´onea onea el procesador ha perdido su tiempo ejecutando un c´odigo odigo err´oneo. oneo. Una manera de evitar este problema, es evitar usar ramificaciones condicionales cuando es posible. El c´ odigo odigo de ejemplo en 3.1.5 muestra un programa muy simple donde uno podr´ podr´ıa hacer esto. En el ejemplo anterior, los bits “encendidos” del registro EAX se cuentan. Usa una ramificaci´on on para saltarse la instrucci´on on INC. La figura 3.3 muestra c´ omo omo se puede quitar la ramificaci´ on on usando la instrucci´ on on ADC para sumar el bit de carry directamente. Las instrucciones SETxx SETxx suministran una manera de suprimir ramificaciones en ciertos casos. Esta instrucci´on on fija el valor de un registro o un lugar de memoria de 8 bits a cero, basado en el estudio del registro FLAGS. Los caracteres luego de SET son los mismos caracteres usados en los saltos condicionales. cionales. Si la condici´ on on correspondiente de SETxx SETxx es verdadero, el resultado 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 t´ecnicas ecnicas ingeniosas que calculan valores sin ramificacione ramificaciones. s. Por ejemplo, considere el problema de encontrar el mayor de dos valores. La aproximaci´ on on normal para resolver este problema ser´ ser´ıa el uso de CMP y usar un salto condicional y proceder con el valor que fue m´ as as grande. El programa de ejemplo de abajo muestra c´ omo se puede encontrar el mayor omo sin ninguna ninguna ramificaci´ ramificaci´ on. on.
1 2 3
; Archivo Archivo: : max.asm max.asm %include %include "asm_io.inc" "asm_io.inc" segment .data
4 5
me mess ssage age1 1 db "D "Digi igite te un n´ umero: umero: ",0
3.3. EVITANDO EVITANDO SALTOS SALTOS CONDICIONALES CONDICIONALES 6 7
57
messa message2 ge2 db "D "Dig igite ite otro otro n´ umer u mero: o: ", 0 messa message3 ge3 db "E "El l mayor mayor n´ umer u mero o es: es: ", 0
8 9
segment segment .bss .bss
10 11
input1
resd
1
; primer numero u ´mero ingresado ingresado
12 13 14 15 16 17
segment .text gl globa obal l _asm_main: enter pusha
_asm_ _asm_mai main n 0,0
;
mov call call call mov
eax, message1 prin print_ t_st stri ring ng read_int [input1], eax
; imprime el primer mensaje
mov call call call
eax, message2 prin print_ t_st stri ring ng read_int
; ingresa el segundo numer u´mero o (en (en
xor cmp setg neg mov and not and or
ebx, eax, bl ebx ecx, ecx, ebx ebx, ecx,
; ; ; ; ; ; ; ; ;
mov call call mov call call
eax, message3 prin print_ t_st stri ring ng eax, ecx print_int print_nl
; imprime los resultado
eax, 0
; retorna a C
18 19 20 21 22
; ingresa el primer numero u´mero
23 24 25 26
; imprime el segundo mensaje eax) eax)
27 28 29 30 31 32 33 34 35 36
ebx [input1]
ebx eax [input1] ebx
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)
37 38 39 40 41 42 43 44 45 46 47
popa mov leave ret
primer numero u´mero ? 1 : ? 0xFFFFFFFF : ? 0xFFFFFFFF : ? input2 : ? 0 : ? 0 : ? input2 :
0 0 0 0 0xFFFFFFFF input1 input1
CAP ´ITULO ITULO 3. OPERACI OPERACIONES ONES CON CON BITS
58
El truco es crear una m´ascara ascara de bits que se pueda usar para seleccionar el valor mayor. La instrucci´ on on SETG en la l´ınea 30 fija BL a 1. Si la segunda entrada es mayor o 0 en otro caso. Esta no es la m´ascara ascara deseada. Para crear la m´ ascara ascara de bits requerida la l´ınea 31 usa la instrucci´ on on NEG en el registro EBX. (Observe que se borr´o EBX primero). Si EBX es 0 no hace nada; sin embargo si EBX es 1, el resultado es la representaci´on on en complemento a dos de -1 o 0xFFFFFFFF. Esta es la m´ascara ascara que se necesita. El resto del c´odigo odigo usa esta m´ ascara para seleccionar la entrada correcta como e la mayor. ascara Un truco alternativo es usar la instrucci´on on DEC. En el c´odigo odigo de arriba, si NEG se reemplaza con un DEC, de nuevo el resultado ser´a 0 o 0xFFFFFFFF. Sin embargo, los valores son invertidos que cuando se usa la instrucci´on on NEG.
3.4. 3.4.
Mani Manipu pula land ndo o bit bitss en en C
3.4.1. 3.4.1.
Las opera operaco cones nes ent entre re bits bits de de C
A diferencia de algunos lenguajes de alto nivel C suministra operadores para operaciones entre bits. La operaci´on on AND se representa con el operador 1 on on OR se representa por el operador binario |. La operaci´ on on & . La operaci´ XOR se representa con el operador binario ^ . Y la operaci´ on on NOT se 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 n´ umero umero de bits a desplazar. Si el valor a desplazar es un tipo sin signo, se realiza un desplazamiento l´ ogico. Si el valor es con signo (como int), entonces se usa ogico. un desplazamiento aritm´etico etico a continuaci´ on, un ejemplo en C del uso de estos operadores: short short int s ; short unsigned u;
s = 1; u = 100; u = u 0x0100; s = s & 0xFFF0; s = s ˆ u; u; u = u << 3; s = s >> 2;
−
|
1
/ se asum asumee que que sho short int int es de 16 bits bits /
∗ ∗ / ∗ s = 0xFFFF (complemento a dos) ∗/ / ∗ u = 0x0064 ∗/ / ∗ u = 0x0164 ∗/ / ∗ s = 0xFFF0 ∗/ / ∗ s = 0xFE94 ∗/ / ∗ u = 0x0B20 (desplazamiento l´ ogico) ∗/ / ∗ s = 0xFFA5 0x FFA5 (desplazamiento (desplaz amiento aritm´etico) etico) ∗/
¡Este operador es diferente del operador binario && y del unario &!
3.4. MANI MANIPULA PULANDO NDO BITS BITS EN C Macro S S S S S S S S S
IRUSR IWUSR IXUSR IRGRP IWGRP IXGRP IROTH IWOTH IXOTH
59
Meaning el propietario puede leer el propietario propietario puede escribir escribir el propietario propietario puede ejecutar 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
Cuadro 3.6: Macros POSIX para permisos de archivos
3.4.2. 3.4.2.
Usand Usando o las operaci operacione oness entre entre bits bits en C
Los operadores entre bits se usan en C para los mismos prop´ositos ositos que en lenguaje ensamblador. Ellos le permiten a uno manipular bits individuales y se pueden usar para multiplicaciones y divisiones r´apidas. apidas. De hecho, un compilador de C inteligente usar´ a desplazamientos autom´ aticamente aticamente para 2 multiplicac multiplicaciones iones como X*=2, Muchos API (Como POSIX 3 y Win 32). tienen funciones que usan operandos que tienen datos codificados como bits. Por ejemplo, los sistemas POSIX mantienen los permisos de los archivos para 3 tipos diferentes de usuarios (un mejor nombre ser´ ser´ıa propietario), propietario), grupo y otros. 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 define varios macros para ayudar (vea el Cuadro 3.6 3.6). ). La funci´on on chmod se puede usar para establecer los permisos de un archivo. Esta funci´on on toma dos par´ ametros, ametros, una cadena con el nombre del archivo sobre el que se va a actuar y un entero 4 Con los bits apropiados para los permisos deseados. Por ejemplo, el c´odigo odigo de abajo fija 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 funci´on on POSIX stat se puede usar para encontrar los bits de permisos actuales de un archivo. Usada con la funci´on on chmod, es posible modificar algunos de los permisos sin cambiar los otros. A continuaci´on on un ejemplo que quita el acceso de la escritura a los otros y a˜nade nade el accesode lectura. 2
Aplication Programming Interface Significa Portable Operatting System Interface for Computer Enviroments. Una norma desarrollada por el IEEE basado en UNIX. 4 Actualmente un par´ ametro ametro de tipo mode t que es un typedef a un tipo integral. 3
CAP ´ITULO ITULO 3. OPERACI OPERACIONES ONES CON CON BITS
60
Los otros permisos no son alterados Los otros permisos no se alteran. stat struct stat
file file stat statss ; / estruc estructur turaa usada usada por por stat stat () / stat stat (”foo”, (”foo”, & file stat statss ); / lee la infor informac maci´ i´ on del archi rchivo vo . file stats stats . st mode mode holds holds permiss permission ion bits bits / chmod(”foo”, ( file stats .st mode & ˜S IWOTH) S IRUSR);
∗ ∗
∗
|
3.5. 3.5.
∗
Repres Represen entac tacion iones es Littel Littel Endian Endian y Big Big Endia Endian n
El Cap´ Cap´ıtulo 1 introdujo introdujo el concepto concepto de las represen representacione tacioness big y little endian de datos multibyte. Sin embargo, el autor ha encontrad que este tema confunde a muchas personas. Esta secci´on on cubre el t´opico opico con m´ as as detalle. El lector recordar´a que lo endian se refiere al orden en que los bytes individuales se almacenan en memoria (no (no bits) de un elemento multibyte se almacena almacena en memoria. memoria. Big endian endian es el m´ etodo etodo m´ as directo. Almacena el byte m´as as significativ significativoo primero, primero, luego el siguiente siguiente byte en peso y as´ as´ı sucesivamente. En otras palabras los bits de m´ as peso se almacenan almacenan primero. primero. Little endian almacena los bytes en el orden contrario (primero el menos significativo). La familia de procesadores X86 usa la representaci´on on little endian. Como un ejemplo, considere la palabra doble 1234567816. En la representaci´ on on big endian, endian, los bytes bytes se alma almacen cenar ar´´ıan como 12 34 56 78. En la representaci´ on on little endian los bytes se almacenar´ almacenar´ıan como 78 56 34 12. El lector probablemente se preguntar´ a as´ı mismo, mis mo, ¿por ¿p or qu´e cualqui cua lquier er dise˜nador nador sensato se nsato de circuitos circuit os integrados integr ados usar usa r´ıa la representaci re presentaci´ on o´n little endian? ¿Eran los ingenieros de Intel s´adicos adicos para infligir esta confusa representaci´on on a multitud de programadores? Parecer´ Parecer´ıa que la CPU no tiene que hacer trabajo extra para almacenar los bytes hacia atr´as as en la memoria (e invertirlos cuando los lee de la memoria). La respuesta es que la CPU no hace ning´un un trabajo extra para leer y escribir en la memoria usando el formato little endian. Uno sabe que la CPU est´a compuesta de muchos circuitos electr´ onicos onicos que simplemente trabajan con bits. Los bits (y bytes) no est´an an en un orden en particular en 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 est´an an en un orden particular en la CPU. Esto significa que los circuitos para AH no est´ an an antes a ntes o despu´es es que los circuitos circuit os para AL.Una instrucci´on on mov que y el valor de AX en memoria el valor de Al y luego AH Quiere decir que, no es dif´ dif´ıcil para la CPU hacer que almacene AH primero. El mismo argumento se aplica a los bits individuales dentro de un byte, no hay realmente ning´ un orden en los circuitos de la CPU (o la memoria). Sin un
3.5. REPRESENT REPRESENTACI ACIONES ONES LITTEL ENDIAN ENDIAN Y BIG ENDIAN
61
unsigned short word = 0x1234; / se asume sizeof(short) == 2 / unsigned char p = (unsigned char ) &word;
∗
∗
∗
∗
if ( p[0] == 0x12 0x12 )
printf (”M´aquina aquina Big Endian n”); else
\
printf (”M´aquina aquina Little Endian n”);
\
Figura 3.4: C´ omo determinar lo Endianness omo 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 parece que conservaran internamente en la CPU. El c´ odigo o digo en C en la Figura 3.4 muestra muestra c´ omo se puede omo puede determ determinar inar lo endian de una CPU. El apuntador p trata la variable word como dos elementos de un arreglo de caracteres. As´ As´ı, p[0] eval´ ua ua el primer byte de word en la memoria que depende de lo endian en la CPU.
3.5.1. 3.5.1.
Cuando Cuando tener tener cuida cuidado do con Little Little and and Big Endi Endian an
Para la programaci´ on on t´ıpica, ıpica, lo endian de la CPU no es importante. La mayor´ mayor´ıa de las la s veces esto es importante imp ortante cuando se transfiere datos binarios entre sistemas de c´omputo omputo diferente. Esto es ocurre normalmente usando un medio de datos f´ısico ısico (como un disco) o una red. Ya que el c´ odigo odigo ASCII es de 1 byte la caracter´ caracter´ıstica endian no le es importante. imp ortante. Todos los encabezados internos de TCP/IP almacena enteros en big Endian (llamado orden de byte de la red ). ). Y las bibliotecas de TCP/IP suministran funciones de C para tratar la cuesti´on on endian de una manera port´ atil. atil. Por ejemplo la funci´on on htonl() convierte una palabra doble ( o long int) del formato del host al de red red . La funci´on on ntohl() hace la transformaci´on on inver5 sa. sa. Para un sistema big endian, las dos funciones s´olo olo retornan su entrada sin cambio alguno. Esto le permite a uno escribir programas de red que compilar´ an an y se ejecutar´an an correctamente en cualquier sistema sin importar lo endian. Para m´as as informaci´ on sobre lo endian programaci´ on on on de redes vea el excelente libro de W. Richard Steven UNIX Network Programming . La Figura 3.5 muestra una funci´on on de C que invierte lo endian de una palabra doble. El 486 ha introducido una nueva instrucci´on o n de m´aquina aquina llamada BSWAP que invierte los bytes de cualquier registro de 32 bits. Por ejemplo: 5
Ahora, invertir invertir lo endian de un entero simplemente coloca al rev´ es es los bytes; as´ı convertir de big a little o de little a big es la misma operaci´ on. on. As A s´ı, ambas amba s funciones fun ciones hacen la misma cosa.
Ahora con los conjuntos de caracteres caracteres multibyte como UNICODE , lo endian es importan importante te a´ un para ara textexto. UNICODE soporta ambos tip tipos de repr epresen esenta ta-ci´ on y tien tienee un mec mecanis anis-mo para especificar cu´ al se est´ a usando para representar los datos.
CAP ´ITULO ITULO 3. OPERACI OPERACIONES ONES CON CON BITS
62
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];
return invert ;
}
∗
∗
/ invier invierte te los bytes bytes individ individual uales es /
∗
∗
/ retor retorna na los bytes bytes invert invertido idos s /
∗
∗
Figura 3.5: Funci´ on on invert endian bswap
edx
; intercambia los bytes de edx
Esta instrucci´on on no se puede usar en los registros de 16 bits, sin embargo la instrucci´ on on XCHG se puede usar para intercambiar los bytes en los registros de 16 bits que se pueden descomponer en registros de 8 bits. Por ejemplo: xchg
3.6. 3.6.
ah,al
; intercambia los bytes de ax
Con Contand tando o bits bits
Al principio se dio una t´ecnica ecnica directa para contar el n´ umero umero de bits que est´an an “encendidos” en una palabra doble. Esta secci´on on mira otros m´etodos etod os menos directos de hacer esto, como un ejercicio que usa las operaciones de bits discutidas discut idas en este e ste cap´ cap´ıtulo. ıtulo .
3.6.1.
M´ etodo etodo uno
El primer primer m´ etodo etodo es muy simple, pero no obvio. obvio. La figura 3.6 muestra el c´ odigo. odigo. ¿C´ omo trabaja este m´ omo etodo? etodo? En cada iteraci´ iteraci´ on o n el bucle, se apaga un bit de data dato. Cuando todos los bits se han apagado (cuando el dato es cero), el bucle finaliza. El n´ umero de iteraciones requerido para hacer el umero umero de bits en el valor original de data data. umero dato cero es igual al n´ La l´ınea ınea 6 es donde donde se apaga apaga un bit del dato. ¿C´ omo o mo se hace esto? Considere la forma general de la representaci´ on on en binario del dato y el 1 del extremo derecho de esta representaci´on. on. Por definici´on on cada bit despu´ desp u´es es
3.6. CONTANDO CONTANDO BITS
63
count bits ( unsigned int count unsigned int data )
{
int cnt = 0; while( data != 0 )
{
data = data & (data cnt++;
− 1);
}
return cnt;
} Figura 3.6: Contando bits: m´etodo etodo uno de este 1 debe ser cero. Ahora, ¿Cu´al al ser´ a la representaci´on o n de data data -1? Los bits a la izquierda del 1 del extremo derecho ser´an an los mismos que para an el complemento data, pero en el punto del 1 del extremo derecho ellos ser´an de los bits originales de data. Por ejemplo: = xxxx xxxxx1 x100 0000 00 data xxxxx0 x011 1111 11 data - 1 = xxxx donde X es igual para ambos n´ umeros. Cuando se hace data AND data umeros. a cero el 1 del extremo derecho en data y deja todos los -1, el resultado ser´ otros bits sin cambio.
3.6.2.
M´ etodo etodo dos dos
Una b´ usqueda en una tabla se puede usar para contar bits de una palabra usqueda doble arbitraria. La aproximaci aproximaci´ on o´n directa directa ser´ ser´ıa precalcular precalcular el n´ umero umero de bits para cada palabra doble y almacenar esto en un arreglo. Sin embargo, hay dos problemas relacionados con esta aproximaci´on. on. ¡Hay alrededor de 4 mil millones de palabras dobles! Esto significa que el arreglo ser´a muy grande grande e iniciarlo iniciarlo consumir consumir´´ıa mucho mucho tiempo. (de hecho, hecho, a menos que uno vaya a utilizar realmente el arreglo de m´as as que 4 mil millones de veces, se tomar´ a m´ as as tiempo tiemp o iniciando el arreglo que el que se requerir´ requerir´ıa para calcular la cantidad de bits usando el m´etodo etodo uno). Un m´eto et o do m´as as realista ser´ ser´ıa precalcular la cantidad de bits para todos to dos los valores posibles de un byte y almacenar esto en un arreglo. Entonces la palabra doble se puede dividir en 4 bytes. Se hallan los b y se suman para encontrar la cantidad de bits de la palabra doble original. La figura 3.7 muestra la implementaci´ on on de esta aproximaci´ on. on. La funci´on on initialize count bits debe ser llamada antes, del primer llama llamado do a la func funci´ i´ on on count bits . Esta funci´on on inicia inicia el arregl arregloo global global on on count bits mira la variable data no como una byte bit count. La funci´
CAP ´ITULO ITULO 3. OPERACI OPERACIONES ONES CON CON BITS
64
static static unsigned unsigned char char byte bit count [256];
∗
∗
initia initializ lizee count count bits bits ()
void
{
/ lookup table /
int cnt, i , data; for ( i = 0; i < 256; i++ )
cnt = 0; data = i; while( data != 0 ) data = data & (data cnt++;
{
{ / m´eto et odo uno un o /
− 1);
∗
∗
}
byte yte bit coun countt [ i ] = cnt; cnt;
}
}
int count count bits ( unsigned int data )
{
const unsigned char
∗ byte = ( unsigned char ∗) & data;
byte bit count count [ byte byte [0]] [0]] + byte byte bit count count [ byte byte [1]] [1]] + return byte byte byte bit count count [byte [byte [2]] [2]] + byte byte bit count count [ byte byte [3]]; [3]];
} Figura 3.7: M´etodo etod o dos palabra doble, sino como un arreglo de 4 bytes. El apuntador dword act´ ua ua como un apuntador a puntador a este arreglo de 4 bytes. As´ As´ı dword dword [0] es uno de los bytes en data ( el byte menos significativo o el m´ as significativo dependienas do si es little o big endian respectivamente). Claro est´ a uno podr po dr´´ıa usar una instrucci´ on on como: (data >> 24) & 0x000000FF Para encontrar el byte m´as as significativo y hacer algo parecido con los otros bytes; sin embargo, estas construcciones ser´an an m´ as lentas que una referencia as al arreglo. Un ultimo u ´ltimo punto, se podr´ıa ıa usar f´ acilmente acilmente un bucle for para calcular la suma en las l´ıneas ıneas 22 y 23. Pero el bucle for incluir inclui r´ıa el traba jo extra de iniciar el ´ındice del bucle, comparar el ´ındice luego de cada iteraci´ on on e incrementar el ´ındice. Calcular la suma como la suma expl´ expl´ıcita de 4 valores ser´ a m´ as a s r´apido. apido. De hecho hecho un compilador compilador inteligen inteligente te podr´ podr´ıa conver convertir tir la
3.6. CONTANDO CONTANDO BITS
65
count bits ( unsigned int count unsigned int x )
{
static static unsigned unsigned int mask[] =
{ 0x55555555,
0x33333333, 0x0F0F0F0F, 0x00FF00FF, 0x0000FFFF ;
int i ; int shift ;
}
/ n´ umero umero de de posicio posiciones nes a despla desplaza zarse rse a la derech derechaa /
∗
∗
for ( i=0, shift shift =1 =1;; i < 5; i++, shift = 2 ) x = (x & mask[i]) + ( (x >> shift) shift) & mask[i] mask[i] ); return x ;
∗
} Figura 3.8: M´etodo eto do tres versi´on on del bucle for a la l a suma expl´ expl´ıcita. Este proceso pro ceso de reducir o eliminar iteraciones iteraciones de bucles es una t´ ecnica ecnica de optimizaci´ optimizaci´ on on conocida como loop unrolling .
3.6.3.
M´ etodo etodo tres tres
Hay otro m´ etodo etodo ingenioso ingenioso de contar contar bits que est´ an en un dato. Este m´etodo etod o literalmente litera lmente a˜ nade los unos y ceros del dato unido. Esta suma debe nade ser igual al n´ umero de unos en el dato. Por ejemplo considere calcular los umero unos en un byte almacenado en una variable llamada data. El primer paso es hacer la siguiente operaci´ on: on: data = (data & 0x55) + ((data >> 1) & 0x55); ¿Qu´e hace hac e esto? est o? La constante consta nte hexadecimal hexa decimal 0X55 es 01010101 en binario. En el primer operando de la suma data es AND con ´el, el, los l os bits bi ts en las posici p osiciones ones pares se sacan. El Segundo operando (data >> 1 & 0x55), primero mueve todos los bits de posiciones pares a impares y usa la misma m´ ascara ascara para 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: 00 01 00 01 data data & 010101012 + (data >> 1) & 010101012 or + 01 01 00 01 01 10 00 10
66
CAP ´ITULO ITULO 3. OPERACI OPERACIONES ONES CON CON BITS
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´ mayor´ıa de estas sumas pueden ser dos, no hay posibilidad de que la suma desborde este campo y da˜ne ne otro de los campos de la suma. Claro est´ a, a, el n´ umero total de bits no se ha calculado a´ umero un. un. Sin embargo la misma t´ecnica ecnica que se us´ o arriba se puede usar para calcular el total en una serie de pasos similares. El siguiente paso podr´ podr´ıa ser: data = (data & 0x33) + ((data >> 2) & 0x33); Continuando con el ejemplo de arriba (recuerde que data es ahora 011000102): 0010 0010 0010 0010 data data & 001100112 + (data >> 2) & 001100112 or + 0001 0000 0011 0011 0010 0010 Ahora hay 2 campos de 4 bits que se suman independientemente. El pr´oximo oximo paso es sumar estas dos sumas unidas para conformar el resultado final: data = (data & 0x0F) + ((data >> 4) & 0x0F); Usando el ejemplo de arriba (con data igual a 001100102 ): 00000010 data data & 000011112 + (data >> 4) & 000011112 or + 00000011 00000101 Ahora data es 5 que es el resultado correcto. La Figura 3.8 muestra una implementaci´ on on de este m´etodo etodo que cuenta los bits en una palabra palabra doble. doble. Usa un bucle for para calcular la suma. Podr´ Podr´ıa ser m´ as as r´ apido apido deshacer el bucle; sin embargo, el bucle clarifica c´ omo omo el e l m´etodo etod o generaliz gen eralizaa a diferentes tama˜ nos nos de datos.
Cap´ıtulo 4
Subprogramas Este cap´ cap´ıtulo se concentr concentraa en el uso de subprogramas subprogramas para hacer 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 c´ odigo odigo que llama el subprograma y el subprograma en s´ı mismo mi smo deben deb en estar de acuerdo en c´omo omo se pasar´ an los datos entre ellos. Estas reglas de an c´omo omo se pasar´ an an el dato son llamadas convenciones de llamado. llamado. Una gran parte de este cap´ cap´ıtulo tratar´ tratara´ de las convenci convenciones ones de llamado est´ andares andares de C, que se 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. 4.1.
Direcc Direccion ionami amien ento to indire indirecto cto
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 par´entesis entesis cuadrados ([]) por ejemplo: 1 2 3
mov mov mov mov
ax, ax, [Dat [Data] a] ebx, Data ax, [ebx]
; Dire Direcc ccio iona nami mien ento to dire direct cto o de memo memori ria a de una una pala palabr bra a ; ebx = & Data ; ax = *ebx
Debido Debido a que AX almacena almacena una palabra, la l´ınea 3 lee una palabra palabra comencomenzando en la direcci´on on almacenada en EBX. Si AX fuera reemplazando con AL, se leer´ leer´ıa un solo byte. byte. Es importante importante notar que los registros registros no tienen tienen tipos como lo hacen las variables en C. A lo que EBX se asume que se˜nala nala est´a totalmente tot almente determinada por qu´ e instrucciones se usan. Si EBX se utiliza incorrectamente, a menudo no habr´ a error en el ensamblador; sin embargo, el programa no trabajar´ a correctamente. Esta es una de las muchas razones 67
CAP ´ITULO 4. SUBPROGR SUBPROGRAMAS AMAS
68
por la cual el ensamblador es m´ as propenso a errores que los lenguajes de as alto nivel. Todos los registros de 32 bits de prop´ osito general (EAX, EBX, ECX, osito 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. 4.2.
Sencill Sencillo o sub subpro progra grama ma de ejemplo ejemplo
Un subprograma es una unidad independiente de c´odigo odigo que puede ser usada desde diferentes partes de un programa. En otras palabras, un subprograma es como una funci´ on en C. Se puede usar un salto para invocar el on subprograma, pero el retorno representa un problema. Si el subprograma es usado en diferentes partes del programa debe retornar a la parte del c´odigo odigo desde la que se la invoc´o. o. Por lo tanto, el salto de retorno desde el subprograma no puede ser a una etiqueta. El c´ odigo siguiente muestra c´ odigo omo o mo se puede realizar esto usando una forma indirecta de la instrucci´on on JMP. Esta forma de la instrucci´on on JMP usa el valor de un registro para determinar a d´ onde onde saltar (as´ (as´ı, el registro registro se comporta muy parecido parecido a un apuntador a una funci´ on en C). A continuaci´ on, on, est´ a el primer programa del Cap´ Cap´ıtulo 1 reescrito reescrito para usarlo como subprogram subprograma. a. 1 2 3
sub1.asm ; file: file: sub1.asm sub1.asm ; Subprog Subprograma rama programa programa de ejemplo ejemplo %include %include "asm_io.inc" "asm_io.inc"
4 5 6 7 8 9 10
segment prompt1 pt1 prompt2 pt2 outmsg1 sg1 outmsg2 outmsg3
.data db db db db db db db
"In "Ingrese ese un nu ´mero: ", 0 "In "Ingrese ese otro tro n´ umer umero: o: ", 0 "Ud "Ud. ha ha in ingre gresado ado ", ", 0 " y ", 0 ", la la su suma de de el ellos es es ", ", 0
; no olvide el NULL
11 12 13 14
segment segment .bss inpu input1 t1 resd resd 1 inpu input2 t2 resd resd 1
15 16 17 18 19 20 21
segment .text globa global l _asm_main: enter pusha
_a _asm_ sm_ma main in 0, 0,0
; setup routine
4.2. SENCILLO SENCILLO SUBPROGRA SUBPROGRAMA MA DE EJEMPLO EJEMPLO 22 23
69
mov call call
eax, prompt1 prin print_ t_st stri ring ng
; imprime el prompt
mov mov jmp
ebx, input1 ecx, ret1 short get_int
; almacena la direccion o´n de inpu input1 t1 en ebx ebx ; almacena la direccion o´n de reto retorn rno o en ecx ecx ; lee un entero
mov call call
eax, prompt2 prin print_ t_st stri ring ng
; imprime el prompt
mov mov jmp
ebx, input2 ecx, \$ + 7 short get_int
mov add mov
eax, [input1] eax, [input2] ebx, eax
mov call mov call mov call mov call mov call mov call call
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
24 25 26 27 28
ret1:
29 30 31 32 33 34
; ecx = esta direccio ´n + 7
35 36 37 38
; eax = palabra doble en input1 ; eax += palabra doble en input2 ; ebx = eax
39 40 41 42 43 44 45 46 47 48 49 50 51 52
; imprime el primer mensaje ; imprime input1 ; imprime el segundo mensaje ; imprime input2 ; imprime el tercer mensaje ; imprime sum (ebx) ; imprime nueva lınea ı ´nea
53 54 55 56 57 58 59 60 61 62 63
; ; ; ; ; ;
popa mov eax, 0 ; retorno a C leave ret subprog subprograma rama get_int get_int Par´ Par´ ametros: ametros: ebx - direccion o ´n de la palab palabra ra doble doble que almace almacena na el ente entero ro ecx - direccion o ´n de la instr instrucc ucci´ i´ on o n a donde donde reto retorna rnar r No Note tes: s: el val valor de ea eax se de destru truye
CAP ´ITULO 4. SUBPROGR SUBPROGRAMAS AMAS
70 64 65 66 67
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 convenci´on on de llamado simple basada en un registro. Ella espera que el registro EBX almacene la direcci´on o n de la palabra doble del n´ umero de entrada y que el registro ECX a almacene umero el c´ odigo odigo de la instrucci´ on on a saltar. En las l´ıneas 25 a 28, el operador operador $ se usa para calcular la direcci´ on de retorno. El operador $ retorna on retorna la direcci´ direcci´ on on de la l´ınea en que aparece. aparece. La expresi´ expresi´ on on $ + 7 calcula la direcci´ on o n de la instrucci´ on on MOV de la l´ınea ıne a 36. Los dos c´alculos alculos de la direcci´on on de retorno son complicados. El primer m´etodo etodo requiere que una etiqueta se defina en cada llamado a subprograma. El segundo m´etodo etodo no requiere una etiqueta, pero requiere un tratamiento cuidadoso. Si se us´o un salto largo en lugar de uno corto ¡el n´ umero umero a a˜ nadirle nadirle a $ podr´ıa ıa no ser 7! Afortunadamente hay una manera mucho m´as as simple de invocar subprogramas. Este m´etodo etodo usa la pila .
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´ a organizada de esta manera. La instrucci´ on on PUSH a˜nade nade datos a la pila y la instrucci´on on POP quita datos. El dato extra´ıdo ıdo siempre es el ultimo u´ltimo dato insertado (esta es la raz´on on por la cual es llamado FIFO). El registro de segmento SS especifica el segmento de datos que contiene la pila. (Normalmente este es el mismo segmento de datos). El registro ESP contiene la direcci´ on on del dato que ser´ ser´ıa quitado quitado de la pila. Los datos s´ olo olo se pueden a˜ nadir en unidades de palabras dobles. Esto es, que no se puede nadir insertar un solo byte en la pila. La instrucci´on on PUSH inserta una palabra doble1 en la pila rest´ andole andole 4 a ESP y entonces almacena la palabra doble en [ESP]. La instrucci´on on nade nade 4 a ESP. El POP lee la palabra doble almacenada en [ESP] y luego a˜ c´odigo odigo siguiente demuestra c´ omo trabajan estas instrucciones asumiendo que omo el valor inicial de ESP es 1000H. 1 2 3
push push push 1
dword 1 dword 2 dword 3
; 1 almacendao en en 0FFCh, ES ESP = 0FFCh ; 2 al almacenado en en 0F 0FF8h, ES ESP = 0F 0FF8h ; 3 almacenado en en 0FF4h, ES ESP = 0FF4h
Tambi´ en en se pueden empujar palabras, pero en el modo mo do protegido de 32 bits es mejor trabajar s´ olo con palabras dobles en la pila. olo
4.4. LAS INSTRU INSTRUCCION CCIONES ES CALL Y RET 4 5 6
pop pop pop
eax ebx ecx
71
; EAX = 3, ESP = 0FF8h ; EBX = 2, ESP = 0FFCh ; ECX = 1, ESP = 1000h
La pila se puede usar como un almac´ almac´en en de datos temporal muy conveconveniente. Tambi´en en se usa para pa ra el llamado ll amado a subprogramas, subprog ramas, pasando par´ ametros ametros y variables locales. El 80x86 80 x86 tambi´ ta mbi´en en suministra sum inistra la instrucci in strucci´ on o´n PSHA que empuja el valor de los registros: EAX, EBX, ECX, EDX, ESI, EOI y EBP (no en este orden). La instrucci´on on POPA se puede usar para devolver todos estos registros a su valor anterior.
4.4. 4.4.
Las Las inst instru rucc ccio ione ness CALL CALL y RET RET
El 80x86 suministra dos instrucciones que usa la pila para hacer llamados a subprogramas r´apido apido y f´ acil. acil. La instrucci´on on CALL hace un salto incondicional a un subprograma y empuja en la pila la direcci´on o n de la pr´oxima oxima instrucci´ on. on. La instrucci´on on RET saca una direcci´on on de la pila y salta a esta direcci´ on. Cuando se usa esta instrucci´on, on. on, es muy importante que uno administre ministre la pila correctament correctamentee ya que la instrucci´ instrucci´ on RET debe extraer de la on pila el n´ umero umero correcto. El programa anterior se puede reescribir usando estas nuevas instrucciones cambiando las l´ıneas 25 a 34 por: mov cal call
ebx, input1 get_in _int
mov cal call
ebx, input2 get_in _int
y cambiando el subprograma get int a: get_int: call call mov ret
read read_i _int nt [ebx], eax
Hay varias ventajas de CALL y RET Es simple Permite a los subprogramas hacer llamados anidados f´acilmente. acilmente. Observe que get int llama read int. Esta llamada empuja otra direcci´ on on
CAP ´ITULO 4. SUBPROGR SUBPROGRAMAS AMAS
72
en la pila. Al final del c´ odigo odigo de red int hay un RET que saca la direcci´ on de retorno y que salta de nuevo al c´odigo on odigo de get int. Cuando la instrucci´ on on RET de get int se ejecuta, saca la direcci´on on de retorno 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 call mov push ret
6
read read_i _int nt [ebx], eax eax ; ¡¡saca el valor de EAX, ; no la dire direcc cci´ i´ on o n de retor retorno! no!! !
Este c´odigo odigo no retornar retorna r´ıa correctamente. correct amente.
4.5. 4.5.
Conv Convencion enciones es de llamad llamado o
Cuando un subprograma se invoca, el c´ odigo llamado y el subprograma odigo (el llamador ) llamador ) deben estar de acuerdo en c´omo omo se pasan datos entre ellos. Los lenguajes de alto nivel tienen modos normalizados de pasarse datos, conocidas conocidas como convenci convenciones ones de llamado. llamado. Para interfasar c´odigo odigo de alto nivel con lenguaje ensamblador, ´este este debe usar las mismas convenciones que el lenguaje de alto nivel. Las convenciones de llamado pueden diferir de compilador a compilador o pueden variar dependiendo de c´omo omo se compila el c´odigo odigo (si se ha optimizado o no). Una convenci´on on universal es que el c´odigo odigo ser´ a invocado con la instrucci´ on on CALL y retornar´a con RET. Todos los compiladores de C para PC soportan una convenci´on on de llamado que ser´a descrita en el resto del cap´ cap´ıtulo por p or etapas. Estas convenciones le permiten a uno crear subprogramas que sean reentrantes. reentrantes. Un subprograma reentrante puede ser llamado en cualquier punto del programa con seguridad (a´un un dentro del subprograma mismo).
4.5.1.
Pasando Pasando par´ ametros ametros en la pila
Los par´ametros ametros a un subprograma se pueden pasar en la pila. Ellos se empujan en la pila antes de la instrucci´on on CALL. Tal como en C, si el par´ ameametro es cambiado por el subprograma se debe pasar la direcci´ on del dato no su valor . Si el tama˜ no no del par´ametro ametro es menor que una palabra doble, se debe convertir a palabra doble antes de ser empujado en la pila. Los par´ametros ametros no son sacados de la pila por el subprograma, en lugar de ello son accedidos a ccedidos desde la pila misma.¿Por qu´e? e?
4.5. CONVENCIO CONVENCIONES NES DE LLAMADO LLAMADO
ESP + 4 ESP
73
Parametro a´metro Direccion ´on de retorno Figura 4.1:
ESP + 8 ESP ESP + 4 ESP
Parametro a´metro Dire Direcc cci´ i´ on on de retorno datos del subprograma Figura 4.2:
Ya que ellos se han empujado a la pila antes de la instrucci´on on CALL, la direcci´ on on de retorno retorno tendr´ tendr´ıa que haberse haberse sacado sacado primero primero (y luego metido otra vez). A menudo los par´ametros ametros tendr´ tendran a´n que usarse en varios lugares en el subprograma. Normalmente, ellos no se pueden dejar en un registro durante todo el subprograma y tendr´ tendr´ıa que almacenarse en memoria. Dej´andolos andolos en la pila tenemos una copia del dato en memoria que se puede acceder en cualquier parte del subprograma. Considere un subprograma al que se le pasa un solo par´ametro ametro en la pila. Cuando el subprograma se invoca, la pila se ve como en la Figura 4.1. Se puede acceder al par´ametro ametro usando direccionamiento indirecto ([ESP+4])2 . Si la pila se usa dentro del subprograma para almacenar datos, el n´ umero umero necesario a ser agregado a ESP cambiar´ a. Por ejemplo, la Figura 4.2 muestra a. c´omo omo se ve la pila si una palabra doble se empuja en ella. Ahora el par´ametro ametro es ESP + 8 y no ESP + 4. As´ As´ı, esto puede ser muy propenso a errores usar ESP cuando uno se refiere a par´ametros. ametros. Para resolver este problema, el 80386 suministra otro registro: EBP. El unico u´nico prop´ osito osito de este registro es referenciar datos en la pila. La convenci´on on de llamado de C ordena 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 modificar EBP. Al final del subprograma, se debe restaurar el valor de EBP (esta es la raz´on on por la cual se guarda el valor al principio del subprograma). La Figura 4.3 muestra la forma general de un subprograma que sigue estas convenciones. 2
Es v´ alido alido a˜ nadir una constante a un registro cuando se usa direccionamiento indirecnadir to. Se pueden construir expresiones m´as as complicadas complic adas tambi´en. en. Este t´ topico o´pico se ver´ a en el cap´ıtulo ıtul o siguient sig uiente. e.
Cuando se usa direccionamiento indirecto, el procesador 80x86 accede a segmentos diferentes diferentes dependependiendo de qu´e registros se se usan usan en la expr expres esi´ i´ on de direcciona direccionamiento miento indirecindirecto. ESP (y EBP) usan el segmento de la pila mientras que EAX, EBX, ECX y EDX EDX usan usan el segm segmen ento to de datos. Sin embargo, esto normalmente no tiene importancia para la mayor´ıa ıa de los programas en modo protegido, porque para ellos los segmentos de datos y de la pila son los mismos.
CAP ´ITULO 4. SUBPROGR SUBPROGRAMAS AMAS
74 1 2 3 4 5 6
subprogram_label: push ebp ; guarda el valor original de EBP en la pila mov ebp, es esp ; nuevo EB EBP = ES ESP ; subprog subprogram ram code pop ebp ; restaura el valor original de EBP ret
Figura 4.3: Forma general de un subprograma
ESP + 8 ESP ESP + 4 ESP
EBP + 8 EBP EBP + 4 EBP
Parametro a´metro Dir Direcci ecci´ on o´n de retorno EBP guardado
Figura 4.4: Las l´ıneas 2 y 3 de la Figura Figura 4.3 componen el pr´ ologo general de un subprograma. Las l´ıneas 5 y 6 conforman el ep´ılogo ogo. La figura 4.4 muestra c´omo omo se s e ve la pila inmediatamente inmedia tamente despu´es es del pr´ ologo. ologo. Ahora los par´ ametros ametros se pueden acceder con [EBP + 8] en cualquier lugar del subprograma sin importar qu´ e haya empujado en la pila el subprograma. Luego que el subprograma culmina, los par´ametros ametros que se empujan en la pila se deben quitar. La convenci´on on de llamado de C especifica que el c´odigo odigo llamado llamadorr debe hacer hacer esto. esto. Otras Otras conve convenci ncione oness son difere diferent ntes. es. Por Por ejemplo la convenci´on on de llamado de Pascal especifica que el subprograma debe quitar los par´ametros ametros de la pila (hay otra forma de la instrucci´ on on RET que hace esto f´acil). acil). Algunos compiladores de C soportan esta convenci´ on on tambi´en. en. El identificador identifica dor pascal es usado en la definici´on on del prototipo de la funci´ on on para decirle decirle al compilador compilador que emplee esta convenci´ convenci´ on. on. De hecho, la convenci´ on on stdcall que usan las funciones de C del API de MS Windows trabajan de esta forma. ¿Cu´ al es la ventaja de este modo? Es un poco m´as al as eficiente que la convenci´on on de llamado de C. Entonces, Entonces, ¿Por qu´ e todas las funciones no usan esta convenci´on? on? En general C le permite a una funci´on on tener un n´ umero variable de argumentos (printf y scanf son ejemplos). umero Para este tipo de funciones, la operaci´on o n de quitar los par´ametros ametros de la pila variar´ a de un llamado a otro. La convenci´ on de C permite realizar esta on operaci´on o n f´acilmente acilmente de un llamado a otro. Las convenciones de Pascal y stdcall stdcall hacen esta operaci´ on on muy dif´ıcil. ıci l. As´ As´ı, la convenci´ conven ci´ on on de Pascal (como el lenguaje Pascal) no permite este tipo de funciones. MS Windows puede usar esta conven convenci´ ci´ o n ya que ninguna de las funciones del API toma un on n´umero umero variable de argumentos.
4.5. CONVENCIO CONVENCIONES NES DE LLAMADO LLAMADO 1 2 3
push call add
dword 1 fun esp, 4
75
; pasa 1 como parametro a ´metro ; quita el para ametr ´metro o de la pila pila
Figura 4.5: Muestra del llamado a un subprograma La Figura 4.5 muestra como ser´ ser´ıa invocado un subprograma usando la convenci´on on de llamado de C. La l´ınea ınea 3 quita los par´ ametros ametros de la pila manipulando directamente el apuntador de la pila. Una instrucci´on on POP se podr´ıa ıa usar para hacer esto pero requerir´ requerir´ıa que el resultado in´ util u til se almacene en un registro. Actualmente, para este caso en particular muchos compiladores compil adores podr po dr´´ıan usar usa r una instrucci i nstrucci´ on o´n POP ECX para quitar el par´ameametro. El compilador usar´ usar´ıa POP en lugar de ADD porque ADD requiere m´as as bytes para la instrucci´on. on. Sin embargo, POP tambi´en en altera el valor de ECX. A continuaci´ on on est´a otro programa de ejemplo con dos subprogramas que usan la convenci´on on de llamado de C discutida 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 ser´an an combinados en un solo segmento de texto y datos en el proceso de encadenamiento. Dividir el c´odigo odigo y los datos en segmentos separados permite que los datos de un subprograma se definan cerca del c´odigo odigo del subprograma.
1
%include "asm_io.inc" "asm_io.inc"
sub3.asm
2 3 4
segment .data sum dd 0
5 6 7
segment segment .bss .bss input resd 1
8 9 10 11 12 13 14 15 16 17 18
; ; pseudopseudo-c´ c´ odigo odigo ; i = 1; 1; ; sum = 0; ; wh whil ile( e( ge get_i t_int nt(i, (i, &input &input), ), in input put != 0 ) { ; sum += input; ; i++; ; } ; print_sum(num) print_sum(num); ; segment .text
CAP ´ITULO 4. SUBPROGR SUBPROGRAMAS AMAS
76 19 20 21 22
globa global l _asm_main: enter pusha
_a _asm_ sm_ma main in 0, 0,0
; setup routine
mov while_loop: push push call add
edx, 1
; edx es ’i’ en el pseudocodigo o ´digo
edx dword input get_int esp, 8
; guarda ’i’ en la pila ; empuja la direccion o´n de input en la pila
23 24 25 26 27 28 29
; quita i e &input de la pila
30 31 32 33
mov cmp je
eax, [input] eax, 0 end_while
add
[sum], eax
inc jmp
edx short while_loop
34 35
; sum += input
36 37 38 39 40 41 42 43
end_while: push call pop
dword [sum] print_sum ecx
; empuja el valor de sum ; quita [sum] de la pila
44 45 46 47
popa leave ret
48 49 50 51 52 53 54 55 56
; subprog subprograma rama get_int get_int ; Param´ Param´ etro e tros s (en (en el orde orden n que que es empu empuja jan n en la pila pila) ) ; n´ umer u mero o de inpu input t (en (en [ebp [ebp + 12]) 12]) ; direccion o ´ n de input en [ebp + 8]) ; Notas: Notas: ; Los Los valo valore res s de eax eax y ebx ebx se dest destru ruye yen n segment .data prompt db ") In Ingrese un un en entero (0 (0 pa para sa salir): ", ", 0
57 58 59 60
segment .text get_int: push
ebp
en la pila
4.5. CONVENCIO CONVENCIONES NES DE LLAMADO LLAMADO 61
mov
ebp, esp
mov call
eax, [ebp + 12] print_int
mov call call
eax, prompt prin print_ t_st stri ring ng
call mov mov
read_int ebx, [ebp + 8] [ebx], eax
pop ret
ebp
62 63 64 65 66 67 68 69 70 71
; almacena input en memoria
72 73 74
; retorna al llamador
75 76 77 78 79 80 81 82 83
; subprog subprograma rama print_s print_sum um ; im impr prime ime la su suma ma ; Paramet Parameter: er: ; suma suma a imp impri rimi mir r (en (en [ebp [ebp+8 +8]) ]) ; No Nota ta: : de destr struy uye e el valor valor de ea eax x ; segment .data result db "La suma es ", 0
84 85 86 87 88
segment .text print_sum: push mov
ebp ebp, esp
89 90 91
mov call call
eax, result prin print_ t_st stri ring ng
mov call call
eax, [ebp+8] print_int print_nl
pop ret
ebp
92 93 94 95 96 97 98
sub3.asm
77
CAP ´ITULO 4. SUBPROGR SUBPROGRAMAS AMAS
78 1 2
subprogram_label: push ebp
3
mov sub
4 5
ebp, esp esp, sp, LOC LOCA AL_BYT BYTES
6 7 8 9 10
; subprog subprogram ram code mov esp, ebp pop ebp ret
; ; ; ; ;
guarda el valor original de EBP en la pila nuevo EBP = ESP = # de de by bytes tes nec nece esitad tados po por la las variable variables s locales 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; 0; for ( i=1; i <= n; i++ )
sum += i; sump = sum;
}
∗
Figura 4.7: versi´ on on de C de sum
4.5.2. 4.5.2.
Variab ariables les local locales es en la la pila pila
La pila se puede usar como un lugar adecuado para las variables locales. Ah´ Ah´ı es exactamente donde C almacena las variables variables normales (o ( o automatic como se dice en C). Usar la pila para las variables es importante si uno desea que el programa programa sea reentran reentrante. te. Un programa programa reentran reentrante te trabajar´ a si es invocado en cualquier lugar, incluido en subprograma en s´ı mismo. mi smo. En otras palabras, los programas reentrantes pueden ser invocados recursivamente. recursivamente . Usar la pila para las variables ariables tambi´ tambi´ en en ahorra ahorra memoria. memoria. Los datos no almacenados en la pila est´ an usando la memoria desde el comienzo hasta el an final del programa (C llama este tipo de variable global o static). static ). Los datos almacenados en la pila solo usan la memoria cuando el subprograma que los define est´a activo. Las variables locales loca les son almacenadas almace nadas justo jus to despu´es es que se guard´ gu ard´ o el valor EBP en la pila. Ellas son colocadas restando el n´ umero de bytes requeridos de umero ESP en el pr´ologo ologo del subprograma. La Figura 4.6 muestra el nuevo esqueleto del subprograma. El registro EBP se usa para acceder a las variables locales.
4.5. CONVENCIO CONVENCIONES NES DE LLAMADO LLAMADO 1 2 3 4
cal_sum: push mov sub
79
ebp ebp, esp esp, 4
; hace espacio para la sum local
dword [ebp - 4], 0 ebx, 1
; sum = 0 ; ebx (i) = 1
ebx, [ebp+8] end_fo _for
; es i <= n?
[ebp-4], ebx ebx short for_l r_loop
; sum += i
ebx, [ebp+12] eax, [ebp-4] [ebx], eax
; ebx = sump ; eax = sum ; *sump = sum;
5 6 7 8 9 10
mov mov for_loop: cmp jnl jnle
11 12 13 14
add inc jmp jmp
15 16 17 18 19
end_for: mov mov mov
20 21 22 23
mov pop ret
esp, ebp ebp
Figura 4.8: Versi´ on en ensamblador de sum on
Considere la funci´on on de C en la Figura 4.7. La Figura 4.8 muestra c´omo omo se podr´ıa ıa escribir un programa equivalente equivalente en ensamblador. La Figura 4.9 muestra como se ve la pila luego del pr´ologo ologo del programa programa en la Figura 4.8 4.8.. Esta parte de la pila que contiene la informaci´ on on de retorno, los par´ ametros y las variables locales es llamado marco de pila (stack frame). ametros frame ). Cada invocaci´ on on a una funci´on on de C crea un nuevo marco de la pila en la pila. A pesar del hecho que El pr´ologo ologo y el ep´ ep´ılogo de un subprograma se pueden simplificar usando dos instrucciones especiales que est´an an dise˜ nadas nadas espec´ıficamente ıficamente para este prop´ osito. osito. La instrucci´on on ENTER ejecuta el c´odigo odigo del pr´ologo ologo y LEAVE ejecuta el ep´ ep´ılogo. ılog o. La instrucci´ instrucc i´ on on ENTER toma dos operandos inmediatos. Para la convenci´on on de llamado de C, el segundo operando es siempre 0. El primer operando es el n´umero umero de bytes necesarios para las variables locales. La instrucci´ on on LEAVE no tiene operandos. La Figura 4.10 muestra c´omo omo se usan
ENTER y LEAVE simplifican
el pr´ ologo y el ep´ ep´ılogo ellos el los no se usan muy a menudo. ¿Por qu´e? e? Porque ellas ell as son m´ as lentas que instrucciones equivalentes m´ as simples. Este es un ejemplo de cuando uno no puede asumir que una sola instrucci´ on es m´ as r´ apida que una secuencia de instrucciones. instrucciones.
CAP ´ITULO 4. SUBPROGR SUBPROGRAMAS AMAS
80 ESP ESP ESP ESP ESP ESP
+ 16 + 12 +8 +4
EBP EBP EBP EBP EBP EBP
+ 12 +8 +4
sump n
Direccion o´n de retorno EBP guardado
-4
sum
Figura 4.9: 1 2
subprogram_label: enter LOCAL_BYTES, 0
3 4 5 6
; = numero u ´mero de bytes bytes necesita necesitados dos ; por las varia variable bles s local locales es
; subprog subprogram ram code leave ret
Figura 4.10: Forma general de un subprograma con variables locales usando ENTER and LEAVE estas instrucciones. Observe que el programa esqueleto (Figura 1.7 1.7)) tamb ta mbi´ i´en en usa ENTER y LEAVE.
4.6. 4.6.
Prog Progra rama mass Mu Mult ltin inm´ m´ odulo odulo
Un programa programa multim´ odulo es uno que est´a compuesto de m´ as as de un archivo objeto. Todos los programas presentados ac´ a han sido multim´ odulo. odulo. Ellos est´ an compuestos del archivo objeto driver y el archivo objeto de enan samblador (m´ as los archivos objeto de las bibliotecas de C). Recuerde que as el encadenador combina los archivos objeto en un solo programa ejecutable. El encadenador debe emparejar las referencias hechas para cada etiqueta en un m´odulo odulo (archivo objeto) con su definici´on o n en otro m´ odulo. odulo. Para que el m´ odulo A use la etiqueta definida en el m´ odulo odulo B, se debe usar la directiva odulo 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 m´ odulo. O sea, esas son etiquetas que se pueden usar en odulo. este m´ odulo odulo pero est´an an definidas en otro. El archivo asm io.inc define las rutinas read int, etc. como externas. En ensamblador, no se puede acceder externamente a las etiquetas por omisi´ on. Si una etiqueta puede ser accedida desde otros m´ on. odulos odulos diferentes al cual se defini´ o, o, debe declararse global en su m´ odulo. odulo. La directiva global hace esto, la l´ınea ınea 13 del programa esqueleto listado en la Figura 1.7 muestra que la etiqueta asm main est´a definida como global. Sin esta declaraci´on, on, el
´ 4.6. PROGRAMAS PROGRAMAS MUL MULTINM TINM ODULO
81
encadenador encadena dor deber´ıa ıa generar un error. ¿Por qu´e? e? Porque el c´odigo odigo de C no podr´ıa ıa hacer referencia a la etiqueta interna asm main. A continuaci´ on on est´ a el c´ odigo del ejemplo anterior, reescrito para usar dos odigo m´ odulos. Los dos subprogramas (get int y print sum) est´ odulos. an an en archivos fuentes diferentes que la rutina asm main.
1
%include "asm_io.inc" "asm_io.inc"
main4.asm
2 3 4
segment .data sum dd 0
5 6 7
segment segment .bss .bss input resd 1
8 9 10 11 12 13 14
segment .text gl globa obal l extern extern _asm_main: enter pusha
_asm_ _asm_mai main n get_int, get_int, print_su print_sum m 0,0
; setup routine
edx, 1
; edx es ’i’ en el pseudocodigo o ´digo
edx dword input get_int esp, 8
; guarda i en la pila ; empuja la direccion o´n inpu input t en la pila pila
15 16 17 18 19 20 21
mov while_loop: push push call add
; quita i e &input de la pila
22 23 24 25
mov cmp je
eax, [input] eax, 0 end_while
add
[sum], eax
inc jmp
edx short while_loop
26 27
; sum += input
28 29 30 31 32 33 34 35 36
end_while: push call pop
dword [sum] print_sum ecx
; empuja el valor de sum de la pila ; quita sum de la pila
CAP ´ITULO 4. SUBPROGR SUBPROGRAMAS AMAS
82 37 38 39
1
popa leave ret
main4.asm
%include %include "asm_io.inc" "asm_io.inc"
sub4.asm
2 3 4
segment .data prompt db
") Ingrese un nu umero ´mero entero entero (0 para para salir salir): ): ", 0
5 6 7 8 9
segment .text global global get_int: enter
get_int get_int, , print_ print_sum sum 0,0
10 11 12
mov call
eax, [ebp + 12] print_int
mov call call
eax, prompt prin print_ t_st stri ring ng
call mov mov
read_int ebx, [ebp + 8] [ebx], eax
13 14 15 16 17 18 19
; almacena input en memoria
20 21 22
leave ret
; retorna
23 24 25
segment .data result db
"La suma es ", 0
segment .text print_sum: enter
0,0
26 27 28 29 30 31 32
mov call call
eax, result prin print_ t_st stri ring ng
mov call call
eax, [ebp+8] print_int print_nl
33 34 35 36 37 38
leave
4.7. INTERFAZANDO INTERFAZANDO ENSAMBLADOR ENSAMBLADOR CON C ret
39
83
sub4.asm
El ejemplo anterior solo tiene etiquetas de c´odigo odigo global sin embargo las etiquetas de datos global trabajan exactamente de la misma manera.
4.7. 4.7.
Inter Interfaz fazand ando o ensa ensamb mblad lador or con C
Hoy d´ıa, ıa, pocos po cos programas program as est´an an escritos completamente en ensamblador. Los compiladores son muy buenos en convertir c´odigo odigo de alto nivel en un c´odigo odigo de m´ aquina eficiente. Ya que es mucho m´as aquina as f´ acil acil escribir c´odigo odigo en un lenguaje de alto nivel, es m´ as as popular. Adem´as, as, el c´odigo odigo de alto nivel es mucho m´ as as port´ atil que el ensamblador. atil Cuando se usa ensamblador, se usa a menudo solo para peque˜ nas nas partes de c´odigo. odigo. Esto se puede hacer de dos maneras: llamando rutinas de ensamblador desde C o ensamblado en l´ınea. El ensamblado en l´ınea le permite al programador programador colocar instruccion instrucciones es de ensamblador ensamblador directament directamentee en el c´ odiodigo de C. Esto puede ser muy conveniente; sin embargo hay desventajas del ensambla ensa mblado do en e n l´ınea. ınea . El c´ odigo en ensamblador se debe escribir en el formato odigo que usa el compilador. No hay compilador que en el momento soporte el formato de 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 t´ecnica ecnica de llamar lla mar una un a rutina ruti na en ensamblado e nsambladorr est´a mucho m´ as generalizada en el PC. as Las rutinas de ensamblador com´ unmente se usan con C por las siguientes unmente razones: Se necesita acceso directo a caracter´ caracter´ısticas del hardware del computador que es imposible o dif´ dif´ıcil acceder desde C. La rutina debe ser lo m´as as r´apida apida posible y el programador puede optimizar a mano el c´ odigo mejor que el compilador odigo La ´ultima ultima raz´ on on no es tan v´alida alida como una un a vez lo fue. f ue. La tecnolo t ecnologg´ıa de los l os compiladores se ha mejorado con los a˜ nos y a menudo generan un c´odigo nos odigo muy eficiente. (Especialmente si se activan las optimizaciones del compilador). Las desventajas de las rutinas en ensamblador son: portabilidad reducida y lo poca legibilidad. La mayor´ mayor´ıa de las convenciones convenciones de llamado ya se han especificado. esp ecificado. Sin embargo hay algunas caracter´ caracter´ısticas adicionales que necesitan ser descritas. 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.
CAP ´ITULO 4. SUBPROGR SUBPROGRAMAS AMAS
84 1 2 3
segment segment .data .data x dd format db
0 "x = %d\n", 0
4 5 6 7 8
segment segment .text .text ... push dword [x] push push dwor dword d form format at
9 10 11
call add
_printf esp, 8
; ; ; ; ;
empuja el valor de x empu empuja ja la la dire direcc cci´ i´ on de la cade cadena na con forma formato to observe el guion o ´n bajo bajo quita los parametr a ´metros os de la pila pila
Figura 4.11: Llamado a printf EBP + 12 EBP EBP + 8 EBP + 4 EBP
valor de x dire direcc cci´ i´ on de la cadena con formato on Direccion o´n de retorno EBP guardado
Figura 4.12: Pila dentro de printf
4.7.1. 4.7.1. La
palab labra
reser eservvada ada
Ahorr Ahorran ando do regist registros ros
Primero, C asume que una subrutina conserva los valores de los siguientes
puede usar usar registros: EBX, ESI, EDI, EBP, CS, DS, SS, ES. Esto no significa que la register se pue
en una una decl declar arac aci´ i´ on de una una vari variab able le de C para ara suge sugeri rirl rlee al compi ompila lado dor r que use un regis registr tro o para ara esta variable en vez de un luga lugarr en memo memori ria. a. Ellas Ellas se conocen como variables register. register. Los compiladores compiladores moder modernos nos hacen hacen esto esto autom´ aticamente sin requerir requerir ninguna sugerencia.
subrutina no pueda cambiarlos internamente. En vez de ello, significa 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 modificar porque C usa esos registros para variables register . Normalmente la pila se usa para guardar los valores originales de estos registros.
4.7.2. 4.7.2.
Etiqu Etiqueta etass de func funcion iones es
La mayor´ mayor´ıa de compiladores compil adores anteponen antepon en un gui´ on bajo ( ) al inicio de los on nombres de funciones y variables globales o static. Por ejemplo una funci´on on llamada f se le asignar´a la etiqueta f. As´ As´ı, si ´esta esta es una rutina en ensamblador se debe llamar f no f. El compilador gcc de Linux no antepone ning´ un un car´acter. acter. Ba jo los lo s ejecutables ELF de Linux, uno simplemente usar´ usar´ıa la etiqueta f para la funci´ on o n de C f. Sin embargo el gcc de DJGPP antepone un gui´on on bajo. Observe que en el programa esqueleto de ensamblador (Figura 1.7 1.7)) la etiqueta de la rutina principal es asm main.
4.7. INTERFAZANDO INTERFAZANDO ENSAMBLADOR ENSAMBLADOR CON C
4.7.3. 4.7.3.
85
Pasan Pasando do par´ par´ ametros ametros
Bajo la convenci´ on de llamado de C, los argumentos de una funci´on on on se empujan en la pila en el orden inverso que aparecen en el llamado a la funci´ on. on. Considere la siguiente instrucci´ on on en C: printf ("x=%d\n",x); ("x=%d\n",x); la Figura 4.11 muestra como se compilar´ compilar´ıa esto (mostrado en el formato equivalente equivalente de NASM). La Figura 4.12 muestra c´omo omo se ve la pila luego del pr´ologo ologo dentro de la funci´on on printf. La funci´on on printf es una de las funciones de la biblioteca de C que puede tomar cualquier n´umero umero de argumentos. Las reglas de la convenci´ on on de llamado ll amado de C fueron escritas espec´ espec´ıficamente para permitir este tipo de funciones. Ya que la direcci´on on de la cadena con formato se empuja de ultimo, u ´ ltimo, ´este este lugar en la pila ser´ a siempre EBP + 8 no importa cuantos par´ametros ametros se le pasan a la funci´on. o n. El c´odigo odigo printf puede ver en la cadena con formato cu´antos antos par´ ametros se le debieron haber pasado y ametros verlos en la en la pila. Claro est´ a, a , si se comete un error, printf ("x=%d\n"); odigo odigo de ("x=%d\n"); el c´ a imprimir una palabra doble en [EBP+12]. Sin embargo este printf esperar´ no ser´a el valor de x.
4.7.4. 4.7.4.
Calcula Calculando ndo las direc direccion ciones es de las varia variables bles locales locales
Hallar la direcci´on on de una variable local definida en el segmento data o asicament asicamentee el encadenador encadenador hace esto. Sin embargo embargo calcular bss es sencillo, b´ la direcci´ on de una variable local (o par´ on ametro) en la pila no es directo. ametro) Sin embargo, es una necesidad muy com´un un cuando se llaman subrutinas. Considere el caso de pasar la direcci´on on de una variable (la llamaremos x) a una funci´ funci´ on on (que llamaremos foo). Si x est´a en EBP 8 en la pila, uno no puede usar:
−
mov
eax, ebp - 8
¿Por qu´e? e? El valor que MOV almacena en EAX debe ser calculado por el ensamblador (esto es, debe ser una constante). Sin embargo, hay una instrucci´ on on que hace el c´alculo alculo deseado. Es llamada LEA (Load Efective Adress). Adress ). Lo siguiente s iguiente calcular calcul ar´´ıa la direcci´ direccion o´n de x y la almacena en EAX: lea
eax, [ebp - 8]
Ahora EAX almacena la direcci´ on o n de x y podr´ podr´ıa ser empujada empujada en la pila cuando se llame la funci´ on on foo. No se confunda, parece como si esta instrucci´ on estuviera leyendo el dato en [EBP 8]; sin embargo esto no es verdad. on ¡La instrucci´on on LEA nunca lee la memoria! Solo calcula la direcci´ on on que qu e ser´ s er´ıa le´ le´ıda por otra instrucci´ instruc ci´ on y almacena esta direcci´on on on en el primer operando de registro. Ya que no se hace ninguna lectura a memoria, no hay necesidad, ni est´a permitido definir el tama˜ no no de la memoria (dword u otros).
−
No es nec necesario esario usar usar ensamblador samblador para ara proc procesar esar un numero ume ´ ro arbitr arbitrari ario o de par´ ametros en C. El archivo de cabe cabece cera ra stdarg.h define marcos que se pueden usar para ara pro procesarlo cesarloss con portabilidad. Vea cualquier buen libro de C para los detalles
CAP ´ITULO 4. SUBPROGR SUBPROGRAMAS AMAS
86
4.7.5. 4.7.5.
Retornan Retornando do valores alores
Las funciones diferentes a void retornan un valor. La convenci´on on de llamado de C especifica c´omo omo se hace esto. Los valores de retorno se pasan a trav´ es es de registros. Todos los tipos enteros (char, int, enum, etc.) se retornan en el registro EAX. Si son m´as as peque˜ nos que 32 bits, ellos son extendidos nos a 32 bits cuando se almacenan en EAX. (el c´ omo se extienden depende de si omo ellos tipos son con o sin signo.) Los valores de 64 bits se retornan en el par de registros regis tros EDX:EAX. Los valores tipo ti po apuntador a puntador tambi´ ta mbi´en en se almacenan alm acenan en en EAX. Los valores de punto flotante son almacenados en el registro ST0 del coprocesador matem´ atico. atico. (Este registro se discute en el cap´ cap´ıtulo de punto flotante).
4.7.6. 4.7.6.
Otras Otras conv convenc encione ioness de llamado llamado
Las reglas anteriores describen la convenci´on on de llamado est´ andar andar de C que es soportada por todos los compiladores de C para 80x86. A menudo los compiladores soportan otras convenciones convenciones de llamado tambi´ en. en. Cuando se interfaza con lenguaje ensamblador es muy importante conocer que convenci´on on de llamado est´ a usando el compilador cuando llama su funci´on. on. Normalmente, por omisi´ on on se usa la convenci´on on de llamado est´ andar; andar; sin 4 embargo no siempre es este el caso . Los compiladores que usan varias convenciones venciones a menudo menudo tienen opciones de la l´ınea de ordenes o´rdenes que se pueden usar para cambiar la convenci´on on por omisi´ on. on. Ellos tambi´en en le l e suminist su ministran ran extensiones a la sintaxis de C para asignar expl´ expl´ıcitamente convenciones de llamado a funciones individuales. Sin embargo, estas extensiones no est´ an an normalizadas y pueden variar de un compilador a otro. El compilador GCC permite diferentes convenciones de llamado. La convenci´on on de una funci´on on se puede pue de declarar decla rar expl´ıcitamente ıcitam ente usando la extensi´ exte nsi´on on on void que usa la conattribute . Por ejemplo para declarar una funci´on venci´on on de llamado est´ andar andar llamada f que toma un par´ ametro ametro int, use la siguiente sintaxis para su prototipo: void f ( int )
attribute
(( cdecl ));
GCC tambi´ en en soporta la convenci´ convenci´ on o n de llamado est´ andar . andar . La funci´on o n de arriba se podr´ podr´ıa declarar declarar para usar esta convenci´ convenci´ on on reemplazand reemplazandoo cdecl con stdcall. La diferencia entre stdcall y cdecl es que stdcall requiere que la subrutina quite los par´ametros ametros de la pila (como lo hace la convenci´ on on de llamado de Pascal). As´ As´ı, la convenci´ convenci´ on on stdcall s´ olo olo se puede usar con 4
El compilador de C de Watcom es un ejemplo de uno que no usa la convenci´ on on de llamado est´ andar andar por omisi´ on. on. Vea el c´ odigo fuente de ejemplo para Watcom para los odigo detalles
4.7. INTERFAZANDO INTERFAZANDO ENSAMBLADOR ENSAMBLADOR CON C
87
funciones que tomen un n´umero umero fijo de par´ametros ametros (ejm. unas que no sean como printf y scanf). GCC tambi´en en soporta sop orta un atributo a tributo adicional adicio nal llamado lla mado regparam que le dice al compilador que use los registros para pasar hasta 3 argumentos enteros a su funci´on on en lugar de usar la pila. Este es un tipo com´un un de optimizaci´ optimizaci´ on on que soportan muchos compiladores. Borland y Microsoft usan una sintaxis com´ un para declarar convencioun nes de llamado. Ellas a˜ naden a las palabras reservadas cdec naden cdecl l stdc stdcal all l a C. Estas palabras reservadas act´ uan como modificadoras de funciones y uan aparecen inmediatamente antes nombre de la funci´on on en un prototipo. Por ejemplo, la funci´on on f de arriba se podr p odr´´ıa definir para Borland y Microsoft as´ı: void void
cdec cdecll f ( int );
Hay ventajas y desventajas para cada convenci´on on de llamado. La principal ventaja de la convenci´on on cdecl es que es simple y muy flexible. Se puede usar para cualquier tipo de funci´on on de C y cualquier compilador de C. Usar otras convenciones puede limitar la portabilidad de la subrutina. Su principal desventaja es que puede ser m´as as lenta que alguna de las otras y usa m´as as memoria (ya que cada vez que se invoca de la funci´on on requiere c´odigo odigo para quitar los par´ ametros ametros de la pila). Las ventajas de la convenci´on on stdcall es que usa menos memoria que requiere limpiar la pila despu´ despu´ es es de la instrucci´ instrucci´ on on CALL. Su cdecl. No se requiere principal desventaja es que no se puede usar con funciones que tengan un n´umero umero variable de argumentos. La ventaja de usar una convenci´on on que use registros para pasar enteros es la velocidad. La principal desventaja es que la convenci´on on es m´as as compleja. compleja. Algunos par´ametros ametros pueden estar en los registros y otros en la pila.
4.7. 4.7.7. 7.
Ejem Ejempl plos os
El siguiente es un ejemplo que muestra c´ omo una rutina de ensamblador omo se puede interfasar con un programa de C (observe que este programa no usa el programa esqueleto de ensamblador (Figura 1.7 1.7)) o el m´ odulo odulo driver.c) main5.c #include
/ proto prototipo tipo para para la rutina rutina en ensamb ensamblad lador or / ) attribute (( cdecl )); void calc sum( int , int
∗
int main( void )
{
∗
∗
CAP ´ITULO 4. SUBPROGR SUBPROGRAMAS AMAS
88 int n, sum;
printf printf (”Sumar (”Sumar enteros hasta: hasta: ”); scanf(” scanf(” %d”, %d”, &n); &n); calc sum(n, sum(n, &sum); &sum); printf printf (”Sum is %d n”, sum); return 0;
\
} main5.c 1 2 3 4 5 6 7 8 9 10 11 12 13
; ; ; ; ; ; ; ; ; ; ; ; ;
sub5.asm subrout subroutinea inea _calc_su _calc_sum m halla la suma de los enteros de 1 hasta n Parametros: Parametros: n - hasta donde o ´ nde suma sumar r (en (en [ebp [ebp + 8]) 8]) sump sump - apun apunta tado dor r a un int int para para alma almace cena nar r sum sum (en (en [ebp [ebp + 12]) 12]) pseudoc pseudoc´ odig o ´ digo o en C: void void calc calc_s _sum um( ( int int n, int * sump sump ) { int i, i, su sum = 0; for( i= i=1; i <= <= n; n; i+ i++ ) sum += i; *sump = sum; }
14 15 16 17 18 19 20 21 22
segment .text globa global l _c _calc alc_s _sum um ; ; local local variable variable: : ; sum at [ebp-4] _calc_sum: enter 4,0 push eb ebx
; hace espacio para sum en la pila ; IMPORTANTE!
23 24 25 26 27 28 29
mov dword [ebp-4],0 dump ump_stac tack 1, 2, 4 mov ecx, 1 for_loop: cmp ecx, [ebp+8] jnle end_for
; sum = 0 ; impri prime la pila ila desd esde ebp ebp-8 hasta ebp+1 p+16 ; ecx es i en el pseudocodigo o ´digo ; cmp i y n ; si no i <= n, sale
30 31
add
[ebp-4], ecx
; sum += i
4.7. INTERFAZANDO INTERFAZANDO ENSAMBLADOR ENSAMBLADOR CON C
89
Sumar Sumar enteros enteros hasta: hasta: 10 Stack Dump # 1 EB EBP P = BFFFF BFFFFB70 B70 ESP = BFFFF BFFFFB68 B68 +16 +16 BFFF BFFFFB FB80 80 0804 080499 99EC EC +12 +12 BFFF BFFFFB FB7C 7C BFFF BFFFFB FB80 80 +8 BFFF BFFFFB FB78 78 0000 000000 000A 0A +4 BFFF BFFFFB FB74 74 0804 080485 8501 01 +0 BFFF BFFFFB FB70 70 BFFF BFFFFB FB88 88 -4 BFFF BFFFFB FB6C 6C 0000 000000 0000 00 -8 BFFF BFFFFB FB68 68 4010 401064 648C 8C Sum is 55
Figura 4.13: Muestra de la ejecuci´on on del programa sub5
32 33
inc jmp
ecx short for_loop
mov mov mov
ebx, [ebp+12] eax, [ebp-4] [ebx], eax
; ebx = sump ; eax = sum
pop leave ret
ebx
; restaura ebx
34 35 36 37 38
end_for:
39 40 41 42
sub5.asm
¿Por ¿Po r qu´e la l´ınea ın ea 22 de sub5.asm es importante? Porque la convenci´on on de llamado de C requiere que el valor de EBX no se modifique por la funci´ on llamada. Si esto no se hace muy probable que el programa no trabaje on correctamente. La l´ınea ın ea 25 demuestr demuestraa como trabaja el macro dump stack. Recuerde que el primer par´ametro ametro es s´olo olo una etiqueta num´ erica, erica, y el segundo y tercero determinan cu´antas antas palabras dobles se muestran antes y despu´ es es de EBP respectiv respectivamen amente. te. La Figura Figura 4.13 muestra un ejemplo de la ejecuci´ on o n del programa. Para este volcado, uno puede ver que la direcci´on on de la palabra doble que almacena la suma es BFFFFB80 (en EBP + 12); el n´umero umero a sumar es 0000000A (en EBP + 8); la direcci´ on de retorno para la rutina es on 08048501 0804850 1 (en EBP + 4); el valor guardado guardado de EBP es BFFFFB88 (en EBP); el valor de la variable local es 0 en (EBP- 4); y finalmente el valor guardado de EBX es 4010648C (en EBP- 8).
CAP ´ITULO 4. SUBPROGR SUBPROGRAMAS AMAS
90
La funci´on on calc sum podr´ıa ıa ser rescrita para devolver la suma como un valor de retorno en lugar de usar un par´ametro ametro apuntador. Ya que la suma es un valor entero, la suma se podr p odr´´ıa dejar en el registro EAX. La l´ınea 11 del archivo main5.c deber´ıa ıa ser cambiada por: sum = calc sum(n); Tambi´ en, en, el prototipo protot ipo de calc sum deber´ıa ıa ser alterado. altera do. A continuaci´ co ntinuaci´on, on, el c´odigo odigo modificado modificado en ensamblador ensamblador..
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
sub6.asm ; subrout subroutina ina _calc_su _calc_sum m ; hall halla a la suma suma de los los ente entero ros s de 1 hast hasta a n ; Par´ ametros: ametros: ; n - hasta donde o ´ nde se suma suma (en (en [ebp [ebp + 8]) 8]) ; Va Valor lor de re retor torno no: : ; la suma ; pseudoc pseudoc´ odig o ´ digo o en C: ; int int calc calc_s _sum um( ( int int n ) ; { ; int i, i, su sum = 0; ; for( i= i=1; i <= <= n; n; i+ i++ ) ; sum += i; ; return sum; ; } segment .text globa global l _c _calc alc_s _sum um ; ; local local variable variable: : ; sum at [ebp-4] _calc_sum: enter 4,0 ; hace espacio en la pila para sum
22 23 24 25 26 27
mov mov for_loop: cmp jnle
dword [ebp-4],0 ecx, 1
; sum = 0 ; ecx es i en el pseudocodigo o ´digo
ecx, [ebp+8] end_for
; cmp i and n ; si no i <= n, sale
[ebp-4], ecx ecx short for_loop
; sum += i
28 29 30 31
add inc jmp
32 33
end_for:
4.8. SUBPROGRAMAS SUBPROGRAMAS REENTRANTES REENTRANTES Y RECURSIVOS RECURSIVOS 1 2
91
segment .data format db "%d", 0
3 4 5 6 7 8 9 10 11
segment .text ... lea eax, [ebp-16] push eax push push dwor dword d form format at cal call _scanf anf add esp, 8 ...
Figura 4.14: Llamando scanf desde ensamblador 34
mov
eax, [ebp-4]
; eax = sum
35 36 37
leave ret
4.7.8. 4.7.8.
sub6.asm
Llamando Llamando funcione funcioness de C desde desde ensam ensamblad blador or
Una gran ventaja de interfasar C y ensamblador es que le permite al c´odigo odigo de ensamblador acceder a la gran biblioteca de C y usar funciones escritas por el usuario. Por ejemplo, si uno desea llamar la funci´on on scanf para leer un entero del teclado. La Figura 4.14 muestra el c´odigo odigo que hace esto. Un punto muy importante para recordar es que scanf sigue la convenci´ on de llamado de C normalizada. Esto significa que preserva los valores de on los registros EBX, ESI y EDI; sin embargo los registros EAX, ECX y EDX se pueden modificar. De hecho, EAX definitivamente ser´a cambiado, ya que ´el el conte co ntendr ndr´ a´ el valor de retorno del llamado a scanf. Para otros ejemplos del uso de interfaces con C, vea el c´odigo odigo en asm io.asm que fue usado para crear asm io.obj.
4.8. 4.8.
Subpro Sub progra gramas mas reen reentra trant ntes es y recurs recursiv ivos os
Un programa reentrante debe satisfacer las siguientes propiedades: No debe modificar ninguna instrucci´on on del programa. En un lenguaje de alto nivel esto podr po dr´´ıa ser dif´ dif´ıcil, pero en ensamblador ensambla dor no es dif´ dif´ıcil para un programa intentar modificar su propio c´ odigo. odigo. Por ejemplo:
CAP ´ITULO 4. SUBPROGR SUBPROGRAMAS AMAS
92 mov add
word [cs:\$+7], 5 ax, 2
; copia 5 en la palabra 7 ; bytes bytes adelant adelante e ; la instruccion o ´n anterior anterior ; cambia 2 por 5
Este c´odigo odigo traba jar´ jar´ıa en modo real, pero p ero en sistemas operativos op erativos con modo protegido el segmento de c´ odigo odigo est´ a marcado de solo lectura. Cuando se ejecute la primera l´ınea, ınea, el programa se abortar´ a en estos sistemas. Esto es una mala forma de programar por muchas razones. Es confuso, dif´ dif´ıcil de mantener y no permite p ermite compartir el c´odigo odigo (vea m´ as as adelante). No debe modificar datos globales (tal como los datos que est´ an an en el segmento data y bss). Todas las variables son almacenadas en la pila. Hay varias ventajas de escribir c´odigo odigo reentrante: Un subprograma reentrante se puede llamar recursivamente. Un programa reentrante puede compartirse en m´ ultiples ultiples procesos. procesos. En muchos sistemas operativos multitarea, si est´ an an ejecutandose varias instancias solo una copia del c´ odigo odigo est´ a en memoria. Las bibliotecas compartidas y DLL (Dinamic (Dinamic Link Libraries) Libraries ) usan esta idea tambi´en. en. Los subprogram subprogramas as reentran reentrantes tes trabajan mucho mucho mejor en programas programas 5 multihilo Windows Windows 9x/NT y la may mayor or´´ıa de los sistemas sistemas operativos operativos tipo UNIX (Solaris, Linux, etc.) soportan programas multihilo.
4.8.1. 4.8.1.
Subprog Sub programa ramass recursiv recursivos os
Estos tipos ti pos de subprogramas se invocan as´ as´ı mismos. La recursi´ on on puede ser directa o indirecta . La recursi´on on directa ocurre cuando un subprogram subprograma, a, digamos foo, se invoca as´ as´ı mismo mis mo dentro de ntro del cuerpo de foo. La recursi´on on indirecta ocurre o curre cuando un subprograma no se llama as´ as´ı mismo mi smo directamente, pero otro subprograma lo llama. Por ejemplo, el subprograma foo po dr´ıa llamar a bar y bar podr po dr´´ıa llamar lla mar a foo. Los programas recursivos deben tener una condici´ on de terminaci´ on . Cuando esta condici´on on es verdadera, no se necesita hacer m´as as llamadas. Si la rutina recursiva no tiene una condici´on on de terminaci´ on o n o la condici´on on nunca se vuelve verdadera, la recursi´on on nunca termina (muy parecido a un bucle infinito). La Figura 4.15 muestra una funci´on on que calcula el factorial recursivamente. Puede ser llamado desde C con: 5
un programa multihilo tiene varios hilos de ejecuci´ on. on. Esto Es to es, el programa pro grama en s´ı mismo mism o es multitarea.
4.8. SUBPROGRAMAS SUBPROGRAMAS REENTRANTES REENTRANTES Y RECURSIVOS RECURSIVOS
1 2 3 4 5
; find finds s n! segment .text global global _fact _fact _fact: ente enter r 0,0 0,0
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
mov cmp jbe dec push call pop mul jmp jmp term_cond: mov end_fact: leave ret
eax, [ebp+8] eax, 1 term_cond eax eax _fact ecx dword [e [ebp+8] short end_f d_fact
; eax = n ; si n <= 1, termina
; eax = fact(n-1) ; respuesta en eax ; ed edx:eax = eax * [ebp+8]
eax, 1
Figura 4.15: Funci´ on on factorial recursiva
marc ma rcoo n=3 n=3
marc ma rcoo n=2 n=2
marc ma rcoo n=1 n=1
n(3) Dire Direcc cci´ i´ on on de retorno EBP guardado n(2) Dire Direcc cci´ i´ on on de retorno EBP guardado n(1) Dire Direcc cci´ i´ on on de retorno EBP guardado
Figura 4.16: Marco de la pila para la funici´ on on factorial factorial
93
CAP ´ITULO 4. SUBPROGR SUBPROGRAMAS AMAS
94 void f ( int x )
{
int i ; for ( i=0; i < x; i++ )
printf printf (” %d n”, i); f( i ); );
\
}
{
} Figura 4.17: Otro ejemplo (versi´on on de C)
x = fact(3);
/ find find 3! /
∗
∗
La Figura 4.16 muestra c´omo o mo se ve la pila en el punto m´ as as profundo del llamado de la funci´on on anterior. Las Figuras 4.17 y 4.18 muestran otro ejemplo recursivo m´as as complicado en C y en ensamblador respectivamente. ¿Cu´ al al es la salida para f(3)? Observe que la instrucci´on on ENTER crea un nuevo i en la pila para cada llamado recursivo. As´ı cada instancia recursiva recursiva de f tiene su propia variable independiente i. Definiendo i como una palabra doble en el segmento data no trabajar´ a igual.
4.8. 4.8.2. 2.
Revi Revisi si´ ´ on de tipos de variables seg´un on un su alcance en C
C suministra varios tipos de almacenamiento para las variables. global Estas variables est´an an definidas fuera de cualquier funci´on o n y est´an an almacenadas en lugares fijos de memoria (en los segmentos data o on bss) y existen desde el inicio hasta el final del programa. Por omisi´on ellas pueden ser accedidas por cualquier funci´on on en el programa; sin embargo, si ellas est´an an declaradas como static, solo las funciones en el mismo m´ odulo odulo pueden acceder a ellas (en t´erminos erminos de ensamblador la etiqueta es interna, no externa). static Estas son variables locales de una funci´on on que son declaradas static (desafortun (desafortunadamen adamente, te, C usa la palabra palabra reserv reservada ada static para dos prop´ ositos ositos diferente diferentes). s). Estas variables ariables se almacenan almacenan tambi´ tambi´ en en en lugares fijos de memoria (en data o bss), pero solo se pueden acceder directamente en las funciones en donde ellas se definieron. automatic Este es el tipo por omisi´on on para una variable definida dentro de una funci´on. on. Estas variables son colocadas en la pila cuando la funci´on on en la cual est´ an definidas se invocada y quitadas cuando la funci´ an on on retorna. As´ı, ı, ellas ella s no tienen un lugar fijo en memoria.
4.8. SUBPROGRAMAS SUBPROGRAMAS REENTRANTES REENTRANTES Y RECURSIVOS RECURSIVOS
1 2 3 4 5 6 7 8 9
%d %defi efine ne i ebp-4 ebp-4 %define x ebp+8 ; macros utiles u ´tiles segment .data format db "%d", 10, 0 ; 10 = ’\n’ segment .text global global _f extern _printf _f: enter 4,0 ; toma espacio en la pila para i
10 11 12
mov
dword [i], 0
; i = 0
mov cmp jnl
eax, [i] eax, [x] quit
; es i < x?
push pus push cal call add
eax format mat _print intf esp, 8
; llama printf
push call pop
dword [i] _f eax
; llama f
inc jmp
dword [i] short lp
; i++
lp:
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
quit: leave ret
Figura 4.18: Otro ejemplo (versi´on on de ensamblador)
95
CAP ´ITULO 4. SUBPROGR SUBPROGRAMAS AMAS
96
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 direcci´on o n de la variable se usa en cualquier parte del programa la variable no ser´ a de este tipo (ya que los registros no tienen direcci´ on). on). Tambi´en, en, solo tipos tipo s enteros entero s pueden pu eden ser valores tipo register. Tipos estructurados no lo pueden ser, ¡ellos no caben en un registro! Los compiladores de C a menudo convertir´an an variables normales automatic en variables register sin ning´ un un aviso al programador. volatile Esta palabra clave le dice al compilador que el valor en la variable puede cambiar en cualquier momento. Esto significa que el compilador no puede hacer ninguna suposici´ on on sobre cu´ando ando se modifica la variable. A menudo un compilador podr´ podr´ıa almacenar el valor de una variable en un registro temporalmente y usar el registro en vez de la variable en una parte del c´odigo. odigo. No se pueden hacer este tipo de optimizaciones con variables volatile. Un ejemplo ejemplo com´ un un de una variable volatile volatile podr p odr´´ıa ser una que se alterara alterara por dos hilos de un programa programa multihilo. Considere el siguiente c´ odigo: odigo: x = 10; y = 20; z = x; Si x pudiera ser alterada por otro hilo, es posible que otro hilo cambie ıne as 1 y 3 as´ı que z podr po dr´´ıa no n o ser 10. Sin embargo, e mbargo, si x no x entre las l´ıneas fue declarada volatile, el compilador podr p odr´´ıa asumir que x no cambia y fija 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. 5.1.
Intr Introdu oducc cci´ i´ on on
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 n´ umero de bytes de memoria para almacenarlo. Por estas propiedaumero des, los arreglos permiten un acceso eficiente de los datos por su posici´on on (o ´ındice) en el arreglo. La direcci´ on de cualquier elemento se puede calcular on conociendo tres hechos: La direcci´on on del primer elemento del arreglo. El n´ umero de bytes de cada elemento. umero El ´ındice del elemento. Es conveniente conveniente considerar el ´ındice del primer elemento como 0 (tal como C). Es posible usar otros valores valores para el primer ´ındice, ındice, pero esto complica los c´ alculos. alculos.
5.1.1. 5.1.1.
Defini Definirr arreg arreglos los
Definir arreglos en los segmentos data y bss Para definir un arreglo con valor inicial en el segmento data, use las directivas normales: db, dw, etc. NASM tambi´ t ambi´ en en suministra la directiva directiva llamada TIMES que se puede usar para repetir una instrucci´on on muchas veces sin tener que duplicar las instrucciones a mano. La Figura 5.1 muestra varios ejemplos de estos. Para definir un arreglo sin valor inicial en el segmento bss, use las directivas resb, resw, etc. Recuerde que estas directivas tienen un operando 97
98 1 2 3 4 5 6 7 8 9 10 11
CAP ´ITULO 5. ARREGLO ARREGLOS S
segment segment .data .data ; de defin fine e un ar arreg reglo lo de 10 pl plabr abras as dobles dobles con valor valores es ; inicial iniciales es de 1,2,.., 1,2,..,10 10 a1 dd 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ; defi define ne un arre arregl glo o de 10 pala palabr bras as inic inicad adas as toda todas s con con 0 a2 dw 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ; lo mi mism smo o de antes antes us usan ando do TI TIMES MES a3 times 10 dw 0 ; defi define ne un arre arregl glo o de byte bytes s con con 200 200 cero ceros s y lueg luego o 100 100 con con unos unos a4 times 200 db 0 times 100 db 1
12 13 14 15 16 17
segment segment .bss ; de defin fine e un ar arreg reglo lo de 10 pa palab labra ras s doble dobles s sin valor valor inici inicial al a5 resd 10 ; de defin fine e un ar arreg reglo lo de 100 pa palab labra ras s doble dobles s sin valor valor inici inicial al a6 resw 100
Figura 5.1: Definir arreglos
que especifica cu´antas antas unidades de memoria reservar. La Figura 5.1 muestra tambi´ en en ejemplos de este tipo de definiciones
Definir arreglos como variables locales en la pila No hay una manera directa de definir una arreglo como variable local en la pila. Como antes, uno calcula el total de bytes necesarios para todas las variables locales, incluidos los arreglos, y resta esto de ESP (o directamente usando la instrucci´on on ENTER). Por ejemplo, si una funci´on on necesita una variable caracter, dos enteros y 50 elementos de un arreglo de palabras, uno necesitar´ necesitar´ıa 1 + 2 + 50 = 109 bytes. Sin embargo, el n´ umero umero restado de ESP deber deb er´´ıa ser un m´ultiplo ultiplo de cuatro (112 en este caso) para que ESP est´e en el l´ımite de una un a palabra pal abra doble. Uno podr´ p odr´ıa ıa formar f ormar las variables dentro de estos est os 109 bytes de varias maneras. La Figura 5.2 muestra dos maneras posibles. La parte sin uso del primer ordenamiento est´a para dejar las palabras dobles en los l´ımites de palabras dobles para optimizar la velocidad del acceso a la memoria.
´ 5.1. INTRODUC INTRODUCCI CI ON EBP - 1
99
char no usado dword 1 dword 2
EBP - 8 EBP - 12
word array
word array
EBP EBP EBP EBP EBP EBP
-
100 104 104 108 108 109
EBP - 112
dwor dword d1 dwor dword d2 char no usado
Figura 5.2: Disposici´ on on de la pila
5.1.2. 5.1.2.
Acced Acceder er a eleme element ntos os de los arreg arreglos los
No existe el operador [ ] en el lenguaje ensamblador como en C. Para acceder a un elemento del arreglo, se debe calcular su direcci´ on. on. Considere Considere las dos definiciones siguientes de arreglos: array1 array2
db dw
5, 4, 3, 2, 1 5, 4, 3, 2, 1
; arreglo de bytes ; arreglo de palabras
A continuaci´ on, algunos ejemplos que utilizan estos arreglos. on, 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 es array2[2]!) array2[3] = ax ax = ??
En la l´ınea 5, se referencia referenci a el elemento 1 del arreglo, arregl o, no el segundo. se gundo. ¿Por qu´e? e? Las palabras son unidades de 2 bytes, para moverse al siguiente elemento del arreglo uno se debe desplazar 2 bytes adelante, no uno. La l´ınea 7 leer´ a un byte del primer elemento y uno del segundo. En C, el compilador mira el tipo de apuntador para determinar cu´ antos bytes mover en una expresi´on antos on aritm´ aritm´etica etica para que el programador programador no tenga que hacerla. hacerla. Sin embargo embargo en ensamblador ensamblador es ´el el el que calcula calcula el tama˜ no de los elementos del arreglo cuando se mueve de elemento en elemento. La Figura 5.3 muestra un fragmento de c´odigo odigo que suma todos los elementos de array1 del ejemplo anterior. En la l a l´ınea ınea 7, a AX se le suma DX. ¿Por qu´e no AL? Primero, los dos operandos operandos de la instrucci´ instrucci´ on on ADD deben
CAP ´ITULO 5. ARREGLO ARREGLOS S
100 1 2 3 4 5
mov mov mov mov
ebx, array1 dx, 0 ah, 0 ecx, 5
; ebx = direccion o ´n de array array1 1 ; dx almacenara ´ sum ; ?
mov add inc loop
al, [ebx] dx, ax ebx lp
; al = *ebx ; dx += ax (not al!) ; bx++
lp:
6 7 8 9
Figura 5.3: Sumar elementos de un arreglo (Versi´ on on 1) 1 2 3 4
6 7
9 10
ebx, array1 dx, 0 ecx, 5
; ebx = direccion o ´n de array array1 1 ; dx almacenara ´ sum
add jnc inc
dl, [ebx] next dh
; dl += *ebx ; if no hay carry vaya a next ; inc dh
inc loop
ebx lp
; bx++
lp:
5
8
mov mov mov
next:
Figura 5.4: Sumar elementos de un arreglo (Versi´ on on 2) ser del mismo tama˜ no. Segundo, es probable que al sumar bytes y llegueno. mos a un resultado que no quepa en un byte. Usando DX, se permite una suma hasta 65.535. Sin embargo es importante ver que se est´a sumando AH tambi´en. en. Esta es la raz´ on por la cual se fija AH a cero. 1 en la l´ınea 3. on Las Figuras 5.4 y 5.5 muestran dos alternativas para calcular la suma. Las l´ıneas ınea s en it´alica alica reemplazan las l´ıneas ıneas 6 y 7 de la Figura 5.3.
5.1.3.
Direccionamiento indirecto m´ as as av avanzado
No es sorprendente que se use direccionamiento indirecto con arreglos. La forma m´as as general de una referencia indirecta memoria es [ reg base base + factor * factor *reg reg ´ ındice + constante ] 1
Fijando AH a cero c ero se asume impl´ıcitamente ıcitamente que AL es un n´ umero sin signo. Si es con signo, la acci´ on on apropiada apro piada ser´ ser´ıa insertar ins ertar una instrucci´ instrucc i´ on on CBW entre las l as l´ıneas 6 y 7
´ 5.1. INTRODUC INTRODUCCI CI ON 1 2 3 4 5 6 7 8
101
mov mov mov
ebx, array1 dx, 0 ecx, 5
; ebx = direccion o ´n de ; dx almacenara ´ sum
array1 ay1
add adc inc loop
dl, [ebx] dh, 0 ebx lp
; dl += *ebx ; dh += bandera de carry + 0 ; bx++
lp:
Figura 5.5: Sumar elementos de un arreglo (Versi´ on on 3) donde: reg base es uno de los registros EAX, EBX, ECX, EDX, EBP, ESP, ESI o EDI. factor es 1, 2, 4 o 8. (Es 1, si el factor se omite) index reg es uno de estos registros EAX, EBX, ECX, EDX, EBP, ESI, EDI. (observe que ESP no est´a en la lista.) (o expresi´on). on).
5.1. 5.1.4. 4.
Ejem Ejempl plo o
Se pres presen enta ta un ejempl ejemploo que que usa usa un arre arreglo glo y se pasa pasa a la func funci´ i´ on. on. Usa el programa array1c.c (listado abajo) como driver y no el programa driver.c. 1 2
%define %define ARRAY_S ARRAY_SIZE IZE 100 %define %define NEW_LIN NEW_LINE E 10
array1.asm
3 4 5 6 7 8 9
segment .data FirstMsg Prompt SecondMsg ThirdMsg InputFormat
db db db db db
segment segment .bss .bss array
resd ARRAY_SIZE
segment .text extern extern
_puts, _puts, _printf, _printf, _scanf, _scanf, _dump_li _dump_line ne
"Primeros 10 elementos del arreglo", 0 "Ingrese el ı ındic ´ndice e del elemen elemento to a mostr mostrar: ar: ", 0 "Elemento %d es %d", NEW_LINE, 0 "Elementos 20 hasta 29 del arreglo", 0 "%d", 0
10 11 12 13 14 15
CAP ´ITULO 5. ARREGLO ARREGLOS S
102 16 17 18 19 20
globa global l _asm_main: enter push push
_a _asm_ sm_ma main in 4,0 ebx esi
; variable local en
EBP - 4
21 22
; Se inic inicia ia el arre arregl glo o con con 100, 100, 99, 99, 98, 98, 97, 97, ... ...
23 24 25 26 27 28 29
mov mov init_loop: mov add loop
ecx, ARRAY_SIZE ebx, array [ebx], ecx ebx, 4 init_loop
30 31 32 33
push call pop
dword FirstMsg _puts ecx
push push call
dword 10 dword array _print_array
add
esp, 8
; imprime FirstMsg
34 35 36 37 38 39
; imprime los 10 primeros ; element elementos os del arreglo arreglo
40 41 42 43 44 45
; Le preg pregun unta ta al usua usuari rio o el ındice ı ´ndice del element elemento o Prompt_loop: push dword Prompt call _printf pop ecx
46 47 48 49 50 51 52 53
lea push push push call add cmp je
eax, [ebp-4] ; eax = direccion o ´n de la variab variable le local local eax dwor dword d Inpu InputF tFor orma mat t _scanf esp, 8 eax, 1 ; eax = valor de retorno de scanf InputOK
call
_dump_line
54 55 56 57
jmp
; vacıa ı ´a el resto de la l´ ınea ı nea y vuel vuelve ve a ; comenzar comenzar Prompt_loop ; si la entrada no es valida a´lida
´ 5.1. INTRODUC INTRODUCCI CI ON
103
58 59
InputOK:
60 61 62 63 64 65
mov push push push call add
esi, [ebp-4] dwo dword [array + 4*e 4*esi] esi dword SecondMsg ; imprime el valor del elemento _printf esp, 12
push call pop
dword ThirdMsg _puts ecx
push push call call add
dword 10 dword array + 20*4 _pri _print nt_a _arr rray ay esp, 8
pop pop mov leave ret
esi ebx eax, 0
66 67 68 69
; imprime los elementos
20 a 29
70 71 72 73 74
; direccion o ´n de array array[20 [20] ]
75 76 77 78 79 80
; retorna a C
81 82 83 84 85 86 87 88 89 90
; ; ; ; ; ; ; ; ;
rutina rutina _print_ _print_arra array y Ru Ruti tina na ll llama amabl ble e de desde sde C que impri imprime me el eleme ement ntos os de arre arreglo glos s de palab palabras ras do dobl bles es co como mo en enter teros os co con n signo signo Pr Prot ototi otipo po de C: void void prin print_ t_ar arra ray( y( cons const t int int * a, int int n); n); Par´ Par´ ametros: ametros: a - Apun Apunta tado dor r al arre arregl glo o a impr imprim imir ir (en (en ebp+ ebp+8 8 en la pila pila) ) n - nu umero ´ mero de en enter teros os a im impri primi mir r (e (en n eb ebp+ p+12 12 en la pila) pila)
91 92 93
segment .data Outp Output utFo Form rmat at
db
"%-5 "%-5d d %5d" %5d", , NEW_ NEW_LI LINE NE, , 0
94 95 96 97 98 99
segment .text global global _print_array: enter push
_print_a _print_array rray 0,0 esi
CAP ´ITULO 5. ARREGLO ARREGLOS S
104 push
100
ebx
101 102 103 104 105 106
xor mov mov print_loop: push
esi, esi ecx, [ebp+12] ebx, [ebp+8]
; esi = 0 ; ecx = n ; ebx = direccion o ´n del del arreg arreglo lo
ecx
; ¡printf podrıa ı ´a cambi cambiar ar ecx! ecx!
push push push push call add
dword [ebx + 4*esi] esi dwor dword d Outp Output utFo Form rmat at _printf esp, 12
; empuja a la pila array[esi]
inc pop loop oop
esi ecx print_ nt_loop
pop pop leave ret
ebx esi
107 108 109 110 111 112
; quita los parametros a ´metros (¡deja (¡deja ecx!) ecx!)
113 114 115 116 117 118 119 120 121
array1.asm
array1c.c #include int asm main( void ); void dump line( void ); int main()
{
int ret status ;
ret statu statuss = asm asm main(); main(); return ret status ;
} /
∗ dump line ∗ funci´ on dump Vac´ıa todos los caracter caracteres es ∗ Vac´ ∗/
void dump line()
de la l´ınea del b´ ufer de entrada entrada
´ 5.1. INTRODUC INTRODUCCI CI ON
{
105
int ch; while( (ch = getchar()) != EOF && ch != ’ n’)
\
/ cuerp cue rpo o vac va c´ıo / ;
∗
}
∗
array1c.c
Revisi´ on on de la instrucci´ on on LEA La instrucci´on on LEA se puede usar para otros prop´ositos ositos que s´olo olo calcular calcular direcciones. Un uso com´ un un es para c´alculos alculos r´ apidos. Considere lo siguiente: apidos. lea
ebx, [4*eax + eax]
Esto efectivamente almacena el valor de 5 EAX en EBX. Usando LEA para hacer esto, es m´as as f´acil acil y r´ apido apido que usar MUL. Sin embargo, uno debe tener en cuenta que la expresi´on on dentro de los l os par´entesis entesis cuadrados cuadrad os debe ser una direcci´ on on indirecta correcta. As´ As´ı por ejemplo, esta instrucci´ on on no se puede usar para multiplicar por 6 r´apidamente. apidamente.
×
5.1.5. 5.1.5.
Arreglos Arreglos multid multidimie imiensio nsionale naless
Un arreglo multidimensional no es realmente muy diferente que los arreglos unidimensionales ya discutidos. De hecho, est´an an representados en memoria tal como un arreglo unidimensional. Arreglos bidimensionales No debe sorprender que el arreglo multidimiensional m´as as elemental es un arreglo bidimensional. Un arreglo bidimensional se presenta a menudo como una malla de elementos. Cada elemento est´a identificado por un par de ´ındices. ındices . Por convenci´on, on, el primer primer ´ındice es identificad identificadoo con la fila del elemento y el segundo ´ındice ındice es la columna. Considere un arreglo con tres filas y dos columnas definidas como: int a [3][2];
El compilador de C reservar reservar´´ıa espacio para un arreglo de 6 (= 2 3) enteros y ordena los elementos como sigue:
×
´Indice Elem Elemen ento to
0 a[0] a[0][0 [0]]
1 a[0] a[0][1 [1]]
2 a[1] a[1][0 [0]]
3 a[1] a[1][1 [1]]
4 a[2] a[2][0 [0]]
5 a[2] a[2][1 [1]]
La tabla intenta mostrar c´omo omo el elemento referenciado como a[0][0] es
CAP ´ITULO 5. ARREGLO ARREGLOS S
106 1 2 3 4 5
mov sal add mov mov
eax, eax, eax, eax, [ebp
[ebp 1 [ebp [eb [ebp + - 52],
44] 48] 4*e 4*eax - 40] 40] eax
; ; ; ; ;
ebp - 44 es el sitio de i multiplica i por 2 add j eb ebp - 40 es es la di direccion o´n a[0][0] a[0][0] Almacena el resultado en ebp - 52
Figura Figura 5.6: Ensamblador Ensamblador para x = a[i ][ j ] almacenado en el comienzo de los 6 elementos de un arreglo unidimensional. El elemento a[0][l] se almacena en la siguiente posici´on on (´ındi ın dice ce 1) y as´ı susu cesivamente. Cada fila del arreglo bidimensional se almacena contiguamente en memoria. El ultimo u´ltimo elemento de una fila es seguido por el primer elemento de la pr´oxima oxima fila. Esto es conocido como la representaci´ on on rowwise del arreglo y es como un compilador de C/C++ podr´ıa ıa representar el arreglo. ¿C´ omo el compilador determina d´ omo onde onde aparece a[i][j] en la representaci´ on on rowwise? rowwise ? Una f´ormula ormula elemental calcular´ a el ´ındice de i y j. La f´ ormula ormula en este caso es 2i + j . No N o es e s dif´ d if´ıcil ıci l ver ve r c´omo omo se deriva esta f´ormula. ormula. Cada fila es de dos elementos; as´ as´ı, el primer elemento de la fila i est´ a en la posici´on on 2i . Entonces la posici´ on on de la columna j se encuentra sumando j a 2i. Este an´ alisis alisi s tambi´en en muestra c´ omo omo la f´ ormula se generaliza para un arreglo con ormula ormula no depende del n´ umero umero de N columnas. N i + j . Observe que la f´ormula filas. Como un ejemplo, veamos c´ omo omo compila gcc el siguiente c´ odigo odigo (usando el arreglo a definido antes):
×
x = a[i ][ j ]; La Figura 5.6 muestra c´omo omo el ensamblador trabaja traba ja esto, As´ As´ı, el compilador esencialmente convierte el c´odigo odigo a: x = (&a[0][0] + 2 i + j);
∗
∗
y de hecho, hecho, el programador programador podr´ podr´ıa escribir escribir de esta manera con los mismos resultados. No hay nada m´agico agico sobre la escogencia de la representaci´ on on rowwise del arreglo. Una representaci´on on columnwise podr po dr´´ıa traba jar bien: Index Elem lement ent
0 a[0] a[0][0 [0]]
1 a[1] a[1][0 [0]]
2 a[2] a[2][0 [0]]
3 a[0] a[0][1 [1]]
4 a[1] a[1][1 [1]]
5 a[2] a[2][1 [1]]
En la representaci´on on columnwise, columnwise , cada columna se almacena contigua. El elemento [i][j] se almacenan en la posici´on on i + 3 j . Otros lenguajes (FORTRAN, por ejemplo) usan la representaci´on on columnwise. columnwise. Esto es importante cuando se interfaza el c´odigo odigo con varios lenguajes.
´ 5.1. INTRODUC INTRODUCCI CI ON
107
Dimensiones mayores que dos Para las dimensiones mayores de 2, se aplica la misma idea b´asica. asica. Considere sidere el arreglo arreglo tridimensio tridimensional: nal: int b [4][3][2];
Este arreglo podr´ıa ıa ser almacenado como si fueran cuatro arreglos bidimensionales cada uno de tama˜ no no [3][2] consecutivamente en memoria. La tabla de abajo muestra como comienza ´el: el: Index Elem Elemen entt Index Elem Elemen entt
0 b[0] b[0][0] [0][0] [0] 6 b[1] b[1][0] [0][0] [0]
1 b[0][ b[0][0][ 0][1] 1] 7 b[1][ b[1][0][ 0][1] 1]
2 b[0][ b[0][1][ 1][0] 0] 8 b[1][ b[1][1][ 1][0] 0]
3 b[0] b[0][1] [1][1] [1] 9 b[1] b[1][1] [1][1] [1]
4 b[0][ b[0][2][ 2][0] 0] 10 b[1][ b[1][2][ 2][0] 0]
5 b[0][ b[0][2][ 2][1] 1] 11 b[1][ b[1][2][ 2][1] 1]
La f´ormula ormula para calcular la posici´ on o n de b[i][j][k] es 6i + 2 j + k . El 6 est´a determinado por el tama˜ no no de los arreglos [3][2]. En general, para un arreglo a[i][j][k] ser´ a: a: M N i + N j + k. Observe que nuevamente la primera dimensi´ on on L no aparece en la f´ormula. ormula. Para dimensiones mayores, se generaliza el mismo proceso. Para un arreglo n dimensional de dimensiones D1 a Dn , la posici´ p osici´ on del elemento denotada on por po r los ´ındice ınd icess i1 a in est´ a dada por la f´ormula: ormula:
× ×
D2
×D ···×D ×i 3
n
1
+ D3
×
× D ···× D × i + ··· + D × i − 4
2
n
n
n 1
+ in
o puede ser escrito m´as as corto como:
n
n
j =1
k= j +1
D i
j
k
La primera primera dimensi´ dimensi´ on on D1 no aparece en la f´ormula: ormula: Por una representaci´on on columnwise la f´ ormula ormu la general gen eral ser´ıa: ıa: i1 + D1
× i + · · · + D × D ×···× D − × i − 2
1
2
n 1 + D1
n 2
× D ×···× D − × i 2
n 1
n
En la notaci´ on on matem´ atica atica griega de uber: u ¨ber:
− D i n
j 1
k
j =1
j
k=1
En este caso, es la ultima u ´ltima dimensi´ on on Dn , la que no aparece en la f´ormula. ormula. Pasar arreglos multidimensionales como par´ametros en C La representaci´on on rowwise de arreglos multidimensionales tiene un efecto directo en la programaci´ on en C. Para arreglos unidimensionales, no se neceon sita el tama˜ no del arreglo para calcular donde est´ no a localizado un elemento en
Aqu´ Aqu´ı es donde dond e puede p uede decirle al autor que fue un me jor f´ısico (o ¿fue la referencia a FORTRAN un regalo?)
CAP ´ITULO 5. ARREGLO ARREGLOS S
108
memoria. Esto no es cierto para arreglos multidimensionales. Para acceder a los elementos de estos arreglos el compilador debe conocer todo menos la primera dimensi´on. on. Esto se vuelve aparente cuando consideramos el prototipo de la funci´ on que toma un arreglo multidimensional como par´ on ametro. ametro. Lo siguiente no compilar´ a: a: void f ( int a [ ] [ ] ) ;
/ No hay informaci´ on de la dimens dimensi´ i´ on /
∗
∗
Sin embargo, lo siguiente compila: void f ( int a[ ][2] );
Cualquier arreglo bidimensional con dos columnas puede ser pasado a esta funci´ on. La primera dimensi´ on. on on no se necesita. necesita.2 No se confunda con una funci´on on con este prototipo: void f ( int
∗ a[
] );
Esto define un arreglo arreglo unidimensio unidimensional nal de apuntador apuntadores es enteros enteros (que incidenincidentalmente 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 especificar todo menos la primera dimensi´ on. on. Por ejemplo, un arreglo arreglo de 4 dimensiones dimensiones se podr´ podr´ıa pasar como: void f ( int a[
5.2. 5.2.
][4 ][4][3 ][3][2] ][2] );
Instruc Instruccio ciones nes de arregl arreglos/ os/cad cadena enass
La familia de procesadores 80x86 suministran varias instrucciones que est´an an dise˜ nadas para trabajar con arreglos. Estas instrucciones son llamanadas das instrucciones de cadena . Ellas usan los registros de ´ındice ındice (ESI y EDI) para realizar una operaci´on on y entonces autom´ aticamente incrementar o deaticamente crementar uno o los dos registros de ´ındice. ındice. La bandera direcci´ on (DF) en el registro FLAGS determina si los registros de ´ındice son incrementados o decrementados. Hay dos instrucciones que modifican la bandera direcci´ on: on: CLD Borra la bandera de direcci´ on. on. En este estado, los registros de ´ındice ındice se incrementan. STD Establece la bandera de direcci´on. on. En este estado los registros de ´ındice se decrementan. decrementa n. Un error muy com´ un en la programaci´ on de 80x86 es olvidar colocar la banon dera de direcci´on on en el estado correcto de manera expl´ expl´ıcita. Esto a menudo 2
Se puede especificar un tama˜ no no all´ı, ı, pero ser´ a ignorado por el compilador.
5.2. INSTRUCCIONES INSTRUCCIONES DE ARREGLOS/CADENAS ARREGLOS/CADENAS LODS LODSB B LODS LODSW W LODS LODSD D
AL = [DS:E [DS:ESI SI] ] ESI = ESI 1 AX = [DS:E [DS:ESI SI] ] ESI = ESI 2 EAX EAX = [DS:E [DS:ESI SI] ] ESI = ESI 4
± ± ±
STOS STOSB B STOS STOSW W STOS STOSD D
109
[ES: [ES:ED EDI] I] = AL AL EDI = EDI 1 [ES: [ES:ED EDI] I] = AX AX EDI = EDI 2 [ES: [ES:ED EDI] I] = EAX EAX EDI = EDI 4
± ± ±
Figura 5.7: Instrucciones de cadena de lectura y escritura 1 2
segment .data array1 dd 1, 2, 3, 3, 4, 5, 5, 6, 7, 7, 8, 9, 9, 10
3 4 5
segment segment .bss .bss arra array2 y2 resd resd 10 10
6 7 8 9 10 11 12 13 14 15
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 hace que el c´odigo odigo trabaje la mayor parte del tiempo (cuando sucede que la bandera de direcci´on on est´a en el estado deseado) pero no trabaja todo el tiempo.
5.2.1. 5.2.1.
Leer Leer y escrib escribir ir en en la memo memoria ria
Las instrucciones m´as as elementales leen o escriben en memoria. Ellas pueden leer o escribir un byte, palabra o palabra doble de una vez. La Figura 5.7 muestra estas instrucciones con una corta descripci´ on on en pseudoc´odigo odigo de qu´ e hace ella. Hay varios puntos para tener en cuenta. Primero, ESI se usa para leer y EDI para escribir. Es f´acil acil recordar esto si uno tiene en cuenta que SI significa Source Index y DI significa significa Destination Index . Ahora, observe que el registro que almacena el dato es fijo (AL, AX o AEX). Finalmente, observe que las instrucciones de almacenamiento usan ES para determinar
CAP ´ITULO 5. ARREGLO ARREGLOS S
110 MO MOVSB VSB
MO MOVSW VSW
MO MOVSD VSD
byte byte [ES:ED [ES:EDI] I] = byte byte [DS:ESI [DS:ESI] ] ESI = ESI 1 EDI = EDI 1 word word [ES:ED [ES:EDI] I] = word word [DS:ESI [DS:ESI] ] ESI = ESI 2 EDI = EDI 2 dword dword [ES:E [ES:EDI] DI] = dword dword [DS:E [DS:ESI SI] ] ESI = ESI 4 EDI = EDI 4
± ± ± ± ± ±
Figura 5.9: Instrucciones de cadena de movimiento de memoria 1 2
segment segment .bss arra array y resd resd 10
3 4 5 6 7 8 9
segment segment .text .text cld mov edi, array mov ecx, 10 xor eax, eax rep stosd stosd
; ¡No olvide esto!
Figura 5.10: Ejemplo de arreglo cero el segmento donde escribir, no DS. En la programaci´on on en modo protegido esto normalmente no es un problema, ya que hay s´olo olo un segmento de datos y ES deber´ deber´ıa ser iniciado autom´ aticamente para referenciarlo. (tal como lo aticamente es DS). Sin embargo, en la programaci´ on on en modo real es muy importante para el programador iniciar ES con el valor de selector del segmento correcto.3 La Figura 5.8 muestra un ejemplo del uso de estas instrucciones que copia un arreglo en otro. La combinaci´ on on de una instrucci´on on LODSx y STOSx (en las l´ıneas 13 y 14 de la Figura 5.8 5.8)) es muy com´ un. De hecho, esta combinaci´ un. on on se puede realizar con una sola instrucci´on on para cadenas MOVSx. La Figura 5.9 describe las operaciones operaciones que estas instrucciones instrucciones realizan. Las l´ıneas 13 y 14 de la Figura 5.8 podr´ podr´ıa ser reemplazada con una sola instrucci´ on on MOVSD con el mismo efecto. La unica u ´nica diferencia diferenc ia ser´ ser´ıa que el registro regist ro EAX no ser´ ser´ıa usado 3
Otra complicaci´ on es que uno no puede copiar el valor de DS en el registro de ES on directamente usando la instrucci´ on on MOV. En lugar de ello, el valor DS debe copiarse a un registro de prop´ osito general (como AX) y entonces copiado de este registro a ES usando osito dos instrucciones MOV.
5.2. INSTRUCCIONES INSTRUCCIONES DE ARREGLOS/CADENAS ARREGLOS/CADENAS CMPSB CMPSB
CMPS CMPSW W
CMPS CMPSD D
SCAS SCASB B SCAS SCASW W SCAS SCASD D
111
compa compara ra el by byte te [DS:ES [DS:ESI] I] y el byte byte [ES: [ES:EDI EDI] ] ESI = ESI 1 EDI = EDI 1 comp compar ara a la pala palabr bra a [DS: [DS:ES ESI] I] y la word word [ES:EDI] ESI = ESI 2 EDI = EDI 2 comp compar ara a la dword dword [DS:E [DS:ESI SI] ] y la dword dword [ES:EDI] ESI = ESI 4 EDI = EDI 4 comp compar ara a AL y [ES:E [ES:EDI DI] ] EDI 1 comp compar ara a AX y [ES:E [ES:EDI DI] ] EDI 2 comp compar ara a EAX EAX y [ES: [ES:ED EDI] I] EDI 4
± ± ± ± ± ±
± ± ±
Figura Figura 5.11: Comparaci´ Comparaci´ on de las instrucciones de cadena on en el bucle.
5.2.2. 5.2.2.
El prefi prefijo jo de de instr instruc ucci ci´ ´on on
REP
La familia 80x86 suministra un prefijo de instrucci´ on on especial4 llamado REP que se puede usar con las instrucciones anteriores Este prefijo le dice a la CPU que repita la pr´ oxima oxima instrucci´ on on de cadena un n´umero umero especificado de veces. El registro ECX se usa para contar las iteraciones (tal como la instrucci´ on on LOOP). Usando el prefijo REP, el bucle en la Figura 5.8 (l´ınea ın eass 12 a 15) se podr po dr´´ıa reemplazar reempla zar con una sola l´ınea: rep movsd movsd
La Figura 5.10 muestra otro ejemplo que llena de ceros el contenido de un arreglo.
5.2.3. 5.2.3.
Compa Comparac raci´ i´ on de las instrucciones de cadena on
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 u ´ tiles para comparar o buscar buscar en arreglos. Ellas fijan el registro FLAGS 4
un prefijo de instrucci´ on on no es una instrucci´ on, es un byte especial que se coloca antes on, de una instrucci´ on de cadena que modifica el comportamiento de la instrucci´ on on. on. Se usan otros prefijos para sobreescribir los segmentos por omisi´on on de los accesos a memoria
CAP ´ITULO 5. ARREGLO ARREGLOS S
112 1 2
segment segment .bss array resd 100
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
segment segment .text .text cld mov edi, di, arra rray ; apu apuntado ador al inic nicio del arr arreglo mov ecx, 100 ; numero u ´mero de element elementos os mov eax, 12 ; nu umer ´mero o a busca buscar r lp: scasd je found loop lp ; c´ odigo o digo a ej ejecu ecuta tar r si no se en enco contr ntr´ o ´ jmp onward found: sub edi, 4 ; edi apunta ahora a 12 en array ; c´ odigo o digo a ej ejecu ecuta tar r si se en encon contr tr´ o ´ onward:
Figura 5.12: Ejemplo de b´ usqueda usqueda REPE, REPZ REPNE, REPNZ
repite la instrucci´onn onn mientras mientras la bandera bandera Z est´ est´e fija o un m´ aximo aximo de ECX veces repite la instrucci´on on mientras la bandera Z est´e borrada o un m´ aximo aximo de ECX veces Figura 5.13: Prefijos de instrucci´on on REPx
tal como la instrucci´ on on CMP. Las instrucciones CMPSx comparan el lugar de memoria correspondiente y SCASx comparan lugares de memoria en busca de un valor espec´ esp ec´ıfico. ıfico . La Figura 5.12 muestra una porci´on o n de c´odigo odigo que busca el n´umero umero 12 en un arreglo arreglo de palabras dobles, la instrucci´ instrucci´ on on SCASD en la l´ınea ın ea 10 siempre le suma 4 a EDI a´ un un si el valor buscado buscado se encuentr encuentra. a. As´ As´ı si uno desea encontrar la direcci´on on de donde est´a el 12 es necesario sustraer 4 de EDI (como lo hace la l´ınea 16 16). ).
5.2.4. 5.2.4.
Prefijo Prefijoss d de e inst instru rucc cci´ i´ on on
REPx
Hay varios prefijos de instrucci´on on del tipo REP que se pueden usar con las instrucciones de comparaci´on on de cadenas. La Figura 5.13 muestra los
5.2. INSTRUCCIONES INSTRUCCIONES DE ARREGLOS/CADENAS ARREGLOS/CADENAS 1 2 3 4 5 6 7 8 9 10 11 12
113
segment .text cld mov mov esi, block1 ck1 ; direcci ccion o ´n de del l pr prim imer er bloqu bloque e mov mov edi, block2 ck2 ; direcci ccion o ´n de del l se segu gundo ndo bloqu bloque e mov ecx, size ; tamano n ~o de del l bl bloqu oque e en bytes bytes repe cmpsb ; repita mientras la bandera Z este´ fija fija je equal ; Si Z esta ´ fi fija, ja, los los bloqu bloque e son igual iguales es ; c´ odigo o digo pa para ra ej ejec ecuta utar r si lo los s bl bloqu oques es no son igual igual jmp onward equal: ; c´ odigo o digo pa para ra ej ejec ecuta utar r si lo los s bl bloqu oques es so son n igual iguales es onward:
Figura 5.14: Comparando bloques de memoria dos nuev nuevos os prefijo prefijoss y descri describe be su operaci operaci´´on. on. REPE y REPZ son sin´onimos onimos para el mismo prefijo (como RPNE y REPNZ) si la comparaci´ on on repetida de la instrucci´on on de cadena se detiene por el resultado de la comparaci´ on, on, el registro o registros ´ındice se incrementan y ECX se decrementa; sin embargo, el registro FLAGS almacenar´ a el estado que termin´o la repetici´on. on . As´ı es ¿Por qu´e uno un o no n o solo sol o ve si des pu´es es de reposible usar la bandera Z para determinar si las comparaciones repetidas ECX es cero despu´ petir la comparaci´ on? pararon por una comparaci´ on o porque ECX es cero. on La Figura 5.14 muestra una porci´on on c´ odigo de ejemplo que determina si odigo dos bloques de memoria son iguales. El JE en la l´ınea ın ea 7 del ejemplo examina el resultado de la instrucci´on on anterior. Si la comparaci´ on on repetida se detiene porque encontr´o dos bytes diferentes, la bandera Z continuar´ a borrada y no se hace el salto; sin embargo, si las comparaciones paran porque ECX se vuelve cero, la bandera Z estar´a fijada y el c´odigo odigo salta a la etiqueta equal.
5.2. 5.2.5. 5.
Ejem Ejempl plo o
Esta secci´on on contiene un archivo fuente en ensamblador con varias funciones que implementan operaciones con arreglos usando instrucciones de cadena. Muchas de las funciones duplican las funciones de las bibliotecas conocidas de C. 1
memory.asm global _asm_copy, _asm_copy, _asm_find, _asm_find, _asm_strlen, _asm_strlen, _asm_strcpy _asm_strcpy
2 3 4 5
segment .text ; fu func nci´ i´ on on _asm_cop _asm_copy y ; co copi pia a un bloqu bloque e de memor memoria ia
CAP ´ITULO 5. ARREGLO ARREGLOS S
114 6 7 8 9 10 11
; prot protot otip ipo o de C ; vo void id as asm_c m_cop opy( y( vo void id * dest, dest, co const nst void * src, src, unsig unsigned ned sz); ; par´ ametros: ametros: ; des dest - ap apunta ntador de del b´ bu ufer ´fer pa para ra copia copiar r a ; src - ap apuntador de del b´ bufer u ´fer para copiar copiar desde desde ; sz - nu umero ´ mero de by bytes tes a co copi piar ar
12 13
; A continua continuaci´ ci´ on, o n, se de defin finen en al algu gunos nos s´ ımbolos ımbolos utiles u ´tiles
14 15 16 17 18 19 20 21
%define %define dest [ebp+8] [ebp+8] %d %def efine ine src src [ebp+ [ebp+12] 12] %def %defin ine e sz [ebp [ebp+1 +16] 6] _asm_copy: enter 0, 0 push esi push edi
22
mov mov mov
23 24 25
esi, src edi, dest ecx, sz
; esi = direccion o ´n del b´ ufer ufer para copiar desde ; edi = direccion o ´n del b´ ufer u fer para para copia copia a ; ecx = numero u ´mero de bytes bytes a copiar copiar
movsb
; borrar la bandera de direccion o ´n ; ejecute movsb ECX veces
26
cld rep
27 28 29
pop pop leave ret
30 31 32 33
edi esi
34 35 36 37 38 39 40 41 42 43 44 45 46 47
; ; ; ; ; ; ; ; ; ; ; ;
functio function n _asm_fi _asm_find nd Busc Busca a en la memo memori ria a un byte byte dado dado vo void id * asm_f asm_find ind( ( const const vo void id * src, src, char char targe target, t, unsig unsigne ned d sz); sz); par´ ametros ametros src - apuntador al lugar donde o ´nde buscar buscar tar target - valo alor del del byt byte a buscar car sz - tamano n ~ o en bytes de d´ onde o nde se busca busca va valor lor de re retor torno no si targ target et se encu encuen entr tra, a, apun apunta ta a la prime primera ra ocur ocurre renc ncia ia de targ target et si no retorna NULL
5.2. INSTRUCCIONES INSTRUCCIONES DE ARREGLOS/CADENAS ARREGLOS/CADENAS 48 49 50 51 52 53
115
; NOTA NOTA: : targ target et es un valor valor de un byte, byte, pero pero se empuj empuja a en la pila pila como como ; un una a palab palabra ra do doble ble. . El by byte te se al almac macen ena a en los 8 bits bits infer inferior iores es. . ; %def define src [ebp ebp+8] %define %define target target [ebp+12] [ebp+12] %define sz [ebp+16]
54 55 56 57
_asm_find: enter push
0,0 edi
58 59 60 61 62
mov mov mov cld
eax, target edi, src ecx, sz
; al tiene el valor a buscar
repne
scasb
; busca hasta que ECX == 0 or [ES:EDI] == AL
je
found_it
; Si la bandera Z se fija, entonces se ; encontr´ encontr´ o el valo valor r ; Si no se encontro, o ´, retor retorna na un apunt apuntado ador r NULL NULL
63 64 65 66 67 68 69 70 71 72 73 74 75 76
mov jmp found_it: mov dec quit: pop leave ret
eax, 0 short quit eax, edi eax
; Si se encuentra retorna (DI - 1)
edi
77 78 79 80 81 82 83 84 85
; ; ; ; ; ; ;
functio function n _asm_str _asm_strlen len re reto torna rna el ta tama~ ma~ no n o de una una cade cadena na un unsi signe gned d asm_s asm_strl trlen en( ( co const nst ch char ar * ); par´ par´ ametro ametro src - apu apuntador dor a la cad cade ena va valo lor r de retor retorno: no: n´ umero u mero de ca carac racte teres res en la cadena cadena (sin contar contar el 0 final final) ) (en (en EAX) EAX)
86 87 88 89
%def %defin ine e src src [ebp [ebp + 8] _asm_strlen: enter 0,0
CAP ´ITULO 5. ARREGLO ARREGLOS S
116 90
push
edi
mov mov mov xor cld
edi, src ; edi = apuntador a la cadena ecx, ecx, 0FF 0FFFF FFFF FFFF FFh h ; usa usa el may mayor or valo valor r posi posibl ble e en en ECX ECX al,al ; al = 0
repnz
scasb
91 92 93 94 95 96 97
; busca un 0 para terminar
98 99 100 101 102 103 104
; ; re repnz pnz tendr tendr´ a ´ un traye trayecto cto muy la largo rgo, , as´ ı el tama tama~ no n ~o ser´ sera ´ ; FFFF FFFFFF FFFE FE - ECX, ECX, y no FFFF FFFFFF FFFF FF - ECX ECX ; mov eax,0F ,0FFFFFF FFFFEh sub eax, ecx ; length = 0FFFFFFFEh - ecx
105 106 107 108
pop leave ret
edi
109 110 111 112 113 114 115 116 117 118 119 120 121 122
; funci´ funci´ on on _asm_strcpy _asm_strcpy ; co copia pia una ca caden dena a ; vo void id as asm_s m_str trcpy cpy( ( char char * de dest, st, const const char char * src); src); ; par´ ametros ametros ; dest dest - apun apunta tado dor r a la cade cadena na de des desti tion on ; src src - ap apunta ntador a la la ca caden dena or origen ; %d %def efine ine dest [ebp + 8] %def %defin ine e src [ebp [ebp + 12] 12] _asm_strcpy: enter 0,0 push esi push edi
123 124 125 126 127 128 129 130 131
mov mov cld cpy_loop: lodsb stosb or jnz
edi, dest esi, src
al, al cpy_loop
; ; ; ;
Caraga AL e incrementa SI almacena AL e incrementa DI fija las banderas de condicion o´n si no mas a ´s all´ all´ a de termi terminar nar 0, conti contin´ n´ ua ua
5.2. INSTRUCCIONES INSTRUCCIONES DE ARREGLOS/CADENAS ARREGLOS/CADENAS
117
132
pop pop leave ret
133 134 135 136
edi esi memory.asm
memex.c #include #define STR SIZE 30
/ prototipos /
∗
∗
void asm copy( void , const void , unsigned ) attribute (( cdecl )); void asm find( 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 copy(st (st2, 2, st1, STR SIZE) SIZE);; / copi copiaa todos todos los los 30 cara caract cter eres es de la cadena / print printff (” %s n”, st2);
∗
\
∗
print printff (”Digite (”Digite un carac caracter ter : ”); / busca busca un byte byte en la cadena cadena / scanf(”%c% [ˆ n]”, &ch); st = asm asm find(s find(st2, t2, ch, STR STR SIZE) SIZE);; if ( s t ) printf printf (”Encontrado (”Encontrado en : %s n”, st);
∗
∗ \
\
else
printf (”No encontrado n”);
\
st1 st1 [0] [0] = 00;; printf printf (”Digite una Cadena:”); Cadena:”); scanf(” scanf(” %s”, %s”, st1); st1); printf (”len = %u n”, asm strlen(st1 strlen(st1)); ));
\
∗
CAP ´ITULO 5. ARREGLO ARREGLOS S
118
asm asm str strcp cpy( y( st2 st2 , st1 st1 ); print printff (” %s n”, st2 );
\
/ copi copiaa un dato dato sign signific ificat ativ ivo o en stri string ng /
∗
∗
return 0;
} memex.c
Cap´ıtulo 6
Punto flotante 6.1. 6.1.
Repr Repres esen enta taci ci´ on o ´n de punto flotante
6.1.1.
N´ umeros binarios no enteros umeros
Cuando se discutie di scutieron ron los l os sistemas si stemas num´ericos ericos en el primer cap´ cap´ıtulo, ıtulo , s´ olo olo se trataron los valores enteros. Obviamente, debe ser posible representar n´umeros umeros no enteros enteros en otras bases y as´ as´ı como en decimal. decimal. En decimal, decimal, los d´ıgitos a la derecha derecha del punto punto decimal decimal tienen asociados potencias potencias de 10 negativas. 0,123 = 1 10−1 + 2 10−2 + 3 10−3
×
×
×
No es sorprendente que los n´ umeros umeros binarios binarios trabajen parecido. 0,1012 = 1
× 2−
1
+0
2
× 2−
+1
3
× 2−
= 0,625
Esta idea se puede combinar con los m´etodos etodos para enteros del Cap´ Cap´ıtulo 1 para convertir un n´umero. umero. 110,0112 = 4 + 2 + 0,25 + 0,125 = 6,375 Conver Convertir tir de decimal decimal a binario binario no es muy dif´ dif´ıcil. En general general divida divida el n´umero umero decimal en dos partes: entero y fracci´on. on. Convierta la parte entera usando a binario usando los m´etodos etodos del Cap´ Cap´ıtulo 1. La parte fraccionaria se convierte usando el m´etodo etodo descrito a continuaci´ on: Considere una fracci´on on binaria con los bits etiquetados como a , b , c , . . . entonces el n´ umero umero se ve como: 0.abc .abcde def f ... Multiplique el n´ umero por dos. La representaci´on umero on binaria del nuevo n´ umero umero ser´ a: a: a.bc a.bcde def f ...
119
CAP ´ITULO ITULO 6. PUNTO PUNTO FLOTANTE FLOTANTE
120
0,5625
×2 0,125 × 2 0,25 × 2 0,5 × 2
= 1,125
first bit = 1
= 0,25
second bit = 0
= 0,5
third bit = 0
= 1,0
four ourth bit = 1
Figura 6.1: Convertir 0.5625 a binario
0,85
×2 0,7 × 2 0,4 × 2 0,8 × 2 0,6 × 2 0,2 × 2 0,4 × 2 0,8 × 2
= 1,7 = 1,4 = 0,8 = 1,6 = 1,2 = 0,4 = 0,8 = 1,6
Figura 6.2: Convertir 0.85 a binario Observe que el primer bit est´a ahora en el lugar del uno. Reemplace a con 0 y obtiene: 0.bcd .bcdef ef . . . y multiplique por dos otra vez para obtener: b.cd b.cdef ef . . .
Ahora el segundo bit (b) est´ a en la primera posici´on. on. Este procedimiento se puede repetir hasta los bits que se deseen encontrar. La Figura 6.1 muestra un ejemplo real que convierte 0.5625 a binario. El m´etodo etodo culmina cuando la parte fraccionaria es cero. Como otro ejemplo, considere convertir 23.85 a binario. Es f´ acil acil convertir la parte entera (23 = 101112 ), pero ¿Qu´ e pasa con la parte fraccionaria (0,85)? La Figura 6.2 muestra el comienzo de este c´ alculo. alculo. Si uno mira cuidadosamente los n´ umeros, se encuentra con un bucle infinito. Esto significa umeros,
´ DE PUNTO FLOTANTE 6.1. REPRESENTACI REPRESENTACI ON
121
que 0.85 en un binario peri´odico odico (a diferencia de los decimales peri´ odicos odicos en 1 base 10). Hay un patr´on on de los n´ umeros calculados. Mirando en el patr´on, umeros on, uno puede ver que 0 ,85 = 0,1101102 . As´ı 23,85 = 10111,1101102 . Una consecuencia importante del c´ alculo anterior es que 23.85 no se alculo puede representar exactamente en binario usando un n´ umero umero finito de bits. 1 (Tal como 3 no se puede representar en decimal con un n´umero umero finito de d´ıgitos). ıgito s). Como muestra este cap´ cap´ıtulo, ıtulo , las variables float y double en C son almacenados almacenados en binario. binario. As´ As´ı, valores como 23.85 no se pueden pueden almacenar almacenar exactamente en estas variables. S´olo olo se puede almacenar una aproximaci´ on on a 23.85 Para simplificar el hardware, los n´umeros umeros de punto flotante se almacenan con un formato consistente. Este formato usa la notaci´ on on cient´ ci ent´ıfica ıfi ca (pero en binario, usando potencias de dos, no de diez). Por ejemplo 23.85 o 10111,11011001100110 . . .2 se almacenar´ a como: 1,011111011001100110 . . .
100
×2
(Donde el exponente (100) est´ a en binario). Un n´umero umero de punto flotante normalizado tiene la forma: 1.ssssssssssssssss
eeeeeee
×2
D´ onde onde 1.sssssssssssss es la mantisa y eeeeeeee es el exponente. exponente.
6.1.2. 6.1.2.
Repre Represen sentac taci´ i´ on IEEE de punto flotante on
El IEEE (Institute of Electrical and Electronic Engineer) es una organizaci´ on internacional que ha dise˜ on nado nado formato binarios espec´ espec´ıficos para almacenar n´ umeros umeros de punto punto flotante. flotante. Este formato formato se usa en la may mayor or´´ıa (pero no todos) los computadore computadoress hechos hechos hoy d´ d´ıa. A menudo menudo es soportado soportado por el hardware hardware de computador computador en s´ı mismo. Por ejemplo ejemplo el coprocesador coprocesador num´erico eri co (o matem´ mat em´atico) atico) de Intel (que est´ a empotrado en todas las CPU desde el Pentium) lo usa. El IEEE define dos formatos con precisi´on on diferente: precisi´on on simple y doble. La precisi´on on simple es usada por las variables on doble es usada por la variable double. float en C y la precisi´on El coprocesador matem´ atico de Intel utiliza un tercer nivel de mayor preatico cisi´ on on llamado precisi´ on extendida . De hecho, todos los datos en el coprocesador sad or en e n s´ı mismo m ismo est´an an en esta precisi´on. on. Cuando se almacenan en memoria desde el coprocesador se convierte a precisi´on on simple o doble autom´ aticaatica2 mente. mente. La precisi´on on extendida usa un formato general ligeramente diferente 1
No deber deb er´ ´ıa sorprenderse so rprenderse de que un n´ numero u ´mero pudiera repetirse en una base, pero no en 1 otra. Piense en 3 , es peri´odico odico en decimal, pero en ternario (base3) ser´ ser´ıa 0,13 4. 2 double usa esta precisi´ Algunos compiladores (como Borland) los tipos long double on on extendouble . dida. Sin embargo, otros compiladores usan la precisi´ on on doble para double y long double (Esto es permitido por ANSI C).
CAP ´ITULO ITULO 6. PUNTO PUNTO FLOTANTE FLOTANTE
122 31 30 s s e f
23
22
e
0 f
bit bit de sign signo o - 0 = positiv positivo, o, 1 = nega negativ tivoo exponen exponente te sesgado sesgado (8-bits) (8-bits) = verdad verdadero ero exponen exponente te + 7F (127 decidecimal). Los valores 00 y FF tienen significado especial (vea el texto). frac fracci ci´´on on - los 23 2 3 primeros bits despu´es es de 1. en la mantisa. ma ntisa. Figura 6.3: Precisi´ on on simple de la IEEE
que los formatos float y double de la IEEE y no ser´a discutido ac´a. a. Presici´ on on simple IEEE
Uno deber´ıa ıa tene t enerr en cuencuen ta que los bytes tes 41 BE CC CD se pue pueden den inte interrpreta pretarr de difer diferente entess maneras dependiendo qu´e hace un programa con ellos. Como Como n´ umer mero de punto flota flotant ntee de pre precisi cisi´ ´ on simple simple,, ellos ellos repr represe esenta ntan n , 23 850000381 850000381,, pero ero como omo un ente enterro, ellos ellos repr epresentan 1, 103, 023, 309 309.. La CPU no conoce cu´ al es la interpretaci´ on correcta.
El punto punto flotante flotante de precisi´ on simple usa 32 bits para codificar el n´ on umeumero. Normalmente son exactos los primeros 7 d´ıgitos ıgitos decimales. Los n´ numeros ´ de punto flotante son almacenados en una forma mucho m´as as complicada que los enteros. La Figura 6.3 muestra la forma b´asica asica del formato de precisi´ on simple del IEEE. Hay varias peculiaridades del formato. Los n´ on umeros umeros de punto flotante no usan la representaci´on on en complemento a 2 para los n´umeros umeros negativos. Ellos usan la representaci´on on de magnitud y signo. El bit 31 determina el signo del n´ umero como se muestra en la figura. umero 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 sesgado siempre es no negativo. La parte fraccionaria se asume que es normalizada (en la forma 1.sssssssss). Ya que el primer bit es siempre si empre uno, ´este este uno no se almacena . Esto permite el almacenamien almacenamiento to de un bit adicional al final y as´ as´ı se incremen incrementa ta un poco p oco la precisi´on. on. Esta idea se conoce como la representaci´ on oculta del uno. uno. ¿C´ omo se podr´ omo p odr´ıa ıa almacen a lmacenar ar 23.85? 23 .85? Primero es positivo, p ositivo, as´ as´ı el bit de signo sig no es 0, ahora el exponente exponente verdader verdaderoo es 4, as´ as´ı que el exponente exponente es 7F + 4 = 8316. Finalmente la fracci´ on es (recuerde el uno de adelante est´a oculto). on Colocando todo esto unido (para ayudar a aclarar las diferentes secciones del formato del punto flotante, el bit de signo y la fracci´on on han sido subrayados y los bits se han agrupado en nibles): 0 100 0001 0001 1 011 1110 1110 1100 1100 1100 1100 1100 11002 = 41BECCCC16 Esto no es exactamente 23.85 (ya que es un binario peri´odico). odico). Si uno convierte el n´ umero anterior a decimal, uno encuentra que es aproximadamente umero 23,849998474. Este n´ umero es muy cercano a 23.85, pero no es exacto. En umero C, 23.85 no se puede representar exactamente. Ya que el bit del extremo izquierdo que fue truncado de la representaci´on on exacta es 1, el ultimo u ´ltimo bit es
´ DE PUNTO FLOTANTE 6.1. REPRESENTACI REPRESENTACI ON e=0
and f = 0
e=0
and f = 0
representa el n´ umero cero (que no puede ser umero normalizado) Observe que hay un +0 y -0. representa un n´ umero sin normalizar . Estos se discuten en la pr´oxima oxima secci´ on. on. repr repres esen enta ta infin infinito ito ( ). Hay infinitos positivo y negativo. repres represen enta ta un result resultado ado indefinid indefinido, o, Conocid Conocidoo como NaN (Not a Number).
e = FF
and f = 0
e = FF
and f = 0
123
∞
Cuadro 6.1: Valores especiales de f y e redondeado redondea do a 1. As´ı 23.85 23 .85 podr po dr´´ıa ser s er representado repr esentado como 41 BE B E CC CD en hexadecimal usando precisi´ on simple. Convirtiendo esto a decimal el resultado on y que es una aproximaci´on on ligeramente mejor de 23.85. ¿C´ omo omo se s e po dr´ dr´ıa representar re presentar -23.8 -23.85? 5? s´olo olo cambie el bit de signo: C1BECCCD ¡no tome el complemento a 2! Ciertas combinaciones de e y f tienen significado especial para los float IEEE. El Cuadro 6.1 describe estos valores especiales. Un infinito se produce por un desborde o por una divisi´on on por cero. Un resultado indefinido se produce por una operaci´on o n no v´ alida alida como tratar de encontrar la ra´ ra´ız cuadrada de un n´umero umero negativo, sumar dos infinitos, etc. Los n´ umeros umeros de precisi´on on simple est´an a n en el rango de 1,0 2−126 ( 1,1755 10−35 ) to 1,11111 . . . 2127 ( 3,4028 1035 ).
×
×
≈
×
×
≈
N´ umeros sin normalizar umeros Los n´ umeros sin normalizar se pueden usar para representar n´umeros umeros umeros con magnitudes m´ as as peque˜ nas que los normalizados (menores a 1,0 2−126). Por nas ejemplo, ejemplo, considere el n´ umero umero 1,0012 2−129 ( 1,6530 10−39). En la forma normalizada, el exponente es muy peque˜ no. Sin embargo, se puede represenno. tar de una forma no normalizada como: 0,010012 2−127 . Para almacenar este n´ umero, el exponente sesgado se fija 0 (ver Cuadro 6.1 umero, 6.1)) y la fracci´ on on − 127 es la mantisa completa del n´ umero escrito multiplicado por 2 umero (todos los bits son almacenados incluyendo el 1 a la izquierda del punto decimal). La representaci´ on o n de 1,001 2−129 es entonces:
×
≈
×
×
×
×
0 000 0000 0000 0 001 0010 0010 0000 0000 0000 0000 0000 0000
Doble precisi´ on on IEEE La doble precisi´on on IEEE usa 64 bits para representar n´ umeros umeros y normalmente son exactos los 15 primeros d´ıgitos decimales. Como muestra la
CAP ´ITULO ITULO 6. PUNTO PUNTO FLOTANTE FLOTANTE
124 63 62 s
52 e
51
0 f
Figura 6.4: Precisi´ on on doble del IEEE Figura 6.4, el formato b´ asico es muy similar a la precisi´ asico on on simple. Se usan m´ as bits para el exponente (ll) y la fracci´on as on (52) que para la precisi´on on simple. El gran rango para el exponente sesgado tiene dos consecuencias. La primera es que se calcula como la suma del exponente real y 3FF (1023) (no 7F como para la precisi´ on simple). Segundo, se permite un gran rango on de exponentes exponentes verdader verdaderos os (y as´ as´ı usa un gran rango de magnitudes) magnitudes).. Las − 308 308 magnitudes de precisi´on on doble van de 10 hasta 10 aproximadamente. En el campo de la fracci´on on el responsable del incremento en el n´umero umero de d´ıgitos significativos para los valores dobles. Como un ejemplo, considere nuevamente 23.85 otra vez. El exponente sesgado ser´a 4 + 3FF = 403 en hexadecimal. hexadecimal. As´ As´ı la represen representaci´ taci´ on doble ser´ıa: 0 100 0000 0011 0111 1101 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1010 O 40 37 D9 99 99 99 99 9A en hexadecimal. Si uno convierte esto a decimal uno encuentra 23.8500000000000014 (¡hay 12 ceros!) que es una aproximaci´ on mucho mejor de 23.85. on La precisi´on on doble tiene los mismos valores especiales que la precisi´on on 3 simple. Los n´ umeros umeros no normalizados son muy similares tambi´ en. en. La principal diferencia es que los n´umeros umeros dobles sin normalizados usan 2−1023 en lugar de 2−127.
6.2.
Aritm´ etica etica de punto punto flotante flotante
La aritm´ aritm´etica etica de punto punto flotante flotante en un computador computador diferent diferentee que en la matem´ atica continua. En matem´ atica aticas, aticas, todos los n´ umeros pueden ser consiumeros derados exactos. Como se muestra en la secci´ on anterior, en un computador on muchos n´ umeros umeros no se pueden pueden represen representar tar exactamente exactamente con un n´ n umero u ´ mero finito de bits. Todos los c´ alculos se realizan con una precisi´on alculos on limitada. En los ejemplos de esta secci´on, on, se usar´an a n n´ umeros con una mantisa de 8 bits por umeros simplicidad. 3
La ´unica unica diferencia es que para los valores de infinito e indefinido, el exponente sesgado es 7FF y no FF.
´ 6.2.. AR 6.2 ARITM ITM ETICA DE PUNTO FLOTANTE
6.2. 6.2.1. 1.
125
suma su ma
Para sumar dos n´ umeros de punto flotante, los exponentes deben ser umeros iguales. Si ellos, no son iguales, entonces se deben hacer iguales, desplazando la mantisa del n´ umero con el exponente m´as umero as peque˜ no. Por ejemplo, considere no. 10,375 + 6,34375 = 16,71875 o en binario: 1,0100110 + 1,1001011
3
×2 ×2
2
Estos dos n´ umeros umeros no tienen el mismo exponente exponente as´ as´ı que se desplaza desplaza la mantisa para hacer iguales los exponentes y entonces sumar: 3
1.0100110 + 0.1100110 10.0001100
×2 ×2 ×2 Observe que el desplazamiento de 1,1001011 × 2 pierde el uno delantero y luego de redondear el resultado se convierte en 0,1100110 × 2 . El resultado de la suma, 10,0001100 × 2 (o 1 ,00001100 × 2 ) es igual a 10000,110 o 16.75. 3 3
2
3
3
4
2
Esto no es igual a la respuesta exacta (16.71875) Es s´ olo olo una aproximaci aproximaci´ on o´n debido al error del redondeo del proceso de la suma. Es importante importante tener en cuenta cuenta que la aritm´ aritm´etica etica de punto punto flotante flotante en un com comput putado adorr (o calcula calculador dora) a) es siempr siempree una aprox aproximac imaci´ i´ on. on. Las leyes leyes de las matem´ aticas no siempre funcionan con n´ aticas umeros de punto flotante umeros en un computador. Las matem´aticas aticas asumen una precisi´ on on infinita que un computador no puede alcanzar. Por ejemplo, las matem´ aticas aticas ense˜ nan nan que (a + b) b = a; sin embargo, esto puede ser exactamente cierto en un computador.
−
6.2. 6.2.2. 2.
Rest Resta a
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:
−
−
− Desplazando 1,1111111
×2
3
4
×2
4
×2 ×2
3
da (redondeando arriba) 1,0000000
− 0,0000110
1,0000110 1,1111111
1,0000110 1,0000000 0,0000110
4
×2 ×2 ×2
4 4
= 0 ,112 = 0 ,75 que no es exacto.
×2
4
CAP ´ITULO ITULO 6. PUNTO PUNTO FLOTANTE FLOTANTE
126
6.2.3. 6.2.3.
Multi Mu ltipl plica icaci ci´ ´ on on y divisi´ on on
Para la multiplicaci´ on, las mantisas son multiplicadas y los exponentes on, son sumados. Considere 10,375 2,5 = 25,9375:
×
1,0100110 23 1,0100000 21 10100110 + 10 1010 1001 0110 10 1,10011111000000
× ×
×
4
×2
Claro est´ a, a, el resultado real podr´ıa ıa ser redondeado a 8 bits para dar: 1,1010000
4
×2
= 11010,0002 = 26
La divisi´ on on es m´as as complicada, pero tiene problemas similares con errores de redondeo.
6.2.4. 6.2.4.
Instrucc Instruccione ioness condici condicional onales es
El principal punto de esta secci´on on es que los c´alculos alculos de punto flotante no son exactos. El programador necesita tener cuidado con esto. Un error com´ un que el programador hace con n´ un umeros de punto flotante es compaumeros rarlos asumiendo que el c´alculo alculo es exacto. Por ejemplo considere una funci´on on llamada f(x) que hace un c´alculo alculo complejo y un programa est´a tratando de encontrar las ra´ ra´ıces de la funci´ on. on.4 . Uno podr´ıa ıa intentar usar la siguiente instrucci´ on on para mirar si x es una ra´ız: ız : if ( f(x) == 0.0 )
¿Pero si f(x) retorna 1 10−30 ? Esto probablemente significa que x es una muy buena aproximaci´ on on a una ra´ ra´ız verdader verdadera; a; sin embargo, embargo, la igualdad igualdad ser´ a falsa. Puede no haber ning´ un valor de punto flotante IEEE de x que un retorne cero exactamente, debido a los errores de redondeo en f(x). f(x). Un m´etodo eto do mucho mejor mejo r ser´ıa ıa usar: usar :
×
if ( fabs(f(x)) < EPS )
D´onde onde EPS es un macro definido para ser un valor positivo muy peque˜no no − 10 (como 1 10 ). Esto es cierto si f(x) est´ a muy cercano a cero. En general, para comparar un valor de punto flotante (digamos x) con otro (y (y) use:
×
if ( fabs(x 4
− y)/fabs(y) < EPS )
Una ra´ ra´ız de una funci´ on on es un valor x tal que f (x) = 0
´ 6.3. EL COPROCESA COPROCESADOR DOR NUM NUM ERICO
6.3.
El coprocesador coprocesador num´ num´ erico erico
6.3.1. 6.3.1.
Hardw Hardware are
127
Los primeros procesadores Intel no ten´ıan ıan soporte sop orte de hardware para las operaciones de punto flotante. Esto no significa que ellos no pod p od´´ıan efectuar operaciones operaciones de punto punto flotante. flotante. Esto s´ olo significa que ellas se realizaban por olo procedimientos compuestos de muchas instrucciones que no son de punto flotante. Para estos primeros sistemas, Intel suministraba un circuito integrado adicional llamado coproc coprocesador esador matem´ atico. atico. Un coprocesador matem´ atico atico tiene instrucciones de m´aquina aquina que realizan instrucciones de punto flotante mucho m´ as as r´apido apido que usando procedimientos de software (en los primeros procesadores se realizaban al menos 10 veces m´ as as r´apido). apido). El coprocesador para el 8086/8088 fue llamado 8087. Para el 80286 era el 80287 y para el 80386 un 80387. El procesador 80486DX integr´ o el coprocesador matem´ atico atico 5 en el e l 8048 8 0486 6 en s´ı m mismo ismo . Desde el Pentium, todas las generaciones del 80x86 tienen un coprocesador matem´atico atico empotrado; empo trado; sin embargo ´el el todav´ıa ıa se programa como si fuera una unidad separada. Inclusive en los primeros sistemas sin un coprocesador se puede instalar un software que emula el coprocesador matem´ atico. Estos emuladores se activan autom´ atico. aticamente aticamente cuando un programa ejecuta una instrucci´on on del coprocesador y corre un procedimiento que obtiene los mismos resultados que el coprocesador (mucho m´as as lento claro est´ a). a). El coprocesador num´ num´erico erico tiene ti ene ocho o cho registros de punto flotante. Cada registro almacena 80 bits. Los n´ umeros de punto flotante se almacenan en umeros estos registros siempre como n´ umeros de 80 bits de precisi´on umeros on extendida. Los registros se llaman STO, ST1, . . . ST7. Los registros de punto flotante se usan diferente que los registros enteros en la CPU. Los registros de punto flotante est´an an organizados como una pila. Recuerde que una pila es una lista LIFO (Last In Firist Out ). Out ). STO siempre se refiere al valores el tope de la pila. Todos los n´ umeros umeros nuevos se a˜ naden al tope de la pila. Los n´ naden umeros umeros existentes se empujan en la pila para dejarle espacio al nuevo n´ umero. umero. Hay tambi´ en en un registro de estado en el coprocesador num´ erico. erico. Tiene varias banderas. S´olo olo las 4 banderas usadas en comparaciones ser´ an an estudiadas. C0 , C1 , C2 and C3 . El uso de ellas se discutir´a luego.
6.3.2. 6.3.2.
Instru Instrucc ccion iones es
Para distinguir f´acilmente acilmente las instrucciones normales de la CPU de las del coprocesador, todos los nem´onicos onicos del coprocesador comienzan por F. 5
Sin embargo el 80486SX no tiene el un coprocesador integrado. Exist´ Exist´ıa un chip separado para estas m´ aquinas aquinas (el 80487SX)
128
CAP ´ITULO ITULO 6. PUNTO PUNTO FLOTANTE FLOTANTE
Carga y almacenamiento
Hay varias instrucciones que cargan datos en el tope de la pila de registro del coprocesador: Carga un n´ umero de punto flotante de la memoria en el tope umero FLD fuente de la pila. fuente puede ser un n´ umero umero de precisi´on on simple doble o extendida o un registro del coprocesador Lee un entero de memoria, lo convierte a punto flotante y FILD fuente almacena el resultado en el tope de la pila. fuente puede ser una palabra, palabra doble o una palabra cu´ adruple. adruple. almacena un uno en el tope de la pila. FLD1 almacena un cero en el tope de la pila. FLDZ Hay tambi´en en varias instrucciones que almacenan datos de la pila pil a en memoria. Algunas de estas instrucciones tambi´ en en sacan el n´ umero umero de la pila. Almacena el tope de la pila (ST0) en memoria. El destino FST dest puede ser un n´umero umero de precisi´on on simple o doble o un registro de coprocesador. Almacena el tope de la pila en la memoria tal como FST; sin FSTP dest embargo embargo luego de que se almacena el n´ umero, umero, su valor se saca de la pila. El destino puede ser un n´umero umero de precisi´on on simple o doble o un registro del coprocesador. Almacena el valor del tope de la pila convertido a entero en FIST dest memoria. El destino puede ser una palabra o palabra doble. La pila no sufre ning´un un cambio. C´ omo omo se convierte el n´ umero umero de punto flotante a entero depende de algunos bits de la palabra de control del coprocesador. Este es un registro especial (no de punto flotante) que controla c´ omo omo trabaja el coprocesador. Por omisi´ on, la palabra de control se inicia de tal on, manera que redondea al entero m´as as cercano cuando se convierte a entero. Sin embargo las instrucciones FSTCW (Store Control Word ) Word ) y FDLCW (Load Control Word ) Word ) se pueden usar para cambiar este comportamiento. Lo mismo que FIST, excepto por dos cosas. El tope de la pila FISTP dest se saca y el destino tambi´en en puede pu ede ser una u na palabra p alabra cu´adruple. adruple. Hay otras dos instrucciones que pueden mover o quitar datos de la pila en s´ı mism mi sma. a. intercambia los valores en ST0 y STn FXCH FXCH ST STn n STn en la pila (donde n es el n´ umero del registro de 1 a 7). umero libera un registro en la pila, marcando el registro como no FFREE FFREE ST STn n usado us ado o vac´ıo. ıo .
´ 6.3. EL COPROCESA COPROCESADOR DOR NUM NUM ERICO 1 2 3
129
segment segment .bss .bss array resq SIZE sum resq 1
4 5 6 7 8 9 10 11 12 13
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 proximo o ´ximo dobule dobule ; almacena 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, operando, el resultado resultado siempre se almacena almacena en un registro del coprocesador. coprocesador. FADD fuente ST0 ST0 += fuente . fuente puede ser cualquier registro del coprocesador o un n´ umero umero de precisi´on on simple o doble en memoria. FADD dest, ST0. El destino puede ser cualquier registro dest, ST0 dest += ST0 del coprocesador FADDP dest o ST0 y luego se saca de la pila. El destino dest += ST0 puede ser cualquier cualquier registro del coprocesador coprocesador.. FADDP dest, dest, ST0 FIADD fuente ST ST0 0 += (float (float) ) fuente . suma un entero con ST0. fuente 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 instrucci´on, on, hay una alterna que resta en el orden inverso. Esas instrucciones al rev´ es es todas culminan con R o RP. La Figura 6.5 muestra un peque˜ no no c´ odigo que suma los elementos de un arreglo de dobles. En las odigo l´ıneas 10 y 13, uno debe deb e especificar esp ecificar el tama˜ no del operando de memoria. De no otra forma el ensamblador ensamblador no conocer´ conocer´ıa si el operando operando de memoria memoria era un float (dword) o double (qword).
− −
CAP ´ITULO ITULO 6. PUNTO PUNTO FLOTANTE FLOTANTE
130 FSUB fuente
FSUBR fuente
FSUB dest, dest, ST0 FSUBR dest, dest, ST0 FSUBP dest o FSUBP dest, dest, STO FSUBRP dest o FSUBRP dest, dest, STO FISUB fuente
FISUBR fuente
ST0 ST0 -= fuente . fuente puede ser cualquier registro
del coprocesador o un n´ umero umero de presici´on o n doble o simple en memoria. ST0 = fuente - ST0. fuente puede ser cualquier registro del coprocesador o un n´ umero umero de presici´on on doble o simple en memoria. dest -= ST0 ST0. dest puede ser cualquier registro del coprocesador. cualquier regisdest = ST0 - dest . dest puede ser cualquier tro del coprocesador. ST0 y luego sale de la pila. dest puede ser dest -= ST0 cualquier registro del coprocesador. dest = ST0 - dest y luego sale de la pila. dest puede ser cualquier registro del coprocesador. ST0 -= (floa (float) t) fuente . Resta un entero de ST0. fuente debe ser una palabra o palabra doble en memoria. ST0 = (flo (float) at) fuente - ST0. Resta ST0 de un entero. fuente debe ser una palabra o palabra doble en memoria.
Multiplicaci´ on on y divisi´ on on La instrucci´on on de multiplicaci´ on on son totalmente an´ alogas alogas a las instrucciones de suma. FMUL fuente
FMUL dest, dest, ST0 FMULP dest o FMULP dest, dest, STO FIMUL fuente
ST0 ST0 *= fuente . fuente puede ser cualquier registro
del coprocesador o un n´ umero umero de precisi´on on simple o doble en memoria. dest *= ST0 ST0. dest puede ser cualquier registro del coprocesador. ST0 y luego sale de la pila. dest puede ser dest *= ST0 cualquier registro del coprocesador. ST0 *= (floa (float) t) fuente . Multiplica un entero con ST0. fuente debe ser una palabra o palabra doble en memoria.
No es sorprendente que las instrucciones de divisi´on on sean an´alogas alogas a las instrucciones de resta. La divisi´on on por cero da infinito.
´ 6.3. EL COPROCESA COPROCESADOR DOR NUM NUM ERICO FDIV fuente
FDIVR fuente
FDIV dest, dest, ST0 FDIVR dest, dest, ST0 FDIVP dest o FDIVP dest, dest, STO FDIVRP dest o FDIVRP dest, dest, STO FIDIV fuente
FIDIVR fuente
131
ST0 ST0 /= fuente . fuente puede ser cualquier registro
del coprocesador o un n´ umero umero de precisi´on on simple o doble en memoria. ST0 = fuente / ST0. fuente puede ser cualquier registro del coprocesador coprocesador o un n´umero umero de precisi´on on simple o doble en memoria. dest /= ST0 ST0. dest puede ser cualquier registro del coprocesador. dest = ST0 / dest . dest puede ser cualquier registro del coprocesador coprocesador.. ST0 y luego sale de la pila. dest puede ser dest /= ST0 cualquier registro del coprocesador. dest = ST0 / dest y luego sale de la pila. dest puede ser cualquier registro del coprocesador. ST ST0 0 /= (floa (float) t) fuente . Divide ST0 por un entero. fuente debe ser una palabra o palabra doble en memoria. Divide ide un ente entero ro ST ST0 0 = (f (floa loat) t) fuente / ST0. Div por ST0. fuente debe ser una palabra o palabra doble en memoria. memoria.
Comparaciones El coprocesador copro cesador tambi´ t ambi´en en realiza reali za comparacione compa racioness de n´ umeros umeros de punto flotante. La familia de instrucciones FCOM hacen esta operaci´ on. on. compara ST0 y fuente . fuente puede puede ser un registr registroo del coprocesador o un float o un double en memoria. puede ser un FCOMP fuente compara ST0 y fuente , luego sale de la pila. fuente puede ser un registro del coprocesador o un float o double en memoria. compara ST0 y ST1, y luego sale de la pila dos veces. FCOMPP compara ST0 y (float) fuente . fuente puede ser una palaFICOM fuente bra o palabra doble en memoria. compara ST0 y (float)fuente FICOMP fuente (float)fuente , y luego sale de la pila. fuente puede ser una palabra o palabra doble entera en memoria. compara ST0 and 0. FTST 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 f´acil acil transferir los bits de la palabra de estado a los bits correspondientes del registro FLAGS usando algunas instrucciones nuevas. FCOM fuente
CAP ´ITULO ITULO 6. PUNTO PUNTO FLOTANTE FLOTANTE
132 1 2 3 4 5 6 7
; ;
if ( x > y ) fld fcomp fstsw sahf jna
qword [x] qword [y] ax ax
; ST0 = x ; compara STO e y ; mueve los bits C a FLAGS
else_part
; si x no es mayor que y, ; va vaya ya a else_ else_par part t
8 9 10 11 12 13 14
then_part: ; c´ odigo o digo para para parte parte de enton entonces ces jmp end_if else_part: ; c´ odigo o digo pa para ra pa part rte e si no end_if:
Figura 6.6: Ejemplo de comparaci´ on on Almacena la palabra de estado del coprocesador in memoria o en el registro AX. Almacena el registro AH en el registro FLAGS. SAHF Carga el registro AH con los bits del registro FLAGS. LAHF La Figura 6.6 muestra muestra un ejemplo ejemplo cortico. cortico. Las l´ıneas 5 y 6 transfieren transfieren 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 an´alogos alogos al resultado de una comparaci´ comparaci´ on on de dos enteros sin signo. signo. Este es el porque la l´ınea 7 usa la instrucci´ on on JNA. El Pentium Pro (y procesadores posteriores II y III) soportan 2 nuevos operadores de comparaci´on on que directamente modifican el registro FLAGS de la CPU. compara ST0 y fuente . fuente debe ser un registro del coproFCOMI fuente cesador. COMPARA ST0 y fuente , y luego sale de la pila. fuente FCOMIP fuente debse ser un registro del coprocesador. La Figura 6.7 muestra una subrutina de ejemplo que encuentran el m´aximo aximo de dos n´ umeros umeros tipo double usando la instrucci´ instrucci´ on on FCOMIP. No confundan esta instrucci´ instrucci´ on on con la funci´on on de comparaci´ on on de enteros (FICOMP y FICOM). FSTSW dest
Instrucciones miscelaneas Esta secci´on on cubre las otras instrucciones que suministra el procesador:
´ 6.3. EL COPROCESA COPROCESADOR DOR NUM NUM ERICO FCHS FABS FSQRT FSCALE ALE
133
ST0 = - ST0 cambia el bit de signo de ST0 ST0 = ST0 Toma el valor absoluto de ST0 l a ra´ ra´ız cuadrada de ST0 ST0 = STO Toma la ST1 multiplica ST0 por una potencia de dos ST0 = ST0 2 r´apidamente. apidamente. ST1 no se quita de la pila del coprocesador . La
√| | ×
Figure 6.8 muestra un ejemplo de c´omo omo usar esta instrucci´ on. on.
6.3. 6.3.3. 3.
Ejem Ejempl plos os
6.3. 6.3.4. 4.
F´ ormula ormu la cuadr´ cua dr´ atica ati ca
El primer ejemplo muestra c´omo omo se puede programar la f´ ormula ormula cuadr´ atiatica en ensamblador. Recuerde que la f´ormula ormula cuadr´ atica atica calcula la soluci´ on on de una ecuaci´on on cuadr´atica: atica: ax2 + bx + c = 0
La f´ormula ormula en s´ı misma da dos soluciones para x: x1 y x2.
√ − b ± b − 4ac = 2
x1 , x2
2a
La expresi´on on dentro de la l a ra r a´ız cuadrada c uadrada (b2 4ac) es llamada discriminante, discriminante, su valor es util u ´ til para determinar las 3 posibilidades para la soluci´ on: on:
−
1. Ha Hay y solo un real en la soluci´ soluci´ on on degenerada. b2
− 4ac = 0
2. Hay dos solucione solucioness reales. b2
− 4ac > 0 3. Hay dos soluciones soluciones complejas. complejas. b − 4ac < 0 2
Este es un peque˜no no programa en C que usa la subrutina en ensamblador. quadt.c #include int quadratic( double, double, double, double , double );
∗
int main()
{
a,b,c, root1, root1, root2; root2; double a,b,c, printf (”Enter a, b, c: ”); scanf(” scanf(” %lf %lf %lf”, %lf”, &a, &a, &b, &c); &c); if ( quad quadra rati tic( c( a, b, c, &root &root1, 1, &root &root2) 2) )
∗
CAP ´ITULO ITULO 6. PUNTO PUNTO FLOTANTE FLOTANTE
134
print printff (”roots (”roots : %.1 %.10g 0g %.1 %.10g 0g n”, root1, root2);
\
else
printf printf (”No real roots n”); return 0;
\
} quadt.c A continuaci´ on on est´a la rutina en ensamblador. 1 2 3 4 5 6 7 8 9 10 11 12
; ; ; ; ; ; ; ; ; ; ; ;
quad.asm funci´ funci´ on on quadrat quadratic ic Ha Halla lla la so soluc luci´ i´ on o n de la ecua ecuaci ci´ on o ´n cuadr´ cuadr´ atica: atica: a*x^2 + b*x + c = 0 Pr Proto ototi tipo po de C: int int quadr quadrat atic ic( ( doubl double e a, doubl double e b, doubl double e c, double * root1, double *root2 ) Par´ ametros: ametros: a, b, b, c - Coef Coefic icie ient ntes es de de la ecua ecuaci ci´ on o´n cuadr´ cuadr´ atica atica (ver arriba) arriba) root root1 1 - Apun Apunta tado dor r al dou doubl ble e que que alma almace cena na la la prim primer era a ra´ raız ´ root root2 2 - Apun Apunta tado dor r al dou doubl ble e que que alma almace cena na la la segu segund nda a ra´ raız ´ Va Valor lor de re retor torno no: : devuelve 1 si si la las ra raıces ı ´ces son son real reales es si no 0
13 14 15 16 17 18 19 20
%define %define %define %define %define %define %def %defin ine e
a b c root1 root2 disc one_ one_ov over er_2 _2a a
qword qword qword dword dword qword qwor qword d
[ebp+8] [ebp+16] [ebp+24] [ebp+32] [ebp+36] [ebp-8] [ebp [ebp-1 -16] 6]
21 22 23
segment .data MinusFour
dw
-4
24 25 26 27 28 29 30 31 32
segment .text globa global l _quadratic: push mov sub push
_q _quad uadra ratic tic ebp ebp, esp esp, 16 ebx
; asigna 2 doubles (disc & one_over_2a) ; debe guardar el valor original de ebx
´ 6.3. EL COPROCESA COPROCESADOR DOR NUM NUM ERICO fild 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
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
word word [Min [Minus usFo Four ur]; ]; a ; c ; st1 ; st1 ; b b ; st1 ; st1 ; ; ax
pila pila -4 pila: a, -4 pila: c, a, -4 pila: a*c, -4 pila: -4*a*c
no_r no_rea eal_ l_so solu luti tion ons s ; disc ; ; a ; ; st1 ; one_over_2a ; b ; disc ; st s t1 ; st1 ; ebx, root1 qword [ebx] ; b ; disc ; ; st1 ; one_over_2a ; ebx, root2 qword [ebx] ; eax, 1 ; short quit
; if if dis disc c < 0, 0, no no hay hay solu soluci cion ones es real reales es 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)
pila: b, b, -4*a*c pila: b*b, -4*a*c pila: b*b - 4*a*c prueba con 0
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
68 69 70
no_real_solutions: mov eax, 0
71 72 73 74
quit: pop mov
135
ebx esp, ebp
; valor de retorno es 0
CAP ´ITULO ITULO 6. PUNTO PUNTO FLOTANTE FLOTANTE
136 pop ret
75 76
6.3.5. 6.3.5.
ebp quad.asm
Leer Leer arregl arreglos os de arc archi hivo voss
Este ejemplo, es una rutina en ensamblador que lee doubles de un archivo. Sigue un peque˜ no programa de prueba en C. no readt.c /
∗ ∗ Este programa programa prueb pruebaa el procedimie procedimiento nto en ensamblado ensambladorr de 32 32 bits read doubles() doubles() double bless de stdin stdin . (use la redire redirecci cci´ oon ´ para para leer leer desde desde un archi archivo vo .) ∗ Lee dou ∗/ #include extern int read doubles( FILE ∗, double ∗, int ); #define MAX 100 int main()
{
int i , n ; double a[MAX];
n = read read doubl doubles es(( stdi stdin n , a, MA MAX) X);; for ( i=0; i < n; i++ )
print printff (” %3d %g n”, i, a[i]); return 0;
\
} readt.c A continuaci´ on la rutina en ensamblador on 1 2
read.asm
segment .data format db db
"%lf", 0
segment .text global global exter extern n
_read_d _read_doubl oubles es _f _fsca scanf nf
; formato para fscanf()
3 4 5 6 7 8
%def %defin ine e SIZE SIZEOF OF_D _DOU OUBL BLE E
8
.
´ 6.3. EL COPROCESA COPROCESADOR DOR NUM NUM ERICO 9 10 11 12
%define %define %def define %def define
FP ARRAYP ARRAY_ AY_SIZE TEMP_D P_DOUBLE BLE
137
dword [ebp + 8] dword [ebp + 12] dword ord [ebp ebp + 16] [ebp - 8]
13 14 15 16 17 18 19 20 21 22 23 24 25
; ; ; ; ; ; ; ; ; ; ; ;
fu func nci´ i´ on on _read_doubles _read_doubles pr prot ototi otipo po de C: int int read read_d _dou oubl bles es( ( FILE FILE * fp, fp, doub double le * arra arrayp yp, , int int arra array_ y_si size ze ); Es Esta ta fu func nci´ i´ on o n lee lee dobl dobles es de un arch archiv ivo o de text texto o hast hasta a EOF EOF o hast hasta a que que se llen llene e el arre arregl glo o Par´ Par´ ametros: ametros: fp - apuntador FILE pointer a leer desde (se debe abrir para entrada) array rayp - ap apuntad tador a un ar arregl eglo de de do doubl ubles pa para le leer en en el ´ array_size - n´ nu umero ´mero de el elem ement entos os en el arreg arreglo lo Va Valo lor r de retor retorno: no: n´ umero u mero de do doubl ubles es al alma macen cenad ado o en el arreg arreglo lo (en (en EAX) EAX)
26 27 28 29 30
_read_doubles: push mov sub
ebp ebp,esp esp, SIZEOF_DOUBLE
; define un double en la pila
esi esi, ARRAYP edx, edx
; guarda esi ; esi = ARRAYP ; edx = ındice ı ´ndice del arreglo arreglo (inicia (inicialmen lmente te en 0)
31 32 33 34
push mov xor
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
while_loop: cmp edx, ARRAY_SIZE ; si edx < ARRAY_SIZE? jnl short quit ; si no, salir del bucle ; ; ll llam ama a a fscan fscanf() f() pa para ra le leer er un do doubl uble e en TE TEMP_ MP_DO DOUBL UBLE E ; fscanf( fscanf() ) podr´ podrıa ı ´ a cam cambi biar ar edx edx y as´ ası ´ guardar guardarlo lo ; push edx ; guarda edx lea eax, TEMP_DOUBLE push ea 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
CAP ´ITULO ITULO 6. PUNTO PUNTO FLOTANTE FLOTANTE
138 cmp jne
51 52
eax, 1 short quit
; fscanf retorno ´ 1? ; si no, salir del bucle
53 54 55 56 57 58 59 60 61
; ; copia copia TEMP_DOU TEMP_DOUBLE BLE en ARRAYP[e ARRAYP[edx] dx] ; (Los (Los ocho ocho byte bytes s del del doub double le son son copi copiad ados os por por las las dos dos copi copias as de 4 byte bytes) s) ; mov eax, [ebp - 8] mov [esi + 8*edx], eax ; primero copia los 4 bytes inferiores mov eax, [ebp - 4] mov [esi + 8*e 8*edx + 4], 4], eax eax ; aho ahor ra cop copia los los 4 byte bytes s sup supe eriore ores
62 63 64
inc jmp
edx while_loop
pop
esi
; restaura esi
mov
eax, edx
; almacena el valor de retorno en eax
mov pop ret
esp, ebp ebp
65 66 67
quit:
68 69 70 71 72 73
6.3.6. 6.3.6.
read.asm
Halla Hallarr primos primos
Este ultimo u ´ ltimo ejemplo halla n´ umeros primos una vez m´as. umeros as. Esta implementaci´ on o n es m´as as eficiente que la anterior. Almacena los primos que ha encontrado en un arreglo y solo divide por los primos que ha encontrado antes, en lugar de cada n´ umero impar, para encontrar nuevos primos. umero Otra diferencia es que el programa calcula la ra´ ra´ız cuadrada del n´ numero u´mero examinado para buscar el pr´oximo oximo primo para determinar determinar en qu´ e punto punto parar la b´ usqueda de factores. Altera la palabra de control de coprocesador usqueda tal que cuando almacena almacena la ra´ ra´ız cuadrada cuadrada como un entero, entero, y la trunca trunca en lugar de redondearla. Esto 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 (por omisi´ on) el coprocesador redondea cuando convierte a entero. on) 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 continuaci´ on on el programa en C: fprime.c
´ 6.3. EL COPROCESA COPROCESADOR DOR NUM NUM ERICO
139
#include #include
/
∗ primes s ∗ funci´ on find prime Halla lla los los n´ umeros primos indicados ∗ Ha Par ´amet am etro ros: s: ∗ Par´ ∗ a − arreglo arreglo que almacena almacena los primos primos ∗ n − cu´antos anto s primos primo s encontrar enco ntrar ∗/ primess ( int ∗ a , unsigned n ); extern void find prime int main()
{
int status status ; unsigned i; unsigned max; a; int
∗
printf (”¿Cu´ antos antos primos desea encontrar Ud.? ”); ”) ; scanf(” scanf(” %u”, %u”, &max); &max); a = calloc( sizeof ( int ), max); max); if ( a )
{
find prime primess (a, max); max); / imprim imprimee los 20 ´ ultimos primos encontrados encontrados / for ( i= ( max max > 20 ) ? max 20 : 0; i < max; i++ ) print printff (” %3d %d n”, i+1, a[i]);
∗
\
−
∗
free(a); status status = 0;
}
else
{
fprin fprintf tf ( stderr stderr , ”No se pued puedee crear crear el arreg arreglo lo de %u ints ints n”, max); status status = 1;
} return stat status us ;
}
\
CAP ´ITULO ITULO 6. PUNTO PUNTO FLOTANTE FLOTANTE
140
fprime.c A continuaci´ on la rutina en ensamblador: on
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
prime2.asm segment .text global global _find_p _find_prime rimes s ; ; functio function n find_pr find_primes imes ; En Encue cuent ntra ra el n´ umero umero indicado indicado de primos primos ; Par´ ametros: ametros: ; arra array y - arre arregl glo o que que alma almace cena na los los pri primo mos s ; n_find - cuantos a ´ntos primos primos encontra encontrar r ; Prot Protot otip ipo o de C ;e ;ext xtern ern void void find_ find_pri prime mes( s( in int t * array array, , unsi unsigne gned d n_fin n_find d ) ; %define array ebp + 8 %define n_find ebp + 12 %define n ebp - 4 ; numero u ´mero de primo primos s a encon encontra trar r %define isqrt ebp - 8 ; el piso de la raız ı´z cudra cudrada da de guess guess %define orig_cntl_wd ebp - 10 ; palabra original de control %define new_cntl_wd ebp - 12 ; nueva palabra de control
18 19 20
_find_primes: enter
12,0
; Hace espacio para las variables locales
push push
ebx esi
; guarda las posibles variables registro
fstc fstcw w mov or mov fldcw fldcw
word word [ori [orig_ g_cn cntl tl_w _wd] d] ax, [orig_c g_cntl_ tl_wd] ax, 0C00h [new_c w_cntl_w l_wd], ax wo word rd [n [new_ ew_cn cntl_ tl_wd wd] ]
; toma toma la pala palabr bra a de cont contro rol l act actua ual l ; fija el redondeo de los bits a 11 (truncar)
mov mov mov mov mov
esi, [array] dword [esi], 2 dword [esi + 4], 3 ebx, 5 dword [n], 2
; ; ; ; ;
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
esi apunta a array array[0] = 2 array[1] = 3 ebx = guess = 5 n = 2
; ; Es Este te bu bucle cle exter externo no en encue cuent ntra ra un nuev nuevo o primo primo en cada cada iter iteraci aci´ on, ´ ; que se a~ n nade ade al final final del arreg arreglo. lo. A dife diferen renci cia a del del prim primer er progr programa ama
´ 6.3. EL COPROCESA COPROCESADOR DOR NUM NUM ERICO 39 40 41 42
; ; ; :
141
qu que e halla halla pr prim imos, os, esta esta funci funci´ on o ´n no determi determina na los primos dividie dividiendo ndo por todos los n´ umeros umeros impares impares. . S´ olo o lo divi divide de por por los n´ numeros u ´meros primos que que ya se han han enco encont ntra rado do. . (Est (Esta a es la raz´ raz´ on por por la cual cual ello ellos s se han han al alma macen cenad ado o en el ar arreg reglo lo) )
43 44 45 46 47
while_limit: mov cmp jnb
eax, [n] eax, [n_find] short quit_limit
; while ( n < n_find )
48 49 50 51 52 53 54
mov push fild pop fsqrt fistp
ecx, 1 ebx dword [esp] ebx dword [isqrt]
; ; ; ; ; ;
ecx se usa como ındice ı ´ndice del arreglo 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))
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
; Es Este te b1 b1uc ucle le in inter terno no di divi vide de gu guess ess por los n´ umeros umeros primos primos ya encontr encontrados ados ; ha hast sta a qu que e en encu cuent entre re un facto factor r pr primo imo de gu gues ess s (que (que signi signifi fica ca que que guess guess ; no es primo) o hasta que n´ u umero mero primo primo a di divid vidir ir sea m´ as a s grand grande e que ; floor(sqrt(gue floor(sqrt(guess)) ss)) ; while_factor: mov eax, dword [esi + 4*ecx] ; eax = array[ecx] cmp eax, [isqrt] ; while ( isqrt < array[ecx] jnbe jnbe shor short t quit quit_f _fac acto tor_ r_pr prim ime e mov eax, ebx xor edx, edx div dword [esi + 4*ecx] or edx, edx ; && guess % array[ecx] != 0 ) jz shor short t quit quit_f _fac acto tor_ r_no not_ t_pr prim ime e inc ecx ; intenta el proximo o ´ximo primo primo jmp sho short while_f e_factor tor
72 73 74 75 76 77 78 79 80
; ; 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
CAP ´ITULO ITULO 6. PUNTO PUNTO FLOTANTE FLOTANTE
142 81 82 83 84
quit_factor_not_prime: add ebx, 2 jmp short while ile_lim limit
; intenta con el impar siguiente
85 86
quit_limit:
87 88 89 90
fldcw pop pop
word [orig_cntl_wd] esi ebx
91 92 93
leave ret
prime2.asm
; restaura la palabra de control ; restaura las variables de registro
´ 6.3. EL COPROCESA COPROCESADOR DOR NUM NUM ERICO
1
143
global global _dmax _dmax
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
segment .text ; funci´ funci´ on o n _dmax _dmax ; retorn retorna a el ma mayor yor de do dos s argum argument entos os do doubl uble e ; Prot Protot otip ipo o de C ; doub double le dmax dmax( ( doub double le d1, d1, doub double le d2 ) ; Par´ Par´ ametros: ametros: ; d1 - primer double ; d2 - segundo double ; Valor Valor de retor retorno: no: ; El may mayor de d1 d1 y d2 (e (en ST0 ST0) %defi efine d1 ebp+8 %def %defin ine e d2 ebp+ ebp+16 16 _dmax: enter 0, 0
17 18 19 20 21 22 23 24 25 26 27 28
fld fld fcomip jna fcomp fld jmp d2_bigger: exit: leave ret
qword qword st1 short st0 qword short
[d2] [d1]
; ST0 = d1, ST1 = d2 ; ST0 = d2
d2_bigger [d1] exit
; pop d2 de la pila ; ST0 = d1 ; si d2 is max, no hace nada
Figura 6.7: Ejemplo de FCOMIP
CAP ´ITULO ITULO 6. PUNTO PUNTO FLOTANTE FLOTANTE
144
1 2 3
segment segment .data .data x dq five dw
2.75 5
; convertido a formato double
4 5 6 7 8
segment segment .text .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.
Estr Estruc uctu tura rass
7.1.1. 7.1.1.
Introd Introduc ucci´ ci´ on on
Las estructuras se usan en C para agrupar datos relacionados en una variable compuesta. Esta t´ecnica ecnica tiene varias varias ventajas: 1. Clarific Clarifica a el c´ odigo mostrando que los datos definidos en la estructura odigo est´an an relacionados relaci onados ´ıntimamente. ıntimame nte. 2. Simplifica Simplifica el paso de datos a las funciones. funciones. En lugar de pasar muchas muchas variables separadas, ellas se pueden pasar como una variable. 3. Incremen Incrementar tar la localidad del c´odigo. odigo.1 Desde el punto de vista del ensamblador una estructura se puede considerar como un arreglo con elementos de diferente tama˜ no. no. Los elementos de los arreglos reales son siempre del mismo tipo y mismo tama˜ no. no. Esta caracter´ ter´ıstica ıstic a es la que le l e permite permi te a uno calcular ca lcular la l a direcci´ direccion o´n de cualquier elemento conociendo la direcci´on on de inicio del arreglo, el tama˜ no no de los elementos y el ´ındice del elemento que se desea. En una estructura los elementos no tienen que tener el mismo tama˜no no (normalmente no). Por esto cada elemento de una estructura debe ser definido expl´ expl´ıcitamente ıcita mente mediante un nombre, nombre, en lugar lug ar de un ´ındice ınd ice num´erico. eri co. En ensamblador los elementos de una estructura se acceder´an an de una manera parecida que los elementos de un arreglo. Para acceder a un elemento uno debe conocer la direcci´on on de inicio de la estructura y el desplazamiento relativo desde el comienzo de la estructura, sin embargo, a diferencia de los arreglos arreglos donde este desplazamien desplazamiento to se puede calcular por el ´ındice ındice del 1
vea la secci´ on de manejo de memoria virtual de cualquier libro de texto de sistemas on operativos para la discusi´ on on del t´ermino. ermi no.
145
CAP ´ITULO 7. 7. ESTRUCTU ESTRUCTURAS RAS Y C++
146
Offse Off sett 0 2
Elem Elemen entt x y
6 z
Figura 7.1: Estructura S elemento, al elemento de una estructura el compilador le asigna un desplazamiento. Por ejemplo considere la siguiente estructura: struct S short short int x ; int y; double z;
{
};
/ entero de 2 bytes / / entero de 4 bytes / / float float de 8 bytes bytes /
∗ ∗ ∗
∗ ∗ ∗
La Figura 7.1 muestra c´omo omo una variable de tipo S podr po dr´´ıa verse en la memoria del computador. La norma ANSI dicta que los elementos de una estructura son colocados en memoria en el mismo orden que ellos son definidos en la estructura. Tambi´ en en dicta que el primer elemento est´a al comienzo de la estructura (desplazamiento cero). Tambi´ en en define otro macro util ´ en 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 par´ ametros, el primero es el nombre de tipo de la estructura, ametros, el segundo del nombre del elemento al cual encontrarle el desplazamiento. As´ As´ı el resulta resu ltado do de offsetof(S,y) podr po dr´´ıa ser 2 seg´un un la Figura 7.1 7.1..
7.1.2. 7.1.2.
Aline Alineami amien ento to de la la memori memoria a
Si uno usa el macro offsetof para encontrar el desplazamiento de y usando el compilador gcc uno encontrar´a que retorna 4 y no 2. ¿Por qu´e? e? muchos otros compiladores compiladores)) alinean alinean por omisi´ on las variables Recuerde que una direcci´ on Porque gcc (y muchos est´ a en los l´ımites ımites de una a los l´ımites ımites de palabras palabras dobles. En el modo protegido de 32 bits, la CPU palabra doble si es divisible lee la memoria m´ as as r´apido apido si el dato comienza en el l´ımite de una palabra palabra por cuatro. doble. La Figura 7.2 muestra c´omo omo se ve realmente la estructura S usando gcc. gcc. El compilador inserta dos bytes no usados en la estructura para alinear e es buena idea y(y z) en un l´ımite de palabra doble. Esto muestra por qu´ usar offsetof para calcular los desplazamientos en lugar de calcularlos uno mismo cuando se usan estructuras definidas en C.
7.1. ESTRUCTUR ESTRUCTURAS AS
147 Offse Off sett 0 2 4
Elem Elemen entt x
unused y
8 z
Figura 7.2: Estructura S Claro est´ a, si la estructura solo se usa en ensamblador el programador a, puede determinar el desplazamiento ´el el mismo. Sin embargo si uno est´ a interfazando C y ensamblador es muy importante que los dos c´odig od igos os est´ es t´en en de acuerdo en los desplazamientos de los elementos de la estructura. Una complicaci´ on es que diferentes compiladores de C pueden tener desplazamientos on diferentes de los elementos. Por ejemplo, como se ha visto, el compilador gcc crea una estructura S que se ve como la Figura 7.2 7.2;; sin embargo, el compilador de Borland podr p odr´´ıa crear una estructura como la de la Figura 7.1. Los compiladores de C suministran maneras de especificar el alineamiento usado para los datos. Sin embargo embargo la norma ANSI C no espec´ espec´ıfica c´ omo omo se hace esto y as´ as´ı, cada compilador lo har´ a a su manera. El compilador gcc tiene un m´etodo etodo flexible y complicado de especificar esp ecificar el alineamiento. El compilador le permite a uno especificar el alineamiento de cualquier tipo tip o usando una sintaxis especial. Por ejemplo, la siguiente l´ l´ınea: typedef short int unaligned int
attribute
(( aligned aligned (1))); (1)));
Define un nuevo tipo llamado unaligned int que se s e alinea al inea en los l os l´ımites ımite s de los bytes (s´ı, ı, todos to dos los par´entesis entes is despu´ desp u´es es de attribute se necesitan). El 1 en el par´ametro ametro de aligned se puede reemplazar con otras potencias de dos para especificar especificar otros alineamiento alineamientoss (2 para alineamien alineamiento to de palabra, 4 para el alineamiento de palabras dobles, etc.). Si el elemento y de la estructura se cambia por un tipo unaligned int, gcc podr po dr´´ıa coloc col ocar ar y a una distancia de 2. Sin embargo z podr´ podr´ıa continuar a una distancia de 8 ya que las palabras dobles tienen un alineamiento alineamiento de palabra palabra por omisi´ on. on. La definici´ definici´ on on del tipo de z tendr´ tendr´ıa que ser cambiada para colocarlos una distancia de 6. El compilador gcc tambi´ ta mbi´en en perm p ermit itee embalar una estructura. estructura. Esto le dice al compilador compila dor que use el m´ınimo de espacio para la estructura. La Figura 7.3 muestra c´omo omo se podr po dr´´ıa escribir escribi r S de esta manera. De esta forma S usar´ıa el menor n´ umero de bytes posibles, 14. umero Los compiladores de Borland y Microsoft, soportan el mismo m´ etodo etodo de especificar especificar el alineamien alineamiento to usando usando la directiv directivaa #pragma.
CAP ´ITULO 7. 7. ESTRUCTU ESTRUCTURAS RAS Y C++
148
struct S / entero de 2 bytes / short short int x ; int y; / entero de 4 bytes / double z; / float float de 8 bytes bytes / attribute ((packed));
{
∗ ∗ ∗
}
∗ ∗ ∗
Figura 7.3: Estructura embalada usando gcc #pragma pack(push) / guarda guarda el estado estado de alineamiento alineamiento / #pragma pack(1) / establ establece ece el alieam alieamien iento to de byte byte /
∗ ∗
struct S short short int x ; y; int double z;
{
};
∗ ∗
/ entero de 2 bytes / / entero de 4 bytes / / float float de 8 bytes bytes /
∗ ∗ ∗
#pragma pack(pop)
∗ ∗ ∗
/ restau restaura ra el alinea alineamie miento nto origi original nal /
∗
∗
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 tructuras en los l´ımites de los bytes bytes (sin relleno extra). extra). El uno se puede reemplazar con dos, cuatro, ocho o diecis´ eis eis para especificar el alineamiento en una palabra, palabra doble, palabra cu´ adruple adruple y p´arrafo arrafo respectivamente. 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´ tendr´ıan por omisi´ on. on. Esto puede conducir un error muy dif´ dif´ıcil de encontrar. Los m´ odulos odulos de un programa pr ograma podr po dr´´ıan desordenar los elementos de las estructuras de otros m´ odulos. odulos. 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 c´omo omo se har´ıa ıa esto. est o.
7.1. 7.1.3. 3.
Campo Camposs de bits bits
Los campos de bit le permiten a uno especificar miembros de una estructura que solo usa un n´ umero especificado de bits. El tama˜ umero no no de los bits
7.1. ESTRUCTUR ESTRUCTURAS AS struct S unsigned unsigned unsigned unsigned
{
f1 f2 f3 f4
: : : :
3; 10; 11; 8;
};
149
/ / / /
∗ campo de 3 bits ∗/ ∗ campo de 10 bits ∗/ ∗ campo de 11 bits ∗/ ∗ campo campo de 8 bits bits ∗/
Figura 7.5: Ejemplo campo de bits no tiene que ser m´ ultiplo de 8. Un campo de bits miembro est´a definido ultiplo unsigned int o un int con dos puntos y luego el tama˜ como un unsigned no no en bits. La Figura 7.5 muestra un ejemplo. Esto define 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´a asignado a los bits menos significativos de esta palabra doble.2 Sin embargo, embargo, el formato formato no es tan simple, si uno mira c´ omo omo se almacenan los bits en memoria. La dificultad ocurre cuando los campos de bits se salen de los l´ımites del byte. byte. Ya que los bytes bytes en un procesador procesador little endian se invertir´an an en memoria. Por ejemplo, los campos de la estructura S se ver´an an en memoria. memoria. 5 bits f2l
3 bits f1
3 bits f3l
5 bits f2m
8 bits f3m
8 bits f4
La etiqueta f2l se refiere a los ultimos u ´ ltimos cinco bits (los 5 bits menos significativos) del campo f2 . La etiqueta f2m se refiere a los 5 bits m´as as significativos de f2 . Las l´ıneas verticales dobles muestran los l os l´ımites de los bytes. Si uno invierte todos los bytes, las partes de los campos f2 y f3 se colocar´ an an en el lugar correcto. La forma de la memoria f´ f´ısica no es importante a menos que los datos se transfieran hacia o fuera de nuestro programa (lo cual es muy poco com´ un un con campos de bits). Es com´ un para dispositivos de hardware usar n´ un umeros umeros impares de bits donde los campos de bits son ´utiles utiles para representarlos. 3 Un ejemplo es SCSI : Una orden de lectura directa para un dispositivo SCSI se especifica enviando un mensaje de 6 bits al dispositivo con el formato especificado en la Figura 7.6 7.6.. La dificultad de representar esto usando 2
Actualmente, la norma ANSI/ISO C le da al compilador alguna flexibilidad de como se ordenan los bits exactamente. Sin embargo los compiladores populares (gcc, Microsoft y Borland ) usar´ an este orden en los campos. an 3 Small Computer System Interface, es una norma de la industria para discos duros etc.
CAP ´ITULO 7. 7. ESTRUCTU ESTRUCTURAS RAS Y C++
150 Byte Bit 0 1 2 3 4 5
\
7
6
5 4 3 2 1 0 Codigo de operacion o´n (08h) Unidad Lo´gica # msb de LBA mitad de la direccion o´n del bloque l´ogico ogico lsb de direccion o´n del bloque l´ogico ogico Longitud de transferencia Control
Figura 7.6: Formato de la orden leer de SCSI campos de bits es la direcci´ on del bloque l´ ogico (LBA) que abarca 3 bytes diferentes 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 definici´ on on que intenta trabajar con todos to dos los compiladores. Las dos primeras l´ıneas definen un macro que retorna verdadero si el c´odigo odigo se compila con los compiladores de Borland o Microsoft. La parte potencialmente confusa est´ a en las l´ıneas 11 a 14. Primero uno podr po dr´´ıa sorprenderse sorprend erse de ¿Por qu´e los bits lba mid y lba lsb est´an an definidos separados y no en un solo campo de 16 bits? La raz´on o n es que el dato est´a en big endian. Un campo de 16 bits podr p odr´´ıa ser almacenado con el orden little endian por el compilador. Ahora, los campos lba msb y logical logical unit parecen invertidos; sin embargo esto no es el caso. Ellos tienen que colocarse en este orden. La Figura 7.8 muestra c´ omo omo se colocan los campos en una entidad de 48 bits (los l´ımites del byte est´ an an una vez m´as as marcados marcados con l´ıneas dobles). Cuando Cuando esto se almacena almacena en memoria memoria en el orden little endian, los bits se colocan en el orden deseado.(Figura 7.6 7.6)) Para complicar las cosas m´ as, as, la definici´on on para SCSI read cmd no trabaja correctamente en el compilador de Microsoft. Si se eval´ua ua la expresi´on on sizeof (SCSI read cmpl), cmpl), Microsoft C retornar´a 8 no 6. Esto es porque el compilador de Microsoft usa el tipo de campo de datos para determinar c´ omo omo colocar los bits. Ya que los bits est´ an definidos como de tipo unsigned, el an compilador rellena dos bytes al final de la estructura para tener un m´ ultiultiplo de palabras dobles. Esto se puede solucionar haciendo todos los campos unsigned short. Ahora el compilador de Microsoft no necesita agregar ning´ un un relleno ya 4 que los 6 bytes son un m´ ultiplo ultiplo de una palabra Los otros compiladores tambi´ tamb i´en en traba tra bajar jar´ a´n correctamente con este cambio. La Figura 7.9 muestra an otra definici´ on que trabaja con los tres compiladores. Se evitan todos los on problemas usando campos de tipo unsigned char. El lector no deber´ deber´ıa entristecerse si encuentra la divisi´ on on anterior confusa. ¡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. ESTRUCTUR ESTRUCTURAS AS
151
OR BORLAN BORLAND D (defin (defined( ed( BORLAN BORLANDC DC ) #define MS OR
||
defined( 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 logi logica call unit unit : 3; unsigned lba mid : 8; / bits bits de la mita mitad d / lba lsb lsb : 8; unsigned lba transfer er length length : 8; unsigned transf unsigned cont contro roll : 8;
{
∗
∗
}
#if defin defined( ed( GN GNUC UC ) attribute ((packed)) #endif
; #if MS OR BORLAND # pragma pack(pop) #endif
Figura 7.7: Estructura del formato de la orden leer de SCSI campos de bits y en su lugar usar las operaciones entre bits para examinar y modificar los bits manualmente.
7.1.4. 7.1.4.
Usando Usando estru estructur cturas as en en ensam ensamblad blador or
Como se discuti´ o antes, acceder a una estructura en ensamblador es muy parecido a acceder a un arreglo. Por ejemplo considere c´omo omo uno podr po dr´´ıa escribir una rutina en ensamblador que le asignar´a cero al elemento y de una estructura S. Asumiendo que el prototipo de la rutina ser´ıa: ıa: void zero y( S
∗ s p );
CAP ´ITULO 7. 7. ESTRUCTU ESTRUCTURAS RAS Y C++
152 8 bits con control trol
8 bits tran transf sfer er leng length th
8 bits lba lba lsb lsb
8 bits lba lba mid mid
3 bits logi logica call unit unit
5 bits lba lba msb msb
8 bits opc opcode ode
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 logi logica call unit unit : 3; / bits bits de la mita mitad d / unsigned char lba mid; unsigned char lba lsb; unsigned char transfer length ; unsigned char control;
{
∗
∗
}
#if defin defined( ed( GN GNUC UC ) attribute ((packed)) #endif
; Figura 7.9: Estructura alterna del formato de la orden leer de SCSI La rutina en ensamblador ensambla dor ser´ ser´ıa: 1 2 3 4
%define _zero_y: ente enter r mov
5 6 7 8
mov leave ret
y_offset
4
0,0 0,0 eax, [ebp + 8]
; toma s_p (apuntador a la estructura) ; de la pila dwor word [e [eax + y_off offset], t], 0
C le permite a uno pasar estructuras por valor a una funci´on; on; sin embargo esto la may mayor or´´ıa de las veces es mala idea. Cuando se pasa por valor, todos los datos de la estructura estructura se deben copiar en la pila y luego tra´ tra´ıdos por la rutina, En lugar de esto es mucho m´as as eficiente pasar un apuntador a una estructura. C Tambi´ ambi´en en permite que un tipo de estructura estructura se use como un valor de retorno. Obviamente la estructura no se puede retornar en el registro EAX. Cada compilador maneja la situaci´ on diferente. Una soluci´on on on com´ un un es que el compilador usa esto para internamente reescribir la funci´on on para que tome una apuntador a una estructura como par´ ametro. El apuntador se usa para ametro.
7.2. ENSAMBLA ENSAMBLADOR DOR Y C++
153
#include void f ( int x )
{ }
print printff (” %d n”, x);
\
void f ( double x )
{ }
print printff (” %g n”, x);
\
Figura 7.10: Dos funciones f() colocar el valor de retorno en una estructura definida fuera de la rutina llamada. La mayor´ mayor´ıa de los ensambladores (incluido NASM) tiene un soporte sop orte empotrado para definir estructuras en su c´odigo odigo de ensamblador. Consulte su documentaci´ on on para los detalles.
7.2. 7.2.
Ensa En sam mblad blador or y C++ C++
El lenguaje de programaci´ on C++ es una extensi´on on on del lenguaje C. Muchas de las reglas b´asicas asicas para interfaz interfazar ar C y ensamblador ensamblador tambi´ tambi´ en en son v´ alidas para C++. Sin embargo algunas reglas necesitan ser modificadas. alidas Tambi´ ambi´en en algunas algunas de las extensiones extensiones de C++ son f´ aciles aciles de entender con conocimiento del lenguaje ensamblador. Esta secci´ on asume un conocimienon to b´ asico asico de C++.
7.2.1. 7.2.1.
Manip Manipula ulaci´ ci´ on de la sobrecarga de nombres on
C++ permite que se definan diferentes funciones (y funciones miembro de clases) con el mismo nombre. Cuando m´ as as de una funci´on on comparte el mismo nombre, se dice que la funci´on on est´a sobrecargada . Si se definen dos funciones con el mismo nombre en C, el encadenador producir´ a un error porque po rque ´el el encontrar enco ntrar´ a´ dos definiciones para el mismo mi smo s´ımbolo ımbolo en los archivos archivos objeto que est´a encadenando. Por ejemplo considere el c´odigo odigo de la Figura 7.10. El c´ odigo odigo en ensamblador equivalente equivalente definir´ definir´ıa 2 etiquetas f que obviamente ser´a un error. C++ usa el mismo proceso de encadenamiento que C, pero evita este error haciendo una manipulaci´ on de nombres o modificando modi ficando el s´ımbolo usado para etiquetar la funci´on. on. De alguna manera C usa la manipulaci´ on on de
154
CAP ´ITULO 7. 7. ESTRUCTU ESTRUCTURAS RAS Y C++
nombres tambi´en. en. A˜ nade nade un gui´on o n bajo a la etiqueta de la funci´on o n de C cuando se crea la etiqueta para la funci´on. on. Sin embargo, C manipular´ a el nombre de ambas funciones en la Figura 7.10 de la misma manera y produce un error. C++ usa un proceso de manipulaci´on on m´ as sofisticado que produce as dos etiquetas diferentes para las funciones. Por ejemplo, a la primera funci´on on de la Figura 7.10 le podr´ p odr´ıa ıa asignar a signar DJGPP la etiqueta e tiqueta f Fi y a la segunda funci´ on, on, f Fd. Esto evita errores de encadenamiento. Desafortunadamente no est´a normalizado c´ omo se manejan los nombres omo en C++ y cada compilador trata los nombres a su manera. Por ejemplo Borland Borla nd C++ podr po dr´´ıa usar us ar las la s etiquetas eti quetas @f$qi y @f$qd para las dos funciones de la Figura 7.10. Sin embargo, las reglas no son completamente arbitrarias. El nombre manipulado codifica la firma de la funci´on. o n. La firma de una funci´ on on est´a definida por el orden y el tipo de sus par´ametros. Observe que la funci´on on que toma un solo par´ametro ametro int tiene una i al final de la etiqueta manipulada (para DJGPP y Borland) y la que toma un argumento double tiene una d al final de la etiqueta manipulada. Si tuviera una funci´ on on llamada f con el prototipo. void f ( int x , int y , double z);
DJGPP podr po dr´´ıa ma manipular nipular el nombre no mbre para ser f Fiid iid y Borland a @f$qiid. El tipo de retorno de una funci´ on on no hace parte de la firma de la funci´ on y no se codifica en el nombre manipulado. Este hecho explica una regla on de sobre carga en C++. S´olo olo funciones cuya firma es ´unica unica se pueden sobrecargar. Como uno puede ver, si dos funciones, con el mismo nombre y firma, est´ an definidas en C++ ellas producir´an an an el mismo nombre manipulado y crear´an an un error de encadenamiento. Por omisi´on on C++ manipula los nombres de las la s funciones funci ones as´ as´ı no est´en en sobrecarg so brecargadas. adas. Cuando se compila co mpila un archivo el compilador no tiene manera de saber si una funci´ on on en particular est´ a sobrecargada o no, as´ as´ı que manipula to dos los nombres. De hecho tambi´ en en manipula manipula los nombres nombres de las variables ariables globales globales codificando codificando el tipo de variable de manera similar a la firma de las funciones. As´ As´ı uno puede definir una variable global en un archivo como de cierto tipo y entonces intentar usarla en otro archivo como de un tipo err´oneo, oneo, se producir´a un error del encadenador. Esta caracter´ caracter´ıstica de C++ es conocida como encadenamiento seguro de tipos( tipos (typesafe linking . Tambi´ ambi´en en se expone a otro tipo de error, error, prototipos inconsistentes. Esto ocurre cuando la definici´ on on de una funci´on on en un m´odulo odulo no concuerda con el prototipo usado por otro m´odulo. odulo. En C esto puede ser un problema muy dif´ıcil ıcil de depurar. C no atrapa este error. El programa compilar´ a y encadenar´a, a, pero tendr´a un comportamiento indefinido cuando se invoca el c´odigo odigo colocando diferentes tipos de datos en la pila de los que la funci´on on espera. En C++, esto producir´a un error de encadenado. Cuando el compilador de C++ est´a examinando la sintaxis de un llamado
7.2. ENSAMBLA ENSAMBLADOR DOR Y C++
155
a funci´on, on, ´el el busca b usca una funci´ func i´on on donde emparejen los tipos de los argumentos 5 pasados a la funci´on. on. Si se empareja entonces crea un CALL a la funci´on on correcta usando las reglas de manipulaci´ on on del compilador. Ya que cada compilador usa sus reglas de manipulaci´ on on de nombres, los archivos objeto de C++ generados por diferentes compiladores no se pueden unir, el c´odigo odigo de C++ compilado con compiladores compiladores diferente diferentess no es posible unirlo. Este hecho es importante cuando se considera usar una biblioteca de C++ precompilada. Si uno desea escribir una funci´on on en ensamblador que ser´a usada con c´odigo odigo C++, debe conocer las reglas de manipulaci´on on de nombres del compilador (o usar la t´ecnica ecnica explicada abajo). El estudiante astuto puede preguntar si el c´ odigo odigo de la Figura 7.10 trabajar´ a como se espera. Ya que C++ manipula los nombres de todas las funciones, entonces la funci´on on printf ser´ a manipulada y el compilador no producir´a un CALL a la etiqueta printf. Esto es un v´alido. alido. Si el prototipo para printf fue colocado colo cado simplemente al principio del archivo, archivo, esto podr p odr´´ıa pasar. El prototipo es: int printf ( const char , ...) ..);
∗
DJGPP podr po dr´´ıa manipul m anipular ar esto est o como com o print on , P printf f FPCce FPCce (la F es por funci´ por apuntador , C por constante, constante, c por char y e por elipsis). elipsis ). Esto podr po dr´´ıa no llamar la funci´ on on printf de la biblioteca de C. Claro est´ a, a, debe haber alguna forma para el c´odigo odigo de C++ llamar c´ odigo de C. Esto es muy importante odigo ya que hay una gran cantidad de c´odigo odigo de C util. u ´til. Adem´ as as permitirle a uno llamar c´ odigo odigo de C heredado, C++ tambi´en en le permite a uno llamar c´ odigo odigo de ensamblador usando las convenciones normales de llamado. C++ extiende la palabra clave extern para permitir especificar que la funci´ on o variable global que modifica usa las convenciones de llamado de on C. En la terminolog terminolog´´ıa de C++, las variables ariables y funciones funciones globales usan el encadenam encadenamiento iento 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 manipulaci´ on on de nombres en esta funci´on, on, y en su lugar usar las reglas de C. Sin embargo, haciendo esto, la funci´ on on printf no se puede sobrecargar. Esto suministra una manera f´acil acil de interfazar C++ y ensamblador, define la funci´on o n para que use el encadenamiento de C y la convenci´ on on de llamado de C. Por conveniencia C++ tambi´ en en permite el encadenamiento de un bloque de funciones y variables globales a ser definidas. El bloque se enmarca por las t´ıpicas ıpi cas llaves: llave s: 5
El emparejamiento no tiene que ser exacto, el compilador considerar´ a las conversiones de tipos de los argumentos. Las reglas para este proceso est´an an m´ as as all´ a del alcance de este libro. Consulte un libro de C++ para los detalles.
CAP ´ITULO 7. 7. ESTRUCTU ESTRUCTURAS RAS Y C++
156 void f ( int & x )
// el & denota denota una refere referenci nciaa
{ x++; } int main()
{
int y = 5;
f(y);
// se pasa pasa la refe refere renc ncia ia a y, obese obeserv rvee que // no hay & ac´a print printff (” %d n”, y); // imprime 6! return 0;
\
} Figura 7.11: Ejemplo de referencia extern ”C”
{
/ encade encadenam namien iento to tipo tipo C a varia variable bless glo global bales es y proto prototipo tiposs de funcio funciones nes /
}
∗
Si uno examina los archivos de encabezado de ANSI C que vienen con los compiladores de C/C++ de hoy d´ d´ıa, en ellos se encontr´ a al principio de cada archivo de cabecera: #ifdef cplusplus extern ”C” #endif
{
Y una construcci´on on parecida cerca del final conteniendo el fin de la llave. Los compiladores de C++ definen el macro cplusplus (con dos guiones bajos adelante). La porci´ on o n de c´odigo odigo anterior encierra todo el archivo de cabecera en un bloque exter extern n 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 d ar´´ıa un error e rror de sintaxis s intaxis para ex ecnica ecnica puede ser exter tern n C). Esta misma t´ 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. 7.2.2.
Refe Referen rencia ciass
Las referencias son otra caracter´ caracter´ıstica nueva nueva C++. Ellas le permiten a uno pasar par´ametros ametros a funciones sin usar apuntadores expl´ expl´ıcitamente. Por ejemplo, considere el c´odigo odigo de la Figura Figura 7.11 7.11.. Los par´ametros ametros 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 par´ ametros ametros var como apuntadores). Cuando el compilador genera el ensamblador para el llamado a la funci´ on on en la l´ınea 7, pasa la
∗
7.2. ENSAMBLA ENSAMBLADOR DOR Y C++
157
direcci´ on de y. Si uno escribi´ o la funci´on on f en ensamblador, ensamblad or, ella se deber´ıa ıa 6 comportar como si el prototipo fuera : void f ( int
∗ xp);
Las referencias son s´olo olo una conveniencia que es especialmente util u ´ til para la sobrecarga de operadores. Esta es otra caracter´ caracter´ıstica de C++ que le l e permite a uno definir el significado de los operadores comunes en estructuras o tipos de clases. Por ejemplo, un uso com´ un es definir el operador m´as un as (+) para unir ob jetos tipo t ipo cadena. As´ As´ı, si a y b fueran cadenas, a + b debe eb er´ıa retornar la uni´ on on de las cadenas a y b. C++ llama una funci´on on para hacer esto (de hecho, esta expresi´on on podr´ıa ıa ser reescrita en la notaci´ on on de funci´on on como operator +(a,b)). Por eficiencia efici encia uno un o podr po dr´´ıa querer quere r pasar la l a direcci´ direcc i´ on on de los objetos cadena en lugar de pasarlos por valor. Sin referencias esto se podr´ po dr´ıa ıa hacer hace r como c omo operator requerir´ıa escribir en la operator +(&a,&b), pero esto requerir´ sintaxis del operador como &a + &b. Esto ser´ ser´ıa muy complicado y confuso. Sin embargo, usando referencias, uno puede escribir a + b que se ve muy natural.
7.2.3. 7.2.3.
Funciones unciones inline inline
Las funciones funciones inline son otra caracter caracte r´ıstica ıstic a de d e C++ C ++7 . Las funciones inline son hechas para reemplazar, los macros basados en el procesador que toman par´ ametros que son muy propensos a tener errores. Recuerde de C, ametros que escribir un macro que eleva al cuadrado un n´umero umero podr po dr´´ıa verse como: #define SQR(x) ((x) (x))
∗
Debido a que el preprocesador no entiende C y hace simples sustituciones, se requieren re quieren los par´entesis entesis para calcular calcul ar la l a respuesta re spuesta correcta en la mayor´ mayor´ıa de los casos. Sin embargo, a´ un un esta versi´on on no dar´a la respuesta correcta para SQR(X++). Los macros se usan porque ellos eliminan el trabajo extra de hacer un llamad a funci´on on para una funci´on on elemental. Como se demostr´ o en el cap´ıtulo ıtu lo de subprogramas realizar un llamado a funci´on on involucra varios pasos. Por cada funci´on on elemental, el tiempo que se toma en llamar la funci´ on on puede ser mayor que el tiempo necesario en realizar las operaciones en la funci´on. on. La funci´on on inline es una manera mucho m´as as amigable de escribir c´ odigo odigo que se vea como una funci´on on normal, pero que no hace el bloque de c´odigo odigo para CALL. En lugar de ello los llamados a funciones inline son reemplazados por el c´odigo odigo que realiza la funci´on. on. C++ le permite a una funci´on on ser inline 6
Claro est´ a ella podr´ıa ıa esperar declarar la funci´ on con encadenamiento C para evitar on la manipulaci´ on de nombres discutida en la secci´ on on on 7.2.1 7 Los compiladores de C a menudo soportan esta caracter´ caracter´ıstica como una extensi´ on on de ANSI C
CAP ´ITULO 7. 7. ESTRUCTU ESTRUCTURAS RAS Y C++
158 inline inline int inline f ( int x ) return x x;
{
∗ }
int f ( int x ) return x x;
{
∗ }
int main()
{
int y, x = 5; 5;
y = f(x); y = inli inline ne f (x); return 0;
} Figura 7.12: ejemplo inline colocando la palabra inline clave al frente de la definici´on on de la funci´on. on. Por ejemplo considere las funciones declaradas en la Figura 7.12. El llamado a la funci´on on f en la l´ınea 10 hace un llamado normal a una funci´ on o n (en ensamblador, asumiendo que x est´ a en la direcci´on on ebp-8 e y est´a en ebp-4:
1 2 3 4
push push call pop mov
dwor dword d [ebp [ebp-8 -8] ] _f ecx [ebp-4], eax
Sin embargo, el llamado a la funci´ on on inline f de la l´ınea ıne a 11 podr po dr´´ıa verse 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 funci´on on en l´ınea es r´apida. apida. No se colocan los par´ametros ametros en la pila, no se crea el marco de la pila ni se destruye, no se hace el salto. Segundo el llamado de las funciones en l´ınea usa menos c´ odigo. odigo. Este ultimo u ´ ltimo punto es verdad para este ejemplo pero no siempre es cierto. La principal desventaja de las funciones inline es que el c´odigo odi go en l´ınea no se encadena y as a s´ı el e l c´ c odigo o´digo de una funci´on on en l´ınea debe estar disponible disp onible
7.2. ENSAMBLA ENSAMBLADOR DOR Y C++ class Simple public :
159
{
Simple(); ˜Simple(); int get data() const; void set data( int ); private : int data; ;
// Construccto Construcctorr por omisi´ on // destructo destructor r // funciones funciones miembro miembro
// datos miembro
}
Simple::Simple() data = 0;
{
}
Simple::˜Simple() / cuerp cue rpo o vac va c´ıo /
{ ∗
∗ }
int Simple:: Simple:: get data data () const return data;
{
}
Simple:: set data ( int x ) void Simple::
{ data = x; } Figura 7.13: Una clase simple de C++
en todos los archivos que la usen. El ejemplo anterior prueba esto. El llamado de una funci´on on normal solo requiere el conocimiento de los par´ametros, ametros, el tipo de valor de retorno, la convenci´on on de llamado y el nombre de la etiqueta para la funci´on. on. Toda esta informaci´ on on est´a disponible en el prototipo de la funci´on. on. Sin embargo al usar funciones inline se requiere conocer todo el c´ odigo odigo de la funci´on. on. Esto significa que si cualquier parte de una funci´on on en l´ınea se cambia, todos los archivos fuentes que usen la funci´on on deben ser recompilados. Recuerde que para las funciones no inline, si el prototipo cambia, a menudo los archivos que usan la funci´on on no necesitan ser recompilados. Por todas estas razones, el c´ odigo odigo de las funciones en l´ınea se colocan normalmente en los archivos de cabecera. Esta pr´actica actica es contraria a la regla estricta de C las instrucciones de c´odigo odigo ejecutable nunca se colocan en los archivos de cabecera.
CAP ´ITULO 7. 7. ESTRUCTU ESTRUCTURAS RAS Y C++
160 void set data( Simple
{ object −>data = x; }
object , ∗ object
int x )
Figura 7.14: Versi´ on de C de Simple::set data() on 1 2 3
_set_data__6Simplei: push ebp mov ebp, esp
; nombre manipulador
4 5 6 7
mov mov mov
eax, ax, [eb [ebp p + 8] 8] edx, [e [ebp + 12] [eax], edx
; eax eax = ap apuntado ador al al ob objet jeto (t (this) ; ed edx = parametro a ´metro entero ; data tiene un desplazamiento de 0
8 9 10
leave ret
Figura 7.15: Salida del compilador para Simple::set data( int )
7.2. 7.2.4. 4.
En efecto, C++ usa la palabra clave this para acceder al apuntador de un ob jeto que est´ a dentro de la funci´ on miembro.
Clas Clases es
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 datos y funciones asociadas a ella. Considere la clase definida en la Figura 7.13 7.13.. Una variable de tipo Simple se podr po dr´´ıa ver tal t al como una estructura 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 par´ametro ametro oculto. oculto. Este par´ametro ametro es un apuntador al objeto al cual la funci´ on on pertenece. pertenece. Por ejemplo ej emplo considere conside re el e l m´etodo etod o set data de la clase Simple de la Figura 7.13. Si se escribi´o en C se ver´ ver´ıa como una funci´ funcion o´n que fue pasada expl´ expl´ıcitamente a un apuntador al objeto como muestra la Figura 7.14 7.14.. La opci´ on on -S en el compilador DJGPP (y en los compiladores gcc y Borland tambi´ tambi´en) en) le dice al compilador compilador que genere genere un archivo archivo que contenga contenga el lenguaje ensamblador equivalente del c´odigo odigo producido. Para DJGPP y gcc el archivo ensamblador finaliza con una extensi´on on .s y desafortunadamente usa la sintaxis del lenguaje ensamblador AT&T que es diferente de la 8
to doss . a menudo llamados en C++ funciones miembro o m´ as as generalmente m´etodo
7.2. ENSAMBLA ENSAMBLADOR DOR Y C++
161
sintaxis de NASM y MASM. MASM .9 (Los compiladores de Borland y Microsoft generan un archivo con extensi´on on .asm usando la sintaxis MASM). La Figura 7.15 muestra la salida de DJGPP convertida a la sintaxis de MASM y con comentarios a˜ nadidos para clarificar el prop´osito nadidos osito de las instruccion instrucciones. es. En la l a prime pr imera ra l´ınea, ıne a, observe obs erve que al m´etodo eto do set data se le asigna la etiqueta manipulada manipulada que codifica el nombre nombre del m´ etodo, etodo, el nombre nombre de la clase y los par´ ametros. ametros. El nombre nombre de la clase se codifica porque otras clases podr´ podr´ıan tener un m´etodo eto do llamado llama do set data y los l os dos m´etodos eto dos deben tener etiqueta diferente. Los par´ametros ametros se codifican tal que la clase pueda sobrecargar el m´etodo set data para tomar otros par´ ametros como es normal en las funametros ciones C++. Sin embargo, tal como antes, cada compilador codificar´ a esta informaci´ on diferente en la etiqueta manipulada. on Luego en las l´ıneas 2 y 3, aparece el pr´ ologo ologo t´ıpico de la funci´ on. on. En la l´ınea 5 se almacena almacena el primer primer par´ ametro a metro de la pila en EAX. Este no es el par´ ametro ametro x, es el par´ametro ametro oculto.10 que apunta al objeto sobre el cual act´ ua. ua. La l´ınea ınea 6 almacena al macena el par´ p ar´ ametro ametro x en EDX y la l´ınea 7 almacena almac ena EDX en la palabra doble a la que apunta EAX. Este es el miembro data del objeto ua, ua, que es el unico u ´ nico dato en la clase, se almacena con Simple sobre el cual act´ un desplazamiento cero en la estructura Simple. Ejemplo Esta secci´on on usa las ideas ideas del cap´ cap´ıtulo ıtulo para para crear crear una clase clase de C++ que representa un entero sin signo de tama˜no no arbitrario. Ya que el entero puede ser de cualquier tama˜no, no, ser´a almacenado en un arreglo de enteros sin signo (palabras dobles). Se puede hacer de cualquier tama˜ no no usando memoria din´ amica. Las palabras dobles se almacenan en orden inverso. amica. inverso .11 (la palabra palabra doble menos significativ significativaa es la palabra palabra doble que est´ a en la posici´ on on 12 0). La Figura 7.16 muestra la definici´on on de la clase Big int. . El tama˜ no no de un Big int se mide por el tama˜ no no del arreglo sin signo 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. 9
El sistema de compilador gcc incluye su propio ensamblador llamado gas . El ensamblador blador gas usa la sintaxis sintaxis AT&T AT&T y as´ as´ı el compilador compilador produce el c´ odigo en el formato de gas. Hay varias p´ aginas web que discuten las diferencias entre los formatos INTEL y aginas AT&T. Hay tambi´en en un programa m´ınimo llamado a2i (http://www.multimania.com/ placr/a2i.html), que convierte del formato AT&T al formato NASM 10 Como es normal nada est´ a oculto en el c´ odigo odigo ensamblador 11 ¿Por qu´e? e? Porque las operaciones de adici´ on siempre se comenzar´ on an an a procesar al inicio del arreglo y se mover´a hacia adelante. 12 Vea los archivos example para el c´odigo odigo completo de este ejemplo. El texto solo se referir´ a a alguna parte del c´ odigo. odigo.
CAP ´ITULO 7. 7. ESTRUCTU ESTRUCTURAS RAS Y C++
162 class Big int public :
{
/
∗ ∗ Par´ ametros: ∗ size − tama˜ no del entero expresado como el n´ umero de ∗ unsigned unsigned int normales normales valorr inic inicia iall de Big int int como como un ∗ initial value − valo unsigned unsigned int normal normal ∗ ∗/
explicit Big int ( size t size , unsigned initia initiall value value = 0);
/
∗ ∗ Par´ ametros ∗ size − tama˜ no del entero expresado como el n´ umero de unsigned unsigned int normale normales s ∗ valorr inic inicia iall de Big Big int int como como la cade cadena na que que ∗ initial value − valo ∗ almacena almacena la represen representaci´ taci´ on hexade hexadecim cimal al del valor valor . ∗/ Big int ( si size t size , initiall value value ); const char ∗ initia
Big int( const Big int int & big int int to copy copy ); ); ˜Big int (); // reto retorna rna el tama˜ tama˜ no de Big int (en t´ erminos erminos de unsigned int ) size size t size size () const; Big int int & operator = ( const Big int & big int to copy ); const Big friend Big int operator + ( const Big int & op1, const Big Big int & op2 ); ); friend Big int operator ( const Big int & op1, const Big int int & op2); op2); friend bool operator operator == ( const Big int & op1, Big int & op2 op2 ); const Big friend bool operator operator < ( const Big int & op1, const Big int int & op2); op2); friend ostream & operator << ( ostream & os, const Big Big int & op ); ); private :
−
size t unsigned
∗
size ; // tama˜ no del arreg arreglo lo sin signo signo number ; // apunta apuntado dorr al arreg arreglo lo sin singno singno que // almacena almacena el el valor valor
}; Figura 7.16: Definici´ Definici´ on on de la clase Big int
7.2. ENSAMBLA ENSAMBLADOR DOR Y C++
// Protot Prototipos ipos para para las rutina rutinass en extern ”C” int add big ints ( Bi Big int & int const Big int int const Big int int sub big ints ( Bi Big int & const Big int int const Big int int
{
163
ensam ensambla blado dor r res , & op1, op1, & op2); op2); res , & op1, op1, & op2); op2);
} int & op2) op2) inline Big int operator + ( const Big int & op1, const Big int
{
Big int int result result (op1. (op1. size size ()); ()); int res = add big big ints ints ( result result , op1, op1, op2); op2); if (res == 1) throw Big int :: Overflo Overflow w (); if (res == 2) Size mismatch() mismatch();; throw Big int :: Size return result ;
} inline Big int operator
{
− ( const Big int & op1, const Big int int
& op2) op2)
Big int int result result (op1. (op1. size size ()); ()); res = sub sub big ints ints ( resu result lt , op1, op1, op2); op2); int res if (res == 1) throw Big int :: Overflo Overflow w (); if (res == 2) throw Big int :: Size Size mismatch() mismatch();; return result ;
} Figura 7.17: C´ odigo odigo de la aritm´etica etica de la clase Big int
164
CAP ´ITULO 7. 7. ESTRUCTU ESTRUCTURAS RAS Y C++
Para simplificar este ejemplo, s´ olo se pueden sumar o restar instancias olo objeto con el mismo tama˜ no no La clase tiene tres constructores: El primero (l´ınea ınea 9) inicia la l a instancia de la clase usando un entero entero sin signo normal; el segundo segundo (l´ (l´ınea 18) inicia la instancia usando una cadena que contiene un valor en hexadecimal. El tercer constructor construc tor (l´ (l´ınea 21) es el constructor copia . Esta discusi´ on on se enfoca en c´omo omo trabajan los operadores de la suma y de la resta ya que es all´ all´ı donde se usa el lengua je ensamblador. La Figura 7.17 muestra las partes relevantes del archivo de encabezado para estos operadores. Ellas muestran c´omo omo se usan los operadores para llamar las rutinas en ensamblador. Ya que cada compilador usa sus reglas de manipulaci´ on on de nombres radicalmente diferentes para las funciones de los operadores, las funciones funciones en l´ınea de los operadores operadores se usan para establecer establecer el encadenaencadenamiento de rutinas en ensamblador. Esto hace relativamente f´acil acil portar a diferentes compiladores y es tan r´apido apido como los llamados directos. Esta t´ecnica ecnica tambi´en en elimina elimi na la necesidad necesid ad de lanzar una excepci´ excepc i´ on on desde ensamblador. ¿Por qu´ e, e, a pesar de todo, se usa el ensamblador ensamblador ac´ a? a? Recuerde que para realizar aritm´etica etica de precisi´ on o n m´ ultiple, el carry se debe mover de ultiple, una palabra doble a la siguiente para sumar. C++ (y C) no le permiten al programador acceder a la bandera de carry de la CPU. Para realizar la suma con C++ se podr´ıa ıa hacerlo recalculando la bandera de carry y condicionalmente sumar esto a la siguiente palabra doble. Es mucho m´as as eficiente escribir el c´odigo odigo en ensamblador donde se puede acceder a la bandera de carry y usando la instrucci´on on ADC que autom´ aticamente suma la bandera de aticamente carry. Por brevedad, s´olo olo se discutir´ a ac´ a la rutina de ensamblador add big ints. A continuaci´ on on est´ a el c´ odigo odigo de esta rutina (de big math.asm): 1 2 3 4
big math.asm segment .text global global add_big add_big_int _ints, s, sub_big_ sub_big_ints ints %define %define size_off size_offset set 0 %define %define number_o number_offs ffset et 4
5 6 7 8
%define %define EXIT_OK EXIT_OK 0 %define %define EXIT_OVE EXIT_OVERFL RFLOW OW 1 %define EXIT_SIZE_MISMATCH EXIT_SIZE_MISMATCH 2
9 10 11 12 13
; Par´ a amet metros ros para las rutina rutinas s ad add d y sub sub %define %define res ebp+8 ebp+8 %define %define op1 ebp+12 ebp+12 %define %define op2 ebp+16 ebp+16
7.2. ENSAMBLA ENSAMBLADOR DOR Y C++
165
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
add_big_ints: push ebp mov ebp, esp push ebx push esi push edi ; ; primero esi senala n ~ala a op1 op1 ; edi senala n ~ ala a op2 op2 ; ebx senala n ~ ala a res res mov esi, [op1] mov edi, [op2] mov ebx, [res] ; ; Asegu Asegurar rarse se que todos todos los 3 Bi Big_I g_Int nt tiene tienen n el mismo mismo tama tama~ no ~ ; mov eax eax, [es [esi + size_o e_offse fset] cmp eax eax, [ed [edi + size_o e_offse fset] jne sizes_not_equal ; op1.size_ != op2.size_ cmp eax eax, [eb [ebx + size_o e_offse fset] jne sizes_not_equal ; op1.size_ != res.size_
36 37 38 39 40 41 42 43 44 45 46
mov ecx, ; ; ahora ahora, , lo los s ; esi = ; edi = ; ebx = ; mov ebx ebx, mov esi esi, mov edi edi,
eax
; ecx = size of Big_int’s
regis registro tros s se~ nalan nalan a sus respecti respectivos vos arreglo arreglos s op1.number_ op2.number_ res.number_ [eb [ebx + number ber_off offset] [es [esi + number ber_off offset] [ed [edi + number ber_off offset]
47 48 49 50 51 52 53 54 55
clc xor ; ; bucl bucle e add_loop: mov adc mov
edx, edx de suma suma eax, [edi+4*edx] eax, [esi+4*edx] [ebx + 4*edx], eax
; borra bandera de carry ; edx = 0
CAP ´ITULO 7. 7. ESTRUCTU ESTRUCTURAS RAS Y C++
166 56 57
inc loop
edx add_loop
; no altera la bandera de carry
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
jc overflow ok_done: xor eax, eax jmp done overflow: mov eax, EXIT_O T_OVERF ERFLOW jmp done sizes_not_equal: mov mov eax, eax, EXIT EXIT_S _SIZ IZE_ E_MI MISM SMAT ATCH CH done: pop edi pop esi pop ebx leave ret big math.asm
; valor de retorno = EXIT_OK
Las l´ıneas ıneas 25 a 27 alma almacen cenan an los apunt apuntado adores res a los objetos objetos Big int pasados a la funci´ on en registro. Recuerde que las referencias realmente on son solo apuntadores. Las l´ıneas ıneas 31 a 35 aseguran que los tama˜ tamanos ˜ de los 3 objetos de los arreglos sean iguales (Observe que el desplazamiento de size se a˜ nade nade al apuntador apuntador para acceder acceder al dato miembro). miembro). Las l´ıneas 44 a 46 ajustan los registros para apuntar al arreglo usado por los objetos respectivos en lugar de los objetos en s´ı mismos. mismos. (Una vez m´as, as, el desplazamiento de nade al apuntador del objeto). nade number se a˜ El bucle en las l´ıneas 52 a 57 suma los enteros almacenados en los arreglos sumando primero la palabra doble menos significativa, luego la siguiente palabra doble y as´ı sucesivamente. La suma debe deb e realizarse en esta secuencia para la aritm´etica etica de precisi´ on extendida (vea la Secci´on on on 2.1.5). 2.1.5). La l´ınea 59 examin examinaa el desbord desborde, e, si hay hay desbord desbordee la bander banderaa de carry carry se deber deber´ıa fijar con la ultima u ´ ltima suma de la palabra doble m´as as significativa. Ya que las 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 final. 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´ expl´ıcitamente como en la l´ınea ınea 16. Esto es necesario por dos razones. Primero, no hay un construcctor de conversi´on on que convierta un unsigned int en un Big int. Segundo, solo se pueden sumar Big Int del mismo tama˜ no, esto hace la conversi´on no, on problem´ atica ati ca ya que q ue podr´ po dr´ıa ıa ser dif´ıcil ıci l conoc con ocer er a qu´e tama˜ tam a˜ no no convertir. Una implementaci´ on on m´ as as sofisticada de la clase deber deb er´´ıa permitir sumar ob jetos de cualquier tama˜ no. El autor no desea complicar este ejemplo implemenno.
7.2. ENSAMBLA ENSAMBLADOR DOR Y C++
167
#include ”big int.hpp” #include using namespace namespace std; int main()
{
try
{
Big int b(5,”800000000 b(5,”800000000000 0000a00 0a00b”); b”); Big int a(5, ”80 ”800000 00000000 00000010 0010230 230”); ”); Big Big int int c = a + b; b; cout << a << ” + ” << b << ” = ” << c << endl; for ( int i=0; i < 2; i++ ) c = c + a; a; cout << ”c = ” << c << endl;
{
}
cout << ”c 1 = ” << c Big int(5,1) << endl; Big int int d(5, ”123 ”123456 45678”) 78”);; cout << ”d = ” << d << endl; cout << ”c == d ” << (c == d) << endl; cout << ”c > d ” << (c > d) << endl;
}
−
−
catch( const char str str ) cerr << ”Caught: ” << str << endl;
}
∗
{
int :: Overflo Overflow w) catch( Big int cerr << ”Overflow” << endl;
}
{
catch( Big int :: :: Size Size mismatch mismatch ) cerr << ”Size mismatch” << endl;
{
}
return 0;
} Figura Figura 7.18: 7.18: Uso simpl simplee de Big int
168
CAP ´ITULO 7. 7. ESTRUCTU ESTRUCTURAS RAS Y C++
tandolo ac´ a. (Sin embargo exhorta al lector a hacer esto). a.
7.2.5. 7.2.5.
Heren Herencia cia y polim polimorfi orfismo smo
La herencia le permite a una clase heredar los lo s datos y m´etodos etodos de otra. Por ejemplo consider el c´odigo odigo de la Figura 7.19 7.19.. Ella muestra dos clases A y B, donde la clase B hereda de A. La salida del programa es: Tama~ no n o de a: 4 Desp Despla laza zami mien ento to de ad: ad: 0 Tama~ no n o de b: 8 De Desp splaz lazam amien iento to de ad: 0 Despl Desplaza azamie mient nto o de bd: 4 A::m() A::m()
Observe que los miembros ad de ambas clases (B hereda de A) est´ an an a la misma distancia. Esto es importante ya que la funci´ on on f puede ser pasada como un apuntador, a un objeto A a cualquier tipo derivado de A. La Figura 7.20 muestra el c´odigo odigo asm (editado) para la funci´ on on (generada por gcc). gcc). Observe que en e n la salida del m´etodo eto do m de A fue llamado por los objetos a y b. Desde ensamblador, uno puede ver que el llamado a A::m() est´ a empotrado en la funci´on. on. Para la programaci´ on realmente orientada a objetos, el on m´etodo eto do llamado llama do depender depend er´´ıa de qu´e tipo de objeto ob jeto se pasa a la funci´ on. on. Esto es conocido como polimorfismo. C++ tiene esta caracter´ caracter´ıstica apagada por omisi´ on. Uno usa la palabra clave virtual para habilitarla. La Figuon. ra 7.21 muestra c´omo omo las dos clases ser´ıan ıan cambiadas. Nada del otro ot ro c´ odigo odigo necesita ser cambiado. El polimorfismo puede implementarse de muchas maneras. Desafortunadamente la implementaci´ on on de gcc est´ a en transici´ on on al momento de escribir esto y es significativamente m´ as complicada que la imas plementaci´ on inicial. Interesado en simplificar esta discusi´ on on, on, el autor solo cubrir´a la implementaci´ on del polimorfismo que usan los compiladores de on Windows, Microsoft y Borland. Esta implementaci´ on o n no ha cambiado en muchos a˜ nos y probablemente no cambie en el futuro cercano. nos Con estos cambios, la salida del programa cambia: Tama~ no n o de a: 8 Desp Despla laza zami mien ento to de ad: ad: 4 Tama~ no n o de b: 12 De Despl splaz azami amien ento to de ad: 4 Desp Desplaz lazami amien ento to de bd: 8 A::m() B::m()
Ahora el segundo llamado a f invoca invo ca el e l m´eto et o do B::m() porque es pasado a un objeto B. Esto no es lo unico u ´ nico que cambia. El tama˜ no no de A es ahora 8 (y B es 12). Tambi´ en en el desplazamiento de ad es 4 no es 0. ¿Qu´ ¿Qu´e es un desplazamiento de cero? La respuesta a esta pregunta est´ a relacionada en c´omo omo es la implementaci´ on on del polimorfismo.
7.2. ENSAMBLA ENSAMBLADOR DOR Y C++
#include #include using namespace namespace std; class A public : cdecl m() void cdecl int ad;
{
{ cout << ”A::m()” << endl; }
}; class B : public A public : cdecl m() void cdecl int bd;
{ { cout << ”B::m()” << endl; }
}; void f ( A
{ }
∗p)
p >ad = 5; p em();
− −
int main()
{
A a; B b; cout << ”Tama˜no n o de a: ” << sizeof (a) (a) << ” Desplazamiento de ad: ” << offsetof(A,ad) << endl; cout << ”Tama˜no n o de b: ” << sizeof (b) (b) << ” Desplazamiento de ad: ” << offsetof(B,ad) << ” Desplazamiento de bd: ” << offsetof(B,bd) << endl; f(&a); f(&b); return 0;
} Figura Figura 7.19: Herencia Herencia simple simple
169
CAP ´ITULO 7. 7. ESTRUCTU ESTRUCTURAS RAS Y C++
170
1 2 3 4 5 6 7 8 9 10 11
_f__FP1A: push mov mov mov mov push call add leave ret
; nombre de la funcion o ´n manipulada manipulada 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 direccion o´n del objeto objeto a A::m( A::m() ) ; nombre del metodo e ´todo manip manipula ulado do para para
Figura 7.20: C´ odigo en ensamblador para la herencia simple odigo
class A public : virtua virtuall void void int ad;
{
cdecl cdecl m()
{ cout << ”A::m()” << endl; }
}; class B : public A public : cdecl m() virtua virtuall void void cdecl int bd;
{
{ cout << ”B::m()” << endl; }
}; Figura 7.21: Herencia Herencia polim´ polimorfica o´rfica
A::m( A::m() )
7.2. ENSAMBLA ENSAMBLADOR DOR Y C++ 1 2 3
171
?f@@YAXPAVA@@@Z: push ebp mov ebp, esp
4 5 6
mov mov
eax, [ebp+8] dword [eax+4], 5
; p->ad = 5;
mov mov mov push call add
ecx, [ebp + 8] edx, [ecx] eax, [ebp + 8] eax dword [edx] esp, 4
; ; ; ; ; ;
pop ret
ebp
7 8 9 10 11 12 13
ecx = p edx = apuntador a vtable eax = p empuja apuntador "this" llama primera funcion o´n en vtabl vtable e limpia la pila
14 15 16
Figura 7.22: C´ odigo en ensamblador para la funci´ odigo on on f() Una clase C++ que tiene cualquier cualquier m´ etodo etodo virtual virtual se le da un campo oculto extra que es un apuntador a un arreglo de apuntadores a m´etodos. etodos.13 . Esta tabla se llama a menudo vtable. 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 arbol a´rbol de herencia. herencia. Mirando en el c´odigo odigo ensamblador (Figura 7.22 7.22)) generado para la funci´ on on 7.19)) para el m´ etodo etodo virtual virtual del programa, programa, uno puede ver f (de la Figura 7.19 que la llamada al m´etodo etodo m no es a una etiqueta. etiqueta. La l´ınea 9 encuentr encuentraa la direcci´ on de la vtable de objeto. La direcci´on on o n del objeto se empuja en la pila en la l´ınea 11. La l´ınea 12 llama al m´etodo etod o virtual virtua l ramific´ ramificandose ´ en la 14 primera direcci´on on en la vtable. . Esta llamada no usa una etiqueta, salta a la direcci´ on on de c´ odigo odigo apuntada por EDX. Este tipo de llamadas es un ejemplo de v´ınculo tard´ıo . El E l v´ınculo ınc ulo tard tar d´ıo retraza ret raza la decisi´ deci si´ on on de qu´e m´etod et odoo llam ll amar ar hasta que el c´odigo odigo se est´e ejecutando. Esto le permite p ermite al a l c´odigo odigo llamar el m´ etodo etodo apropiado apropiado para el objeto. El caso normal normal (Figura (Figura 7.20 7.20)) fija una llamada a cierto m´etodo etodo es llamado prim pr imer er v´ıncu ın culo lo ya que aqu aq u´ı el m´etod et odoo est´a atado en el tiempo de compilaci´ on. on. 13
Para las clases sin m´ etodos etodos virtuales los compiladores de C++ siempre hacen la clase compatible con una estructura normal de C con los mismos miembros. 14 Claro est´ a, a, este valor ya est´ a en el registro ECX ´el el fue coloc col ocado ado en la l´ınea 8 y la l´ınea ınea 10 pod´ p od´ıa ıa ser quitado y la pr´ oxima oxima l´ınea cambiada para empujar ECX. El c´ odigo odigo no es muy eficiente porque se genera sin tener activadas las optimizaciones del compilador.
CAP ´ITULO 7. 7. ESTRUCTU ESTRUCTURAS RAS Y C++
172 class A public : virtua virtuall void void virtua virtuall void void int ad;
{
cdecl cdecl m1() m1() cdecl cdecl m2() m2()
{ cout << ”A::m1()” << endl; } { cout << ”A::m2()” << endl; }
}; // B here hereda da de A m2() m2() class B : public A public : virtua virtuall void void cdecl cdecl m1() m1() cout << ”B::m1()” << endl; int bd;
{
{
}
}; / ∗ imprim imprimee la vtable vtable de un objeto objeto dado dado ∗/ void print vtable ( A ∗ pa ) { // p ve a pa como como un arre arreglo glo de palab palabras ras dobles dobles unsigned ∗ p = reinterpret cast(pa); // vt ve la vtable vtable como como un arre arreglo glo de apuntado apuntadores res void ∗∗ vt = reinterpret cast(p[0]);
cout << hex << ”Direcci´ on on de la vtable = ” << vt << endl; for ( int i=0; i < 2; i++ ) cout << ”dword ” << i << ”: ” << vt[i] << endl;
// lllam lamado ado a funcio funciones nes virtua virtuales les de una una maner maneraa // EXTREMADAMENTE no portable void ( m1func m1func pointer)(A pointer)(A ); // functi function on pointer pointer varia variable ble m1func pointer = reinterpret cast(vt[0]); m1func pointer(pa); // Llama al m´etodo etodo m1 a trav´es es de un // apuntador a una funci´ on void ( m2func m2func pointer)(A pointer)(A ); // functi function on pointer pointer varia variable ble m2func pointer = reinterpret cast(vt[1]); m2func pointer(pa); // llllamado amado al m´etodo etodo m2 a trav´es es de // un apuntador a funci´ on
∗
∗
∗
∗
∗ ∗ ∗ ∗
} int main()
{
A a; B b1; B b2; cout << ”a: ” << endl; endl; print print vtable vtable (&a); (&a); cout << ”b1: ” << endl; print print vtable (&b); cout << ”b2: ” << endl; print print vtable (&b2); return 0;
} Figura 7.23: Un ejemplo m´ as as complicado
7.2. ENSAMBLA ENSAMBLADOR DOR Y C++
173
Figura 7.24: Representaci´ on on interna de b1 a: vtable vtable address address = 004120E8 004120E8 dword dword 0: 0040132 00401320 0 dword dword 1: 0040135 00401350 0 A::m1() A::m2() b1: vtable vtable address address = 004120F0 004120F0 dword dword 0: 004013A 004013A0 0 dword dword 1: 0040135 00401350 0 B::m1() A::m2() b2: vtable vtable address address = 004120F0 004120F0 dword dword 0: 004013A 004013A0 0 dword dword 1: 0040135 00401350 0 B::m1() A::m2()
Figura 7.25: Salida del programa de la Figura 7.23 El lector atento se sorprender sorprender´ a´ por qu´e los m´etodos etod os de la clase en la Figura 7.21 est´an an declarados decl arados expl´ expl´ıcitamente ıcita mente para par a usar la conveci´ co nveci´ on on de llamado de usando la plabra clave cdecl. Por omisi´ on, Microsoft usa una convenon, ci´ on de llamado diferente a la convenci´on on on normal de C para los l os m´etodos etodos de ´ pasa un apuntador al objeto sobre el que act´ las clases de C++. El ua u a el m´etodo etod o en el registro regist ro ECX en lugar de usar la pila. La pila se sigue usando para los otros par´ametros ametro s expl´ expl´ıcitos ıcito s del m´etodo. etod o. El modificador modi ficador cdecl le dice que use la convenci´on on de llamado est´ andar de C. Borland C++ usa la andar convenci´on on de llamado de C por omisi´ on. on. Ahora miraremos un ejemplo un poco m´as as complicado (Figura 7.23 7.23). ). En ´el, el, las clases A y B cada una tiene dos m´etodos etodos m1 y m2. Recuerde que ya que la clase B no define su propio m´etodo etodo m2, hereda el m´etodo etodo de la 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 direcci´ on on de vtable para cada objeto. Las dos direcciones de los objetos B son iguales y as´ as´ı, ellos comparten 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
CAP ´ITULO 7. 7. ESTRUCTU ESTRUCTURAS RAS Y C++
174
apuntado apu ntadorr al a l m´etodo eto do m1 es un desplazamiento cero (o palabra doble 0) y m2 es un desplazamiento de 4 (palabra (pala bra doble 1). Los apuntadores al m´etodo etodo m2 son los mismos vtables de las clases A y B porque la clase B hereda here da el m´etodo eto do m2 de la clase A. Las l´ıneas ıneas 25 a 32 muestran como uno podr´ p odr´ıa ıa llamar l lamar a una un a funci´ fun ci´ on on virtual 15 leyendo su direcci´on on de la vtable para el objeto. . La direcci´on on del m´etod et odoo se se almacena en un apuntador a funci´on on de tipo C con un apuntador expl´ expl´ıcito this. this. De la salida en la Figura 7.25 7.25,, uno puede ver que esto trabaja. Sin embargo, por favor no escriba c´odigo odigo como este. Es solo para ilustrar c´omo omo los m´etodos etodos virtuales usan la vtable. Hay algunas lecciones pr´acticas acticas para aprender de esto. Un hecho importante es que uno tendr´ tendr´ıa que tener mucho cuidado cuando lee y escribe variables de una clase a un archivo binario. Uno no puede s´oo oo usar una lectura lectura o escritura escritura binaria en un objeto completo completo ya que esto po dr´ dr´ıa leer o escribir la apuntador vtable al archivo. Este es un apuntador que se˜nala nala a donde la vtable reside en la memoria del programa y variar´ a de programa a programa. Este mismo problema puede ocurrir con estructuras, pero en C, las estructuras s´olo olo tienen apuntadores en ellas si el programador las coloca expl´ expl´ıcitamente en ellas. No hay apuntadores obvios definidos en las clases A o B. Otra vez, es importante tener en cuenta que cada compilador implementa los m´ etodos etodos virtuales a su manera. En Windows, los objetos o bjetos de las clases COM (Component Object Model) usan las vtables para implementar interfaces COM16 . S´ olo olo los compiladores que implementan m´etodos etodos virtuales como Microsoft pueden crear clases COM. Esta es la raz´on on por la cual Borland usa la misma implementaci´ on que Microsoft y una de las principales on razones por las cuales gcc no se puede usar para crear clases COM. El c´ odigo odigo para el m´etodo etodo virtual parece ex´actamente actam ente como un m´etodo eto do no virtual. S´olo olo el c´ odigo que llama es diferente. Si el compilador puede estar odigo absolutamente absol utamente seguro de qu´e m´etodo eto do virtual ser´ a llamado, puede ignorar la vtabley vtable y llamar l lamar el m´etodo eto do directamente, directa mente, (usando el primer v´ınculo). ınculo ).
7.2.6.
Otras caracter caracter´ ´ısticas de C++ C++
Las otras caracter´ caracter´ısticas de C++ (informaci´ on en tiempo de ejecuci´ on on, on, manejo de excepciones y herencia m´ ultiple) ultiple) est´ an a n m´ as as all´ a del alcance de este texto. Si el lector desea avanzar m´ as, un buen punto de partida es The as, Annotated C++ Reference Manual por Ellis and Stroustrup y The Design and Evolution of C++ por Stroustrup. 15
Recuerde que este c´odigo odigo solo trabaja con los compiladores de Borland y MS, no para
gcc. 16
Las clases COM tambi´ en en usan la convenci´ on de llam llamado ado code code de C.
stdcal stdcalll no la norma normall
Ap´ endice A
Instrucciones del 80x86 A.1. A.1.
Instruc Instruccio ciones nes para para enter enteros os
Esta secci´on on lista y describe las acciones y formatos de las instrucciones para enteros de la familia de Intel 80x86 Los formatos formatos usan las siguientes siguientes abreviaturas: abreviaturas: R R8 R166 R1 R322 R3 SR M M8 M166 M1 M322 M3 I
regis gistro gen general regi regist stro ro de 8 bits its regis registr troo de 16 bits bits regis registr troo de 32 bits bits regi regist stro ro de segm segmen ento to memoria byte pala palabr braa palab palabra ra dobl doblee valor inmediato
Las abreviaciones se pueden combinar para las instrucciones con varios operandos. Por ejemplo el formato R,R significa que la instrucci´on on toma dos operandos de registro. Muchas de las instrucciones con dos operandos permiten los mismos operandos. La abreviaci´ on on O2 se usa para representar los siguientes operandos: R,R 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 abreviaci´ on on R/M8 La tabla tambi´en en muestra c´ omo afectan las instrucciones varias de las omo banderas del registro FLAGS. Si la columna est´ a en vac´ vac´ıa, el bit corresponcorresp ondiente no se afecta. Si el bit siempre cambia a alg´ un valor en particular, se un muestra un 1 o un 0 en la columna. Si el bit cambia a un valor que depende del operando de la instrucci´on on se coloca una C en la columna. Finalmente, si el bit es modificado de alguna manera no definida se coloca un ? en 175
´ AP ENDICE ENDI CE A. INSTRUCCIO INSTRUCCIONES NES DEL 80X86 80X86
176
la columna. Ya que las unicas u ´nicas instrucciones que cambian las banderas de direcci´ on on son CLD y STD, no se listan bajo la columna de FLAGS.
Nombre ADC ADD AND BSWAP CALL CBW CDQ CLC CLD CMC CMP CMPSB CMPSW CMPSD CWD CWDE DEC DIV ENTER IDIV IMUL
Descripci´ on Suma con carry Suma enteros AND entre bits Byte Swap Llamado a rutina Convierta byte a palabra Con Conviert iertee Dword ord a Qword Borra el Carry borra la bandera de direcci´ on on 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 Divisi´ on sin signo Hace el marco de la pila Divisi´ on con signo Multiplicaci´ on con signo
Formatos O2 O2 O2 R32 RMI
O C C 0
Banderas S Z A P C C C C C C C C 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
? ?
? ?
? ?
? ?
? C
C
C
C
C
R16,R/M16,I R32,R/M32,I
INC INT JA
Incremento entero Genera Genera una inter interrup rup-ci´ on on Salta si est´ a sobre
C C C 0
RM I I
C
A.1. INSTRUCCIONES INSTRUCCIONES PARA PARA ENTEROS ENTEROS
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
Descripci´ on Salta si est´ a sobre o es igual Salta si est´ a ba jo Salta si est´ a bajo o es igual Salta si hay carry Salta si CX = 0 Salta si es igual Salta si es mayor Salta lta si es mayor o igual Salta si es menor Salta lta si es menor o igual Salto lto incondicio icion nal Salta si no est´a sobre Salta si no est´ a sobre o es igual Salta si no est´a ba jo Salta si no est´ a bajo o es igual Salta alta si no hay hay carr carry y Salta si no es igual Salta lta si no es mayor Salta si no es mayor o es igual Salta lta si no es menor Salta si no es menor o igual Salta si no hay desborde Salta si es pos positivo Salta si no es cero Salt Saltaa si hay hay desb desbor orde de Salt Saltaa si hay hay pari parida dad d par Salt Saltaa si hay hay pari parida dad d impar Salta si hay signo Salta si hay cero
177
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
O
Banderas S Z A P
C
´ AP ENDICE ENDI CE A. INSTRUCCIO INSTRUCCIONES NES DEL 80X86 80X86
178
Nombre
LODSB LODSW LODSD LOOP LOOPE/LOOPZ LOOPNE/LOOPNZ MOV
Descripci´ on Carga FLAGS en AH Carga direcci´ direcci´ on on efectiva Abandona el marco de la pila Carga byte Carga palabra Carga palabra doble Bucle Bucle si es igual Bucle si no es igual Mueve datos
MOVSB MOVSW MOVSD MOVSX
Mueve byte Mueve palabra Mueve palabra doble Mueve con signo
MOVZX
Mueve sin signo
MUL NEG NOP NOT OR POP
Multiplicaci´ on sin signo Negaci´ on No Opera Complemento a 1 OR entre bits Saca de la pila
POPA POPF PUSH
Saca todo de la pila Saca FLAGS Empuja en la pila
PUSHA PUSHF RCL
Empuja todo en la pila Empuja FLAGS Rota a la izquierda izquierda con carry
LAHF LEA LEAVE
Banderas S Z A P
C
R16,R/M8 R32,R/M8 R32,R/M16 R16,R/M8 R32,R/M8 R32,R/M16 RM C
?
?
?
?
C
RM
C
C
C
C
C
C
0
C
C
?
C
0
C
C
C
C
C
C
Formatos
O
R32,M
I I I O2 SR,R/M16 R/M16,SR
RM O2 R/M16 R/M32
R/M16 R/M32 I
R/M,I R/M,CL
C
C
A.1. INSTRUCCIONES INSTRUCCIONES PARA PARA ENTEROS ENTEROS
Nombre
REP REPE/REPZ REPNE/REPNZ RET ROL
Descripci´ on Rota a la derecha con carry Repite Repite si es igual Repite si no es igual Retorno Rota a la izq izquierda
ROR
Rota a la derecha
SAHF SAL
Copia AH en FLAGS Dezp Dezplaz lazam amien iento to a la izquierda Resta con pr´estamo busca un Byte Busca una palabra Busca una palabra doble fija sobre fija sobre o igual fija ba jo fija ba jo o igual fija el carry fija igual fija mayor fija mayor o igual fija menor fija menor o igual fija no sobre fija no sobre o igual fija no ba jo fija no bajo ajo o igual fija no carry fija no igual fija no mayor fija no ma may yor o igu igual fija no menor fijan fijan no me meno norr o igua iguall fija no desborde fija no signo
RCR
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
179 Banderas S Z A P
Formatos R/M,I R/M,CL
O C
R/M,I R/M,CL R/M,I R/M,CL
C
C
C
C
R/M,I R/M, CL O2
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 R/M8 R/M8
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
´ AP ENDICE ENDI CE A. INSTRUCCIO INSTRUCCIONES NES DEL 80X86 80X86
180
Nombre
SUB TEST
Descripci´ on fija no cero fija desborde fija paridad par fija paridad impar fija signo fija cero Desplazamiento arit aritm´ m´ etic e ticoo a la dederecha Desplazamie Desplazamiento nto l´ ogico ogico a la derecha Desplazamie Desplazamiento nto l´ ogico ogico a la izquierda fija el carry fija fija la band bander eraa de didirecci´ on on almacena byte almacena palabra almace alm acena na palabr palabraa doble resta comparaci´ on on l´ ogica
XCHG
Intercambio
XOR
XOR entre bits
SETNZ SETO SETPE SETPO SETS SETZ SAR
SHR SHL STC STD STOSB STOSW STOSD
Formatos R/M8 R/M8 R/M8 R/M8 R/M8 R/M8 R/M,I R/M, CL
O
Banderas S Z A P
C
C
R/M,I R/M, CL R/M,I R/M, CL
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
0
C
C
?
C
0
A.2. INSTRUCCIONES INSTRUCCIONES DE PUNTO PUNTO FLOTANTE FLOTANTE
A.2. A.2.
181
Instruc Instruccio ciones nes de pun punto to flotan flotante te
En esta secci´on, on, se describen muchas de las instrucciones del coprocesador matem´ atico del 80x86. La secci´ atico on on de descripci´on on describe brevemente la operaci´on on de la instrucci´on. on. Para ahorrar espacio, la informaci´ on on sobre si la instrucci´ on saca de la pila no se da en la descripci´ on on. on. La columna de formato muestra que tipo de operandos se pueden usar con cada instrucci´on. on. Se usan las siguientes abreviaturas STn STn F D E I16 I16 I32 I32 I64 I64
Un registro del coprocesador n´ umero umero de precisi´on on simple en memoria n´ umero umero de precisi´on on doble en memoria n´ umero umero de precisi´on on extendida en memoria pala palabr braa ente entera ra en me memo mori riaa pala palabr braa dob doble le ente entera ra en me memo mori riaa pala palabr braa cu´ adruple entera en memoria adruple
Las instrucciones que requieren un Pentium Pro o posterior est´an an mar* cadas con un asterisco ( ). Instrucci´ on FABS FADD src FADD dest, dest, ST0 FADDP dest [,ST0] FCHS FCOM src FCOMP src FCOMPP src FCOMI* src FCOMIP* src FDIV src FDIV dest, dest, ST0 FDIVP dest [,ST0] FDIVR src FDIVR dest, dest, ST0 FDIVRP dest [,ST0] FFREE dest FIADD src FICOM src FICOMP src FIDIV src FIDIVR src
Descripci´ on ST0 = ST0 ST0 ST0 += src dest += STO STO ST0 dest += ST0 ST0 = ST0 Compara ST0 y src Compara ST0 y src Compara ST0 y ST1
| |
−
Compara en FLAGS Compara en FLAGS ST0 ST0 /= src dest /= STO STO dest /= ST0 ST0 ST0 = src /ST0 ST0/dest dest = ST0/ dest ST0/dest dest = ST0/ dest
Marca como vac´ıo ST0 ST0 += src Compara ST0 y src Compara ST0 y src STO STO /= src STO = src /ST0
Formato STn STn F D STn STn STn STn STn STn F D STn STn F D STn STn STn STn F D STn STn STn STn STn STn F D STn STn STn STn STn I16 I32 I16 I32 I16 I32 I16 I32 I16 I32
´ AP ENDICE ENDI CE A. INSTRUCCIO INSTRUCCIONES NES DEL 80X86 80X86
182 Instrucci´ on FILD src FIMUL src FINIT FIST dest FISTP dest FISUB src FISUBR src FLD src FLD1 FLDCW src FLDPI FLDZ FMUL src FMUL dest, dest, ST0 FMULP dest [,ST0] FRNDINT FSCALE FSQRT FST dest FSTP dest FSTCW dest FSTSW dest FSUB src FSUB dest, dest, ST0 FSUBP dest [,ST0] FSUBR src FSUBR dest, dest, ST0 FSUBP dest [,ST0] FTST FXCH dest
Descripci´ on Push src on Stack ST0 ST0 *= src
Inicia el coprocesador almacena ST0 almacena ST0 ST0 ST0 -= src ST0 = src - ST0
empuja src en la pila empuja 1.0 en la pila Load Loa d Con Contro troll Word Word Registe Registerr empuja π en la pila empuja 0.0 en la pila ST0 ST0 *= src dest *= STO dest *= ST0 Redondea ST0 ST0 = ST0 2ST1 ST0 = STO almacena ST0 almacena ST0
Formato I16 I32 I64 I16 I32 I16 I32 I16 I32 I64 I16 I32 I16 I32 STn F D E I16
STn STn F D STn STn STn STn
√ ×
Store Store Cont Control rol Word Word Registe Registerr Stor Storee Statu Statuss Word ord Regis Registe terr ST0 ST0 -= src dest -= STO dest -= ST0 ST0 = src -ST0 ST0-dest dest = ST0dest dest = ST0ST0-dest dest Compare ST0 with 0.0 Intercambia ST0 y dest
STn STn F D STn STn F D E I16 I16 I16 AX AX STn STn F D STn STn STn STn STn STn F D STn STn STn STn STn STn
´Indice alf alfab´ etico ADC, 39 39,, 56 ADD, 13, 39 AND, 52 archivo de listado, 25 25––26 archivo esqueleto, 27 Aritm´etica etica de complem c omplemento ento a dos, 35 array1.asm, 101 101––105 arreglos, 97 97––118 acceso, 99 99––105 defininir est´atico, atico, 97 variable local, 98 definir, 97 97––98 multidimensional, 105 105––108 dos dimensiones, 105 105––106 par´ametros, ametros, 107 107––108
herencia, 168 168––174 manipulaci´ on on de nombres, 153 153–– 156 polimorfismo, 168 168––174 primer pri mer v´ıncul ın culo, o, 171 referencias, 156 156––157 v´ıncu ın culo lo tard´ ta rd´ıo, ıo , 171 vtable, 168 168––174 CALL, 71 71––72 CBW, 33 CDQ, 33 CLC, 39 CLD, 108 CMP, 40 CMPSB, 111, 112 CMPSD, 111 111,, 112 CMPSW, 111 111,, 112 bandera bandera de paridad, paridad, 42 codigo de operaci´ on, on, 12 binario, 1–2 COM, 174 suma, 2 compilador, 6, 12 BSWAP, 61 Borland, 23 23,, 24 bucle do while, 45 45––46 DJGPP, 23, 24 bucle while, 45 gcc, 23 byte, 4 attribu attribute te , 86 86,, 147, 148, 151 151,, 152 C driver, 20 Microsoft, 24 C++, 153 153––174 embalado pragma, 147, 148, 151 151,, clases, 160 160––174 152 ejemplo Big int, 161 161––168 Watcom, 86 encadenamiento seguro de tipos, complemento a dos, 30 30––32 154 arit ar itm´ m´etic et ica, a, 39 extern ”C”, 155 155––156 constructor copia, 164 funciones inline, 157 157––159 62––66 funciones miembro, v´ease m´eto- contando bits, 62 dos m´etod et odoo dos, do s, 63 63––65 183
184
´INDICE ALFABETICO ´
m´eto et o do tres tr es,, 65 65––66 m´eto et o do uno, un o, 62 62––63 convenci´on on de llamado, 67, 72 72––80 80,, 86 86–– 87 cdecl, 87 stdcall, 87 C, 22, 74 74,, 83 83––87 etiquetas, 84 par´ ametros, ametros, 85 registros, 84 valores de retorno, 86 llamado estandar, 87 Pascal, 74 registro, 87 stdcall, 74, 86 86,, 174 coprocesador de punto flotante, 127 127–– 142 carga y almacenamiento de datos, 128 comparaciones, 131 131––132 hardware, 127 multiplicaci´ on on and divisi´ on, on, 130 130–– 131 suma y resta, 129 129––130 CPU, 5–7 80x86, 6 CWDE, 33
ejecuci´ on on especulativa, 55 encadenando, 25 encadenar, 24 endianess, 26 26,, 60 60––62 invert endian, 62 ensamblador, 12 enteros, 29 29––40 bit de signo, 29, 33 comparaciones, 40 con signo, 29 29––32, 40 divisi´on, on, 36 36––37 extensi´ on on del signo, 32 32––35 multiplicaci´ on, on, 35 35––36 precisi´ on on extendida, 39 representaci´ on, on, 29 29––35 complemento a dos, 30 magnitud y signo, 29 representaci´ on on de complemento a uno, 30 representaci´ on on en complemento a dos, 32 sin signo, 29 29,, 40 estructuras, 145 145––153 alineamiento, 146 146––148 campos de bit, 151 campos de bits, 148 offsetof(), 146 etiqueta, 16 16––17
decimal, 1 depuraci´ on, on, 18 18––19 direccionamiento indirecto, 67 67––68 arreglos, 100 100––105 directiva, 14 14––16 %define, 14 DX , 97 datos, 15 15––16 equ, 14 extern, 80 global, 23 23,, 80, 83 RESX RESX , 97 TIMES, 97 DIV, 36 36,, 50
f´ ormula ormula cuadr´ atica, atica, 133 FABS, 133 FADD, 129 FADDP, 129 FCHS, 133 FCOM, 131 FCOMI, 132 FCOMIP, 132 132,, 143 FCOMP, 131 FCOMPP, 131 FDIV, 131 FDIVP, 131 FDIVR, 131 FDIVRP, 131
´INDICE ALFABETICO ´ 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, 161 GHz, 6 hexadecimal, 3–4 I/O, 17 17––19 asm io library, 19 dump math, 19 dump mem, 18 dump regs, 18 dump stack, 19 biblioteca asm io, 17 print char, 18 print int, 18
185 print nl, 18 print string, 18 read char, 18 read int, 18 IDIV, 37 immediato, 13 impl´ im pl´ıcit ıc ito, o, 13 IMUL, 35 35––36 instrucci´ on on if, 44 44––45 instrucciones de cadena, 108 108––118 interfazando con C, 83 83––91 interrupci´on, on, 11 JC, 42 JE, 43 JG, 43 JGE, 43 JL, 43 JLE, 43 JMP, 41 JNC, 42 JNE, 43 JNG, 43 JNGE, 43 JNL, 43 JNLE, 43 JNO, 42 JNP, 42 JNS, 42 JNZ, 42 JO, 42 JP, JP, 42 JS, 42 JZ, 42 LAHF, 132 LEA, 85, 105 leer archivos, 136 lenguaje de m´ aquina, aquina, 5, 11 lenguaje ensamblador, 12 12––13 localidad, 145 LODSB, 109 LODSD, 109
186
´INDICE ALFABETICO ´
LODSW, 109 LOOP, 44 LOOPE, 44 LOOPNE, 44 LOOPNZ, 44 LOOPZ, 44
ensamblador, 54 54––55 OR, 53 XOR, 53 operciones con bits NOT, 53 OR, 53
m´eto et o dos, do s, 160 MASM, 12 math.asm, 37 37––39 memoria, 4–5 p´aginas, aginas, 10 segmento, 9, 10 virtual, 10 memory.asm, 113 113––118 modo protegido protegido 16 bits, 10 16-bits, 9 32-bits, 10 10––11 modo real, 9 MOV, 13 MOVSB, 110 MOVSD, 110 MOVSW, 110 MOVSX, 33 MOVZX, 33 MUL, 35 35––36 36,, 50, 105
pila, 70 70––80 par´ametros, ametros, 72 72––75 variables locales, 78 78––80, 85 predicci´ on on de ramificacione ramificaciones, s, 55 55––56 prime.asm, 46 46––48 prime2.asm, 138 138––142 programas multim´ odulo, odulo, 80 80––83 punto flotante, 119 119––142 arit ar itm´ m´etic et ica, a, 124 124––126 representaci´ on, on, 119 119––124 desnormalizada, 123 IEEE, 121 121––124 precisi´ on on doble, 123 precisi´ on on simple, 123 presici´ on on simple, 122 uno oculto, 122 representaci´ oon oon precisi´ on on doble, 124 quad.asm, 136
RCL, 51 RCR, 51 read.asm, 138 recursi´ on, on, 91 91––94 registro, 5, 7–8 ´ındic nd ice, e, 7 32-bit, 8 operaciones con bits AND, 52 apuntador a la pila, 7, 8 apuntador base, 7, 8 C, 58 58––60 desplazamientos, 49 49––52 EDI, 109 EDX:EAX, 36 36,, 39 39,, 86 desplazamiento l´ ogicos, ogicos, 50 EFLAGS, 8 desplaz desp lazami amiento entoss aritm´ ari tm´eticos eti cos,, 50 50–– 51 EIP, 8 desplazamientos l´ ogicos, ogicos, 49 ESI, 109 rotaciones, 51 FLAGS, 8, 40 NASM, 12 NEG, 58 nem´ onico, onico, 12 nibble, 4 NOT, 54
´INDICE ALFABETICO ´ CF, 40 DF, 108 OF, 40 PF, 42 SF, 40 ZF, 40 IP, IP, 8 segmento, 8, 110 reloj, 6 REP, 111 REPE, 112, 113 REPNE, 112, 113 REPNZ, v´ease REPNE, 113 REPZ, v´ease REPE, 113 RET, 71 71––72, 74 ROL, 51 ROR, 51 SAHF, 132 SAL, 50 salto condicional, 41 41––44 SAR, 50 SBB, 39 SCASB, 111 111,, 112 SCASD, 111, 112 SCASW, 111, 112 SCSI, 149 149––150 segmento bss, 22 segmento de c´odigo, odigo, 22 segmento segmento de datos, 22 segmento text, v´ease segmento de c´ odiodigo SETxx SETxx , 56 SETG, 58 SHL, 49 SHR, 49 STD, 108 STOSB, 109 STOSD, 109 SUB, 13, 39 subprograma, 68 68––96 llamado, 71 71––80 reentrante, 91 91––92
187 subrutina, v´ease subprograma TASM, 12 TEST, 54 tipos de almacenamien almacenamiento to automatic, 94 global, 94 register, 96 static, 94 volatile, 96 UNICODE, 61 virtual, 168 word, 8 XCHG, 62 XOR, 53