Está en la página 1de 110

arquitectura IA-32

89 / 198

Capítulo 5

Juego de instrucciones

A la definición detallada del conjunto de instrucciones que es capaz de ejecutar un procesador se le denomina su ‘juego de
instrucciones’ (o, en ingles, Instruction Set Architecture). Esta definición es la que determina de forma inequívoca el efecto de
cada instrucción sobre las diferentes partes de la arquitectura del procesador. El número de instrucciones máquina puede llegar a
ser muy elevado debido a que la misma instrucción (por ejemplo, la de suma) se puede ejecutar sobre diferentes tipos de datos y
con diferentes variantes (números naturales, enteros, etc.)

5.1. Tipos de juegos de instrucciones

La decisión de qué instrucciones es capaz de ejecutar un procesador es una de las más importantes y en buena medida es
determinante en el rendimiento a la hora de ejecutar programas. Además, el juego de instrucciones y la arquitectura del procesador
están interrelacionados. Por ejemplo, generalmente todas las instrucciones del lenguaje máquina de un procesador pueden utilizar
los registros de propósito general, por lo que su número tiene un efecto directo en la codificación de instrucciones.
La decisión de qué instrucciones incluir en un procesador está también influenciada por la complejidad que requiere su diseño. Si
una instrucción realiza una operación muy compleja, el diseño de los componentes digitales necesarios para su ejecución puede
resultar demasiado complejo.
Considérese el siguiente ejemplo. ¿Debe un procesador incluir en su lenguaje máquina una instrucción que dado un número real
y los coeficientes de un polinomio de segundo grado obtenga su valor? Supóngase que esta instrucción se llama EPSG (evaluar
polinomio de segundo grado). Un posible formato de esta instrucción se muestra en el ejemplo 5.1.

Ejemplo 5.1 Formato de la instrucción EPSG


EPSG a, b, c, n, dest

La instrucción realiza los cálculos con los cuatro primeros parámetros tal y como se muestra en la ecuación 5.1 y almacena el
resultado en el lugar especificado por el parámetro dest.

f (n) = an2 + bn + c

E QUATION 5.1: Polinomio de segundo grado para el valor n

La ecuación 5.1 especifica las operaciones a realizar para evaluar el polinomio, en este caso suma y multiplicación. Un procesador
que no disponga de la instrucción máquina EPSG puede obtener el mismo resultado pero ejecutando múltiples instrucciones.
El compromiso a explorar, por tanto, a la hora de decidir si incluir una instrucción en el lenguaje máquina de un procesador está
entre la complejidad de las instrucciones y la complejidad del lenguaje. Si un procesador soporta la ejecución de la instrucción
arquitectura IA-32
90 / 198

EPSG, requiere una estructura interna más compleja, pues debe manipular sus múltiples operandos y ejecutar las operaciones
necesarias. En cambio, si un procesador ofrece la posibilidad de realizar multiplicaciones y sumas, la evaluación del polinomio
es igualmente posible aunque mediante la ejecución de múltiples instrucciones, con lo que no será una ejecución tan rápida. En
general, un lenguaje máquina con instrucciones sofisticadas requiere una implementación más compleja del procesador. De igual
forma, un lenguaje máquina sencillo (pero que ofrezca las operaciones mínimas para poder realizar todo tipo de cálculos) permite
un diseño más simple.
De este compromiso se ha derivado a lo largo de los años una división de los procesadores en dos categorías dependiendo de la
filosofía utilizada para el diseño de su lenguaje máquina:

Los procesadores que ejecutan un conjunto numeroso de instrucciones y algunas de ellas de cierta complejidad se les denomina
de tipo CISC (Complex Instruction Set Computer). Las instrucciones más complejas son las que requieren múltiples cálculos
y accesos a memoria para lectura/escritura de operandos y resultados.
El ejemplo más representativo de esta filosofía es la arquitectura IA-32. Su lenguaje máquina consta de instrucciones capaces
de realizar operaciones complejas. Otro ejemplo de procesador CISC es el Motorola 68000, que aunque en la actualidad ha
dejado paso a otro tipo de procesadores pero que está todavía presente en ciertos productos electrónicos y ha sido la inspiración
de múltiples modelos actuales.
Los procesadores que ejecutan un conjunto reducido de instrucciones simples se denominan de tipo RISC (Reduced Instruction
Set Computer). El número de posibles instrucciones es muy pequeño, pero a cambio, el diseño del procesador se simplifica y
se consiguen tiempos de ejecución muy reducidos con el consiguiente efecto en el rendimiento total del sistema.
Ejemplos de algunos procesadores diseñados con esta filosofía son:

• MIPS (Microprocessor without interlocked pipeline stages): utilizado en encaminadores, consola Nintendo 64, PlayStation
y PlayStation 2 y PlayStation portátil (PSP).
• ARM: presente en ordenadores portátiles, cámaras digitales, teléfonos móviles, televisiones, iPod, etc.
• SPARC (Scalable Processor Architecture): línea de procesadores de la empresa Sun Microsystems. Se utilizan principal-
mente para servidores de alto rendimiento.
• PowerPC: arquitectura inicialmente creada por el consorcio Apple-IBM-Motorola para ordenadores personales que está
presente en equipos tales como servidores, encaminadores, es la base para el procesador Cell presente en la PlayStation 3,
XBox 360, etc.

En la actualidad, esta división entre procesadores CISC y RISC se ha empezado a difuminar. La propia arquitectura IA-32
decodifica las instrucciones de su lenguaje máquina y las traduce a una secuencia de instrucciones más simples denominadas
‘microinstrucciones’. Se puede considerar, por tanto, que el lenguaje formado por estas microinstrucciones tiene una estructura
cercana a la categoría RISC, mientras que el conjunto de instrucciones máquina es de tipo CISC.
Otra importante decisión a la hora de diseñar un lenguaje máquina es el formato en el que se van a codificar las instrucciones.
Ateniendo a este criterio los procesadores se pueden dividir en:

Formato de longitud fija. Todas las instrucciones máquina se codifican con igual número de bits. De esta característica se
derivan múltiples limitaciones del lenguaje. El número de operandos de una instrucción no puede ser muy elevado, pues todos
ellos deben ser codificados con un conjunto de bits. Al igual que sucede con los operandos, el tipo de operación debe ser
también codificado, y por tanto este tipo de lenguajes no pueden tener un número muy elevado de instrucciones.
Como contrapartida, un formato de instrucción fijo se traduce en una fase de decodificación más simple. El procesador obtiene
de memoria un número fijo de bits en los que sabe de antemano que está contenida la instrucción entera. Los operandos
generalmente se encuentran en posiciones fijas de la instrucción, con lo que su acceso se simplifica enormemente.
El procesador PowerPC es un ejemplo de procesador con formato fijo de instrucción. Todas ellas se codifican con 32 bits. En
general, los procesadores de tipo RISC optan por una codificación con formato de longitud fija.
Formato de longitud variable. Las instrucciones máquina se codifican con diferente longitud. La principal consecuencia es que
la complejidad de una instrucción puede ser arbitraria. En este tipo de lenguaje máquina se puede incluir un número elevado
de instrucciones.
El principal inconveniente es la decodificación de la instrucción pues su tamaño sólo se sabe tras analizar los primeros bytes
con lo que identificar una instrucción y sus operandos es más complejo.
Los procesadores con arquitectura IA-32 son un ejemplo de procesadores con formato variable de instrucciones. Dicho formato
se estudia en mayor detalle en las siguientes secciones.
arquitectura IA-32
91 / 198

5.2. Formato de instrucciones máquina de la arquitectura IA-32

La arquitectura IA-32 codifica sus instrucciones máquina con un formato de longitud variable. Toda instrucción tiene una longitud
entre 1 y 16 bytes. La figura 5.1 ilustra las diferentes partes de las que puede constar una instrucción así como su tamaño en bytes.

Figura 5.1: Formato de Instrucción

Las instrucciones comienzan por un prefijo de hasta cuatro bytes, seguido de uno o dos bytes que codifican la operación, un byte
de codificación de acceso a operandos, un byte denominado escala-base-índice (scale-base-index), un desplazamiento de hasta
cuatro bytes, y finalmente un operando inmediato de hasta cuatro bytes. Excepto los bytes que codifican la operación, el resto de
componentes son todos opcionales, es decir, su presencia depende del tipo de operación.
Los prefijos son bytes que modifican la ejecución normal de una instrucción de acuerdo a unas propiedades predefinidas. El
procesador agrupa estos prefijos en cuatro categorías y se pueden incluir hasta un máximo de uno por categoría. Por ejemplo, el
prefijo LOCK hace que mientras se ejecuta la instrucción el procesador tiene acceso en exclusiva a cualquier dispositivo que sea
compartido. Este prefijo se utiliza en sistemas en los que se comparte memoria entre múltiples procesadores.
El código de operación codifica sólo el tipo de operación a realizar. Su tamaño puede ser de hasta 2 bytes y en ciertas instrucciones
parte de este código se almacena en el byte siguiente denominado ModR/M. Este byte se utiliza en aquellas instrucciones cuyo
primer operando está almacenado en memoria y sus ocho bits están divididos en tres grupos o campos tal y como ilustra la figura
5.2 y que almacenan los siguientes datos:

Figura 5.2: Byte ModR/M de las instrucciones de la arquitectura IA-32

El campo Mod combinado con el campo R/M codifica uno de los 8 posibles registros de propósito general, o uno de los 24
posibles modos de direccionamiento.
El campo Reg/Opcode codifica uno de los ocho posibles registros de propósito general. En algunas instrucciones estos tres
bits forman parte del código de operación.
El campo R/M codifica o uno de los ocho posibles registros de propósito general, o combinado con el campo Mod uno de los
24 posibles modos de direccionamiento.

Algunas combinaciones de valores en el byte ModR/M requieren información adicional que se codifica en el byte SIB cuya
estructura se muestra en la figura 5.3.
arquitectura IA-32
92 / 198

Figura 5.3: Byte SIB de las instrucciones de la arquitectura IA-32

Algunos de los modos de direccionamiento ofrecidos por el procesador requieren un factor de escala por el que multiplicar
un registro denominado índice, y un registro denominado base. Estos tres operandos se codifican en el byte SIB con los bits
indicados en cada uno de sus campos. Los campos que codifican el registro base y el índice tienen ambos un tamaño de 3 bits, lo
que concuerda con el número de registros de propósito general de los que dispone el procesador. El factor de escala se codifica
únicamente con 2 bits, con lo que sólo se pueden codificar 4 posibles valores.
El campo denominado ‘desplazamiento’ es opcional, codifica un número de 1, 2 o 4 bytes y se utiliza para calcular la dirección
de un operando almacenado en memoria. Finalmente, el campo denominado ‘inmediato’ (también opcional) tiene un tamaño de
1, 2 o 4 bytes y codifica los valores constantes en una instrucción.
La figura 5.4 muestra un ejemplo de como se codifica la instrucción ADDL $4, 14( %eax, %ebx, 8) que suma la constante
4 a un operando de 32 bits almacenado en memoria a partir de la dirección cuya expresión es 14 + %eax + ( %ebx * 8) con 5
bytes con valores 0x8344D80E04.

Figura 5.4: Codificación de una instrucción ensamblador

En este caso, el código de operación está contenido en los primeros 8 bits (valor 0x83) y los 3 bits del campo Reg/Opcode del
byte ModR/M y codifica la instrucción de suma de un valor constante de 8 bits a un valor de 32 bits almacenado en memoria.
Los valores 01 y 100 en los campos Mod y R/M del byte ModR/M respectivamente indican que la instrucción contiene en el
byte SIB los datos que precisa el modo de direccionamiento para acceder al segundo operando así como la dirección en la que se
almacena el resultado.
Los campos del byte SIB contienen los valores 11, 011 y 000 que codifican respectivamente el factor de escala 8, el registro
índice %ebx y el registro base %eax así como el tamaño del desplazamiento que es un byte. La instrucción concluye con un byte
que codifica el desplazamiento, seguido de un byte que codifica la constante a utilizar como primer operando.

5.3. El lenguaje ensamblador

Para escribir programas que puedan ser ejecutados por un procesador, todas las instrucciones y datos se deben codificar mediante
secuencias de ceros y unos. Estas secuencias son el único formato que entiende el procesador, pero escribir programas enteros en
este formato es, aunque posible, extremadamente laborioso.
arquitectura IA-32
93 / 198

Una solución a este problema consiste en definir un lenguaje que contenga las mismas instrucciones, operandos y formatos que
el lenguaje máquina, pero en lugar de utilizar dígitos binarios, utilizar letras y números que lo hagan más inteligible para el
programador. A este lenguaje se le conoce con el nombre de lenguaje ensamblador.
El lenguaje ensamblador, por tanto, se puede definir como una representación alfanumérica de las instrucciones que forman
parte del lenguaje máquina de un procesador. Tal y como se ha mostrado en la sección 5.2, la traducción de la representación
alfanumérica de una instrucción a su representación binaria consiste en aplicar un proceso de traducción sistemático.
Considérese de nuevo la instrucción de lenguaje ensamblador utilizada en la figura 5.4, ADDL $4, 14( %eax, %ebx, 8).
Una segunda forma de escribir esta instrucción puede ser ADDL 14[ %eax, %ebx * 8], 4. En este nuevo formato se han
cambiado el orden de los operandos así como la sintaxis utilizada. Cualquiera de las dos notaciones es válida siempre y cuando
se disponga del programa que pueda traducirlo a su codificación en binario entendida por el procesador (5 bytes con valores
0x8344D80E04).

5.3.1. Formato de instrucción ensamblador

El lenguaje ensamblador que se describe a continuación sigue la sintaxis comúnmente conocida con el nombre de ‘AT&T’ y sus
principales características son que los operandos destino se escriben en último lugar en las instrucciones, los registros se escriben
con el prefijo % y las constantes con el prefijo $.
Una sintaxis alternativa utilizada por otros compiladores es la conocida con el nombre de ‘Intel’. En ella, los operandos destino
se escriben los primeros en una instrucción, y los registros y constantes no se escriben con prefijo alguno.
En principio es el programa ensamblador quien estipula la forma en la que se deben escribir las instrucciones. Por tal motivo, es
posible que existan diferentes ensambladores con diferentes definiciones de su lenguaje, pero que produzcan el mismo lenguaje
máquina. Existen también ensambladores capaces de procesar programas escritos en más de un formato, el programa gcc, incluido
con el sistema operativo Linux es uno de ellos. En adelante se utilizará únicamente la sintaxis ‘AT&T’.
Las instrucciones del lenguaje máquina de la arquitectura IA-32 pueden tener uno de los tres siguientes formatos:

Operación. Las instrucciones con este formato no precisan ningún operando, suelen ser fijos y por tanto se incluyen de
forma implícita. Por ejemplo, la instrucción RET retorna de una llamada a una subrutina.
Operación Operando. Estas instrucciones incluyen únicamente un operando. Algunas de ellas pueden referirse de manera
implícita a operandos auxiliares. Un ejemplo de este formato es la instrucción INC %eax que incrementa en uno el valor de
su único operando.
Operación Operando1, Operando2. Un ejemplo de este tipo de instrucciones es ADD $0x10, %eax que toma la
constante 0x10 y el contenido del registro %eax, realiza la suma y deposita el resultado en este mismo registro. Como regla
general, cuando una operación requiere tres operandos, dos fuentes y un destino (por ejemplo, una suma), el segundo operando
desempeña siempre las funciones de fuente y destino y por tanto se pierde su valor inicial.

Algunas de las instrucciones del procesador tienen un formato diferente a estos tres, pero serán tratadas como casos excepciona-
les. El ejemplo 5.2 muestra instrucciones de los tres tipos descritos anteriormente escritas en lenguaje ensamblador.

Ejemplo 5.2 Instrucciones del lenguaje ensamblador


push ( %ecx)
push 4( %ecx)
push $msg
call printf
add $12, %esp

pop %edx
pop %ecx
pop %eax
ret
arquitectura IA-32
94 / 198

5.3.2. Descripción detallada de las instrucciones

Para escribir programas en lenguaje ensamblador se necesita una descripción detallada de todas y cada una de sus instrucciones.
Dicha descripción debe incluir todos los formatos de operandos que admite, así como el efecto que tiene su ejecución en el
procesador y los datos. Esta información se incluye en los denominados manuales de programación y acompañan a cualquier
procesador.
En el caso de la arquitectura IA-32, su descripción detallada, el lenguaje máquina y funcionamiento se incluye en el documento
de poco más de 2000 páginas que lleva por título IA-32 Intel Architecture Software Developer’s Manual y cuyo contenido está
dividido en los siguientes tres volúmenes:

Volumen 1. Arquitectura básica (Basic Architecture): describe la arquitectura básica del procesador así como su entorno de
programación.
Volumen 2. Catálogo del juego de instrucciones (Instruction Set Reference): describe cada una de las instrucciones del proce-
sador y su codificación.

Volumen 3. Guía para la programación de sistemas (System Programming Guide): describe el soporte que ofrece esta arqui-
tectura al sistema operativo en aspectos tales como gestión de memoria, protección, gestión de tareas, interrupciones, etc.

El ejemplo 5.3 muestra la definición de la instrucción de suma de enteros que forma parte del lenguaje máquina de la arquitectura
IA-32 tal y como consta en su manual.

Ejemplo 5.3 Descripción de la instrucción de suma de enteros en la arquitectura IA-32

ADD--Add
Opcode Instruction Description
Opcode Instruction Description
04 ib ADD AL,imm8 Add imm8 to AL
05 iw ADD AX,imm16 Add imm16 to AX
05 id ADD EAX,imm32 Add imm32 to EAX
80 /0 ib ADD r/m8,imm8 Add imm8 to r/m8
81 /0 iw ADD r/m16,imm16 Add imm16 to r/m16
81 /0 id ADD r/m32,imm32 Add imm32 to r/m32
83 /0 ib ADD r/m16,imm8 Add sign-extended imm8 to r/m16
83 /0 ib ADD r/m32,imm8 Add sign-extended imm8 to r/m32
00 /r ADD r/m8,r8 Add r8 to r/m8
01 /r ADD r/m16,r16 Add r16 to r/m16
01 /r ADD r/m32,r32 Add r32 to r/m32
02 /r ADD r8,r/m8 Add r/m8 to r8
03 /r ADD r16,r/m16 Add r/m16 to r16
03 /r ADD r32,r/m32 Add r/m32 to r32
Description
Adds the first operand (destination operand) and the second operand (source operand) and stores the result in the
destination operand. The destination operand can be a register or a memory location; the source operand can be an
immediate, a register, or a memory location. (However, two memory operands cannot be used in one instruction.) When
an immediate value is used as an operand, it is sign-extended to the length of the destination operand format. The ADD
instruction performs integer addition. It evaluates the result for both signed and unsigned integer operands and sets the OF
and CF flags to indicate a carry (overflow) in the signed or unsigned result, respectively. The SF flag indicates the sign of
the signed result. This instruction can be used with a LOCK prefix to allow the instruction to be executed atomically.
Operation
DEST DEST + SRC
Flags Affected
The OF, SF, ZF, AF, CF, and PF flags are set according to the result.
arquitectura IA-32
95 / 198

La parte superior incluye las diferentes versiones de suma de enteros que soporta el procesador dependiendo de los tipos de
operandos. La primera columna muestra los códigos de operación para cada una de las versiones y la segunda columna muestra
la estructura en lenguaje ensamblador de cada una de ellas. La sintaxis utilizada en este documento es de tipo Intel (ver la sección
5.3.1), por tanto, el operando destino es el primero que se escribe.
La codificación de la instrucción ADD $4, 14( %eax, %ebx, 8) utilizada en el figura 5.4 coincide con la mostrada por esta
tabla en la octava fila ADD r/m32, imm8, o en otras palabras, la suma de una constante de ocho bits (imm8) a un registro o
un dato en memoria (en el ejemplo, un dato en memoria).
En el código de operación, los símbolos ‘ib’, ‘iw’ e ‘id’ significan respectivamente una constante de 8, 16 o 32 bits. El símbolo
‘\r’ representa cualquiera de los registros de propósito general del procesador. En la segunda y tercera columna el prefijo ‘imm’
seguido de un número representa una constante del tamaño en bits indicado por el número. El prefijo ‘r/m’ seguido de un número
significa que el operando es o un registro o un dato en memoria del tamaño del número indicado.
El documento continua con una descripción de palabra de la operación que realiza la instrucción. Se aclara que uno de los
operandos es fuente y destino a la vez, y que no es posible sumar dos operandos que estén ambos en memoria.
La siguiente sección es una descripción funcional de la operación y se utiliza como resumen formal de la descripción textual que
le precede. Algunas instrucciones, debido a su complejidad, son más fácilmente descritas mediante esta notación que mediante
texto. Finalmente se mencionan aquellos bits de la palabra de estado del procesador que se modifican al ejecutar una de estas
instrucciones.

5.3.3. Tipos de operandos

Los operandos que utilizan las instrucciones de la arquitectura IA-32 se dividen en las siguientes categorías:

Constantes. El valor debe ir precedido del símbolo ‘$’. Se pueden especificar valores numéricos y cualquier letra o símbolo
manipulable por el procesador. Las constantes numéricas se pueden escribir en base hexadecimal si se antepone el prefijo ‘0x’,
en base 8 (u octal) si se antepone el prefijo ‘0’, o en binario si se antepone el prefijo ‘0b’. Una constante numérica sin prefijo
se considera escrita en base 10, por ejemplo: $0xFF23A013, $0763, 0b00101001, $255.
Las constantes que representan letras deben ir precedidas por la comilla simple ’. Por ejemplo, $’A representa la constante
numérica que codifica el valor de la letra a mayúscula.
Registro de propósito general. El nombre del registro contiene el prefijo %. Se pueden utilizar cualquiera de los ocho registros
de propósito general así como sus diferentes porciones (ver la sección 4.1.2), por ejemplo: %eax, %dh, %esp, %bp.
Dirección de memoria. El operando está almacenado a partir de la dirección de memoria dada en la instrucción. Se permite
un amplio catálogo de formas para especificar la dirección de los operandos denominados ‘modos de direccionamiento’ y se
describen de forma detallada en el capítulo 7.
Operando implícito. No constan pero la instrucción hace uso de ellos. Por ejemplo, la instrucción PUSH deposita el único
operando dado en la cima de la pila. La instrucción tiene como operando implícito el registro %esp que contiene la dirección
de memoria en la que está almacenado el dato de la cima y se le resta la constante 4 al final de la operación.
La presencia o ausencia de operandos implícitos está contenida en la descripción detallada de las instrucciones máquina.

En la arquitectura IA-32 no todas las combinaciones posibles de tipos de operandos se pueden dar en todas las instrucciones. La
arquitectura impone la restricción de que no se permite la ejecución de una instrucción con dos operandos explícitos que estén
almacenados ambos en memoria. Además, no todas las combinaciones de instrucciones con tipos de operandos tienen sentido.
La Tabla 5.1 muestra ejemplos de instrucciones en lenguaje ensamblador correctas e incorrectas.

5.3.4. El sufijo de tamaño

De los tipos de operandos presentados en la sección anterior, no todos tienen definido el tamaño de todos sus componentes. Tal
y como se ha visto en el capítulo 2, cuando se procesan datos es preciso saber el tamaño utilizado para su codificación.
Considérese la instrucción utilizada como último ejemplo en la Tabla 5.1, MOV $-4, contador. A primera vista, la instruc-
ción puede parecer correcta, pues se mueve una constante a una dirección de memoria representada, en este caso, por el símbolo
contador. El primer operando, sin embargo, puede ser representado por un número arbitrario de bits. Lo mismo sucede con
arquitectura IA-32
96 / 198

Instrucción Correcta
PUSH $4 Sí
No. El operando de esta instrucción es el destino en el que
POP $0b11011101 almacenar el dato en la cima de la pila, y por tanto, no
puede ser una constante.
MOV $-4, %eax Sí. Primer operando es de tipo constante y el segundo de
tipo registro.

MOV %eax, $0x11011110 No. El segundo operando es el destino de la operación, y


no puede ser una constante.

MOV %eax, contador Sí. El segundo operando representa una dirección de


memoria.

MOV $’A, %eax Sí. ¿Qué tamaño de datos se está moviendo en esta
instrucción a %eax?

MOV $65, %eax Sí. Esta instrucción tiene una codificación idéntica a la
anterior.

MOV contador, resultado No. Instrucción con dos operandos, y ambos son de tipo
dirección de memoria.

MOV $-4, contador ¿Qué tamaño de datos se transfiere a memoria?

Tabla 5.1: Instrucciones con diferentes tipos de operandos

el segundo operando, pues al ser una dirección de memoria, lo único que se puede asegurar es que se utilizarán tantos bytes de
memoria como sea preciso.
Como conclusión, la instrucción MOV $-4, contador a pesar de tener un formato correcto, es ambigua. El mismo formato
puede representar las instrucciones que almacena la constante -4 representada por un número variable de bytes en la dirección
indicada por contador. La arquitectura IA-32 sólo permite 3 tamaños para sus operandos: 1 byte, 2 bytes (un word), o 4
bytes (un doubleword, ver la Tabla 4.1). Por tanto, la instrucción MOV $-4, contador, puede ser interpretada de tres formas
diferentes dependiendo del tamaño con el que se representa la constante y el número de bytes utilizados para almacenar su valor
en memoria (ambos deben ser el mismo número, 1, 2 o 4).
Para solventar este problema, el lenguaje ensamblador permite la utilización de un sufijo en el código de instrucción que indica
el tamaño de los operandos utilizados. Este sufijo es la letra ‘B’ para operandos de 1 byte, ‘W’ para operandos de 2 bytes (un
word), y ‘L’ para operandos de 4 bytes (un doubleword).
Por tanto, si se quiere codificar la instrucción que almacena la constante -4 representada por 32 bits en la dirección indicada por
contador se debe escribir MOVL $-4, contador.
De todas las instrucciones posibles sólo algunas de ellas son ambiguas. Si alguno de los operandos es un registro, el tamaño del
operando queda fijado por el tamaño del registro. La ambigüedad aparece cuando ninguno de los operandos es un registro, y por
tanto no es posible deducir el tamaño. Se permite el uso del sufijo de tamaño en una instrucción que no lo requiera, siempre y
cuando esté en consonancia con el tamaño de los operandos. La Tabla 5.2 muestra ejemplos de utilización del sufijo de tamaño.

5.4. Instrucciones más representativas de la arquitectura IA-32

A continuación se describe el subconjunto de instrucciones de la arquitectura IA-32 necesario para poder codificar tareas básicas
de programación y manipulación de datos de tipo entero y strings. La descripción del lenguaje máquina completo se puede
encontrar en la documentación facilitada por el fabricante. Para simplificar su estudio, las instrucciones se dividen en categorías.
Una descripción detallada de cada una de ellas se puede encontrar en el apéndice A.
arquitectura IA-32
97 / 198

Instrucción Comentario
No es preciso el sufijo, los operandos de la pila son siempre
PUSH $4
de 32 bits.
PUSHL $0b11011101 El sufijo es redundante y concuerda con el tamaño del
operando.
El sufijo es imprescindible porque la instrucción almacena
MOVB $-4, contador un único byte que codifica el número -4 en complemento a
dos en la posición de memoria indicada por contador.
No es preciso el sufijo porque la presencia del
MOV $-4, %ax operando %ax hace que la constante se represente con 16
bits.
La presencia del registro %eax hace que el operando se
MOVL %eax, contador considere de 32 bits, y por tanto el sufijo es redundante
pero correcto.
Esta instrucción es incorrecta porque contiene un error de
sintaxis. El sufijo indica tamaño de 1 byte y el segundo
MOVB $’A, %eax operando indica 4 bytes. El sufijo es innecesario y la
instrucción transfiere el número que codifica la constante
$’A como número de 32 bits.
La instrucción incrementa el valor de su único operando
INCL contador que está almacenado en memoria con lo que la ausencia de
sufijo la haría ambigua.

Tabla 5.2: Instrucciones con sufijos de tamaño

5.4.1. Instrucciones de transferencia de datos

En esta categoría se incluyen las instrucciones que permiten la transferencia de datos entre registros y memoria tales como MOV,
PUSH, POP y XCHG.
La instrucción MOV recibe dos operandos y transfiere el dato indicado por el primer operando al lugar indicado por el segundo.
Dada la restricción que impone el procesador de que en una instrucción con dos operandos no pueden estar ambos en memoria,
si se quiere transferir datos de un lugar de memoria a otro, se deben utilizar dos instrucciones y utilizar un registro de propósito
general.
Las instrucciones PUSH y POP también transfieren datos, aunque en este caso, uno de los operandos es implícito y se refiere
a la cima de la pila. La instrucción PUSH necesita como operando el dato a colocar en la cima de la pila mientras que la
instrucción POP requiere un único operando para indicar el lugar en el que depositar el dato contenido en la cima de la pila.
Ambas instrucciones modifican el registro %esp que contiene la dirección de la cima de la pila (tal y como se ha descrito en la
sección 4.3).
Estas dos instrucciones aceptan como operando una posición de memoria, por ejemplo PUSH contador. El procesador carga
en la pila el dato en memoria en la posición con nombre contador. En este caso, a pesar de que la transferencia se está
realizando de memoria a memoria, la arquitectura sí permite la operación. La restricción de dos operandos en memoria aplica
únicamente a aquellas instrucción con dos operandos explícitos.
La instrucción XCHG (del inglés exchange) consta de dos operandos e intercambia sus valores por lo que modifica los operandos
(a no ser que tengan idéntico valor). No se permite que los operandos estén ambos en memoria.
La Tabla 5.3 muestra ejemplos correctos e incorrectos de la utilización de este tipo de instrucciones. Se asume que los símbolos
contador1 y contador2 se refieren a operandos en memoria.

5.4.2. Instrucciones aritméticas

En este grupo se incluyen aquellas instrucciones que realizan operaciones aritméticas sencillas con números enteros y naturales
tales como la suma, resta, incremento, decremento, multiplicación y división.
arquitectura IA-32
98 / 198

Instrucción Comentario
MOV $4, %al Almacena el valor 4 en el registro de 8 bits %al.

Almacena los cuatro bytes que se encuentran en memoria a


MOV contador1, %esi partir de la posición que representa contador1 en el
registro %esi.
MOV $4, contador1 Instrucción ambigua, pues no se especifica el tamaño de
datos en ninguno de los dos operandos.
Instrucción incorrecta. El segundo operando es el destino al
MOVL contador, $4 que mover el primer operando, por lo tanto, no puede ser
de tipo constante.
Instrucción incorrecta. El tamaño de los dos operandos es
MOV %al, %ecx inconsistente. El primero es un registro de 8 bits, y el
segundo es de 32.
PUSH $4 Instrucción correcta. Almacena el valor 4, codificado con
32 bits en la cima de la pila. No precisa sufijo de tamaño.
Instrucción incorrecta. El operando indica el lugar en el
POP $4 que almacenar el contenido de la cima de la pila, por tanto,
no puede ser un valor constante.
XCHG %eax, %ebx Instrucción correcta.

XCHG %eax, contador1 Instrucción correcta.

Instrucción incorrecta. Se intercambian los contenidos de


XCHG $4, %eax los dos operandos, por lo que ninguno de ellos puede ser
una constante.
Instrucción incorrecta. Ambos operandos están en
XCHG contador1, contador2 memoria, y el procesador no permite este tipo de
instrucciones.

Tabla 5.3: Instrucciones de transferencia de datos


arquitectura IA-32
99 / 198

5.4.2.1. Instrucciones de suma y resta

Las instrucciones ADD y SUB realizan la suma y resta respectivamente de sus dos operandos. En el caso de la resta, la operación
realizada es la sustracción del primer operando del segundo. Como tales operaciones precisan de un lugar en el que almacenar el
resultado, el segundo operando desempeña las funciones de fuente y destino por lo que se sustituye el valor del segundo operando
por el valor resultante.
El procesador ofrece también las instrucciones INC y DEC que requieren un único operando y que incrementan y decrementan
respectivamente el operando dado. Aunque las instrucciones ADD $1, operando e INC operando realizan la misma
operación y se podría considerar idénticas, no lo son, pues INC no modifica el bit de acarreo.
La instrucción NEG recibe como único operando un número entero y realiza la operación de cambio de signo.
La Tabla 5.4 muestra ejemplos de utilización de este tipo de instrucciones. Se asume que el símbolo contador se refiere a un
operando almacenado en memoria.

Instrucción Comentario
Suma la constante 3 al número de 32 bits almacenado a
ADDL $3, contador partir de la posición contador. El tamaño viene
determinado por el sufijo, que en este caso es
imprescindible.
SUB %eax, contador Deposita en memoria el número de 32 bits resultante de la
operación contador- %eax.

NEGL contador Cambia de signo el número de 32 bits almacenado en


memoria a partir de la posición contador.

Tabla 5.4: Instrucciones aritméticas

5.4.2.2. Instrucciones de multiplicación

La instrucción de multiplicación tiene dos variantes, IMUL y MUL para números enteros y naturales respectivamente y su formato
supone un caso especial, pues permite la especificación de entre uno y tres operandos.
La versión de IMUL y MUL con un único operando ofrece, a su vez la posibilidad de multiplicar números de 8, 16 y 32 bits.
Las instrucciones asumen que el segundo multiplicando está almacenado en el registro %al (para números de 8 bits), %ax (para
números de 16 bits) y %eax (para números de 32 bits). El tamaño del número a multiplicar se deduce del operando explícito de
la instrucción.
Si se multiplican dos operandos de n bits, el resultado tiene tamaño doble y debe representarse con 2n bits. Por tanto, si los
operandos son de 8 bits, el resultado de esta instrucción se almacena en %ax, si son de 16 bits se almacena en los 32 bits
resultantes al concatenar los registros %dx: %ax, y si los operandos son de 32 bits, en los 64 bits obtenidos al concatenar los
registros %edx: %eax. En estos dos últimos casos, los registros %dx y %edx contienen los bytes más significativos del resultado.
La versión de IMUL y MUL con dos operandos es más restrictiva que la anterior. El segundo operando puede ser únicamente
uno de los ocho registros de propósito general (no puede ser ni una constante ni un número en memoria) y el tamaño de ambos
operandos puede ser de 16 o 32 bits. Para almacenar el resultado se utiliza el mismo número de bits con los que se representan
los operandos, con lo que se corre el riesgo, si el resultado obtenido es muy elevado, de perder parte del resultado. Esta última
condición se refleja en los bits de estado del procesador.
La versión de IMUL y MUL con tres operandos es la más restrictiva de todas. Los dos primeros operandos son los multiplicandos
y el primero de ellos debe ser una constante. El tercer operando es el lugar en el que se almacena el resultado y sólo puede ser
un registro de propósito general. Al igual que la versión con dos operandos, los únicos tamaños que se permiten son de 16 y 32
bits, y el resultado se almacena en el mismo tamaño que los operandos, por lo que de nuevo se corre el riesgo de pérdida de bits
del resultado.
La Tabla 5.5 muestra ejemplos de utilización de este tipo de instrucciones. Se asume que el símbolo contador se refiere a un
operando almacenado en memoria.
arquitectura IA-32
100 / 198

Instrucción Comentario
Multiplica el número natural 3 representado en 8 bits por el
MULB $3 registro implícito %al y deposita el resultado en %eax. El
tamaño de los operandos lo determina el sufijo B.
Multiplica el número entero almacenado en %eax por sí
IMUL %eax mismo (operando implícito). El resultado se almacena en el
registro de 64 bits %edx: %eax.
Multiplica el número natural de 32 bits almacenado a partir
MUL contador, %edi de la posición de memoria representada por contador
por el registro %edi en donde se almacenan los 32 bits de
menos peso del resultado.
Multiplica el número de 32 bits almacenado en memoria a
IMUL $123, contador, %ecx partir de la posición contador por la constante $123 y
almacena los 32 bits menos significativos del resultado
en %ecx.

Tabla 5.5: Instrucciones de multiplicación

5.4.2.3. Instrucciones de división entera

Las instrucciones de división de números naturales y enteros devuelven dos resultados, el cociente y el resto, y se almacenan
ambos valores. De manera análoga a las instrucciones de multiplicación, existen dos versiones IDIV y DIV para división de
enteros y naturales respectivamente y el tamaño del dividendo es el doble del divisor. De esta forma, se permite dividir un
número de 16 bits entre uno de 8, uno de 32 entre uno de 16 y uno de 64 entre uno de 32.
Su formato admite de forma explícita un único operando que es el divisor, y que puede ser un número de 8, 16 o 32 bits. El
dividendo es implícito y está almacenado en %ax si el divisor es de 8 bits, en el registro de 32 bits resultante de concatenar %-
dx: %ax si el divisor es de 16 bits, y en el registro de 64 bits resultante de concatenar %edx: %eax si el divisor es de 32
bits.
Los dos resultados que se devuelven también tienen un destino implícito y depende del tamaño de los operandos. Si el divisor es
de 8 bits el cociente se almacena en %al y el resto en %ah. Si el divisor es de 16 bits, se utilizan %ax y %dx para cociente y resto
respectivamente. En el caso de un divisor de 32 bits, el cociente se devuelve en %eax y el resto en %edx.
La Tabla 5.6 muestra ejemplos de utilización de este tipo de instrucciones. Se asume que el símbolo contador se refiere a un
operando almacenado en memoria.

Instrucción Comentario
IDIVB $-53 Divide el registro %ax por la constante $-53. El cociente
se deposita en %al y el resto en %ah.
Se divide el número de 64 bits obtenido al concatenar los
IDIV %eax registros %edx: %eax entre el propio registro %eax.
En %eax se deposita el cociente, y en %edx el resto.
Divide el número de 32 bits almacenado en el registro
obtenido al concatenar %dx: %ax entre el número de 16
DIVW contador bits almacenado a partir de la posición de memoria
indicada por contador. En %ax se almacena el cociente
y en %dx el resto.

Tabla 5.6: Instrucciones de división

5.4.3. Instrucciones lógicas

En este grupo se incluyen las instrucciones de conjunción, disyunción, disyunción exclusiva y negación. La aplicación práctica
de estas instrucciones no es a primera vista del todo aparente, sin embargo, suelen estar presentes en la mayoría de programas.
arquitectura IA-32
101 / 198

Las cuatro instrucciones lógicas consideradas son AND, OR, NOT y XOR para la conjunción, disyunción, negación y disyunción
exclusiva, respectivamente.
Estas instrucciones tienen en común que realizan sus operaciones ‘bit a bit’. Es decir, el procesador realiza tantas operaciones
lógicas como bits tienen los operandos tomando los bits que ocupan la misma posición y, por tanto, produciendo otros tantos
resultados.
Considérese el caso de la instrucción de conjunción AND con sus dos operandos. Al igual que en el caso de instrucciones como
la de suma o resta, el segundo operando es a la vez fuente y destino. El procesador obtiene un resultado de igual tamaño que sus
operandos y en el que cada bit es el resultado de la conjunción de los bits de idéntica posición de los operandos. Las instrucciones
de disyunción (OR) y disyunción exclusiva (XOR) se comportan de forma análoga.
La instrucción NOT tiene un único operando que es fuente y destino y cambia el valor de cada uno de sus bits.
La Tabla 5.7 muestra ejemplos de utilización de este tipo de instrucciones. Se asume que el símbolo contador se refiere a un
operando almacenado en memoria.

Instrucción Comentario
Calcula la conjunción bit a bit entre la constante $-1 y el
AND $-1, %eax registro %eax. ¿Qué valor tiene %eax tras ejecutar esta
instrucción?
Calcula la disyunción bit a bit entre la constante $1 y el
ORL $1, contador número de 32 bits almacenado en memoria a partir de la
posición denotada por contador.
Cambia el valor de los 32 bits almacenados a partir de la
NOTL contador posición de memoria que denota contador. El sufijo de
tamaño es necesario para definir el tamaño del operando.

Tabla 5.7: Instrucciones lógicas

5.4.4. Instrucciones de desplazamiento y rotación

En este grupo se incluyen instrucciones que mediante desplazamientos efectúan operaciones aritméticas de multiplicación y di-
visión por potencias de dos. Además, se incluyen también instrucciones que manipulan sus operandos como si los bits estuviesen
dispuestos de forma circular y permite rotaciones en ambos sentidos.

5.4.4.1. Instrucciones de desplazamiento

Las instrucciones de desplazamiento se subdividen a su vez en dos categorías: desplazamiento aritmético y desplazamiento
lógico.
Las instrucciones de desplazamiento aritmético son aquellas que equivalen a multiplicar y dividir un número por potencias de
2. Un desplazamiento de un bit quiere decir que cada uno de ellos pasa a ocupar la siguiente posición (a derecha o izquierda) y
por tanto, dependiendo de cómo se introduzcan nuevos valores y cómo se descarte el bit sobrante, dicha operación es idéntica a
multiplicar por 2.
En adelante se asume que el bit más significativo de un número es el de más a su izquierda. La figura 5.5 muestra un desplaza-
miento aritmético a izquierda y derecha de un número de 8 bits.
arquitectura IA-32
102 / 198

Figura 5.5: Desplazamiento aritmético de 1 bit en un número de 8 bits

Para que la equivalencia entre los desplazamientos de bits y la operación aritmética de multiplicación y división por 2 sean
realmente equivalentes hay que tener en cuenta una serie de factores.

Si se desplaza un número a la izquierda, el nuevo bit menos significativo debe tener el valor cero.
Si se desplaza a la izquierda un número natural con su bit más significativo a uno se produce desbordamiento.
Si se desplaza un número a la derecha, el nuevo bit más significativo debe tener valor idéntico al antiguo.

Las instrucciones SAL (Shift Arithmetic Left) y SAR (Shift Arithmetic Right) desplazan su segundo operando a izquierda y
derecha respectivamente tantas veces como indica el primer operando. En ambas instrucciones, el último bit que se ha descartado
se almacena en el bit de acarreo CF. Estas instrucciones tienen la limitación adicional de que el primer operando sólo puede ser
una constante o el registro %cl.
La Tabla 5.8 muestra ejemplos de utilización de este tipo de instrucciones. Se asume que el símbolo contador se refiere a un
operando almacenado en memoria.

Instrucción Comentario
Desplaza 4 bits a la derecha el contenido del registro %eax.
SAR $4, %eax Esta operación es equivalente a multiplicar por 16 el
registro %eax.
Desplaza el byte almacenado en la posición de memoria
denotada por contador tantas posiciones a la izquierda
como indica el registro %cl. El sufijo de tamaño es
SALB %cl, contador necesario porque a pesar de que el primer operando es un
registro, éste contiene sólo el número de posiciones
desplazar. El tamaño de los datos se deduce, por tanto del
segundo operando.

Tabla 5.8: Instrucciones de desplazamiento aritmético

Las instrucciones de desplazamiento no aritmético son SHR y SHL para desplazar a derecha e izquierda respectivamente. El
comportamiento y restricciones son idénticas a las instrucciones anteriores con una única diferencia. Los nuevos bits que se
insertan en los operandos tienen siempre el valor cero. Por tanto, dependiendo de los valores de los operandos, las instrucciones
SAR y SAL se pueden comportar de forma idéntica.
arquitectura IA-32
103 / 198

5.4.4.2. Instrucciones de rotación

Las instrucciones de rotación permiten manipular un operando como si sus bits formasen un círculo y se rotan en ambos sentidos
un número determinado de posiciones.
Las instrucciones ROL y ROR rotan a izquierda y derecha respectivamente el contenido de su segundo operando tantas posiciones
como indica el primer operando. El último bit que ha traspasado los límites del operando se almacena en el bit de acarreo CF.
Las instrucciones RCL y RCR son similares a las anteriores con la excepción que el bit de acarreo CF se considera como parte
del operando. El bit que sale del límite del operando se carga en CF y éste a su vez pasa a formar parte del operando.
La figura 5.6 ilustra el funcionamiento de estas instrucciones.

Figura 5.6: Rotación de un operando de 8 bits

Al igual que las instrucciones de desplazamiento aritmético, el primer operando puede ser o una constante o el registro %cl.
El tamaño del dato a manipular se deduce del segundo operando, y si este está en memoria, a través del sufijo de tamaño de la
instrucción.
La Tabla 5.9 muestra ejemplos de utilización de este tipo de instrucciones. Se asume que el símbolo contador se refiere a un
operando almacenado en memoria.

Instrucción Comentario
RCR $4, %ebx Rota el registro %ebx cuatro posiciones a su derecha
utilizando el bit de acarreo CF.
Rota a la izquierda tantas posiciones como indica el
registro %cl el operando de 32 bits almacenado en
RCLL %cl, contador memoria a partir de la posición denotada por contador.
A pesar de que el primer operando es un registro, la
instrucción necesita sufijo de tamaño, pues éste se deduce
únicamente del segundo operando que está en memoria.
Rota a la derecha el registro %eax tantas posiciones como
ROR %cl, %eax indica el registro %cl. El bit CF almacena el bit más
significativo del resultado.
Rota a la izquierda tantas posiciones como indica el
registro %cl el número de 32 bits almacenado en memoria
ROLL %cl, contador a partir de la posición contador. De nuevo se precisa el
sufijo de tamaño porque éste se deduce únicamente a la
vista del segundo operando.

Tabla 5.9: Instrucciones de rotación

5.4.5. Instrucciones de salto

El procesador ejecuta una instrucción tras otra de forma secuencial a no ser que dicho flujo de ejecución se modifique. Las
instrucciones de salto sirven para que el procesador, en lugar de ejecutar la siguiente instrucción, pase a ejecutar otra en un lugar
arquitectura IA-32
104 / 198

que se denomina ‘destino del salto’.


La instrucción de salto JMP (del inglés jump) tiene un único operando que representa el lugar en el que el procesador debe
continuar ejecutando. Al llegar a esta instrucción, el procesador no realiza operación alguna y simplemente pasa a ejecutar
la instrucción en el lugar especificado como destino del salto. El único registro, por tanto, que se modifica es el contador de
programa.
A la instrucción JMP se le denomina también de salto incondicional por contraposición a las instrucciones de salto en las que el
procesador puede saltar o no al destino dependiendo de una condición.
La arquitectura IA-32 dispone de 32 instrucciones de salto condicional. Todas ellas comienzan por la letra J seguida de una
abreviatura de la condición que determina si el salto se lleva a cabo o no. Al ejecutar esta instrucción el procesador consulta esta
condición, si es cierta continua ejecutando la instrucción en la dirección destino del salto. Si la condición es falsa, la instrucción
no tienen efecto alguno sobre el procesador y se ejecuta la siguiente instrucción.
Las condiciones en las que se basa la decisión de saltar dependen de los valores de los bits de estado CF, ZF, OF, SF y PF. La
Tabla 5.10 muestra para cada instrucción los valores de estos bits para los que se salta a la instrucción destino.

Instrucción Condición Descripción Instrucción Condición Descripción


Salto si mayor,
JA mem JBE mem Salto si menor o
salto si no menor
CF = 0 y ZF = 0 CF = 1 ó ZF = 1 igual, salto si no
JNBE mem o igual (sin JNA mem mayor (sin signo)
signo)
Salto si menor,
JAE mem Salto si mayor o JB mem
salto si no mayor
CF = 0 igual, salto si no CF = 1
JNB mem JNAE mem o igual (sin
menor (sin signo)
signo)

JE mem JNE mem


Salto si igual, Salto si diferente,
ZF = 1 ZF = 0
JZ mem salto si cero. JNZ mem salto si no cero.

Salto si menor o
JG mem Salto si mayor, si JLE mem
ZF = 1 ó SF != igual, si no
ZF = 0 y SF = OF no menor o igual
JNLE mem JNG mem OF mayor (con
(con signo)
signo)
Salto si mayor o
JGE mem JL mem Salto si menor, si
igual, si no
SF = OF SF != OF no mayor o igual
JNL mem menor (con JNGE mem (con signo)
signo)

JC mem Salto si acarreo JNC mem Salto si acarreo


CF = 1 CF = 0
es uno es cero
Salto si Salto si
JCXZ mem %cx = 0 registro %cx es JECXZ mem %ecx = 0 registro %ecx es
cero. cero.
Salto si el bit de Salto si el bit de
JO mem OF = 1 desbordamiento JNO mem OF = 0 desbordamiento
es uno. es cero.
JPO mem Salto si paridad JPE mem
Salto si paridad
PF = 0 impar, si no PF = 1
JNP mem JP mem par, si paridad.
paridad.

JS mem SF = 1 Salto si positivo. JNS mem SF = 0 Salto si negativo.

Tabla 5.10: Instrucciones de salto condicional


arquitectura IA-32
105 / 198

En la tabla se incluyen instrucciones con diferente nombre e idéntica condición. Estos sinónimos son a nivel de lenguaje ensam-
blador, es decir, las diferentes instrucciones tienen una codificación idéntica y por tanto corresponden con la misma instrucción
máquina del procesador.
La utilidad de estas instrucciones se debe entender en el contexto del flujo normal de ejecución de un programa. El resto de ins-
trucciones realizan diferentes operaciones sobre los datos, y a la vez modifican los bits de la palabra de estado. Las instrucciones
de salto se utilizan después de haber modificado estos bits y para poder tener dos posibles caminos de ejecución.
El ejemplo 5.4 muestra una porción de código ensamblador muestra un posible uso de las instrucciones de salto.

Ejemplo 5.4 Uso de saltos condicionales


MOV $100, %ecx
dest2: DEC %ecx
JZ dest1
ADD %ecx, %eax
JMP dest2

La instrucción DEC %ecx decrementa el valor del registro %ecx y modifica los bits de la palabra de estado. La instrucción JZ
provoca un salto si ZF = 1. Como consecuencia, la instrucción ADD %ecx, %eax se ejecuta un total de 100 veces.
Las instrucciones de salto condicional son útiles siempre y cuando los valores de los bits de estado hayan sido previamente
producidos por instrucciones anteriores, como por ejemplo, operaciones aritméticas. Pero en algunos casos, la ejecución de un
salto condicional requiere que se realice una operación aritmética y no se almacene su resultado, sino simplemente que se realice
una comparación. Por ejemplo, si se necesita saltar sólo si un número es igual a cero, en lugar de ejecutar una instrucción ADD,
SUB, INC o DEC para que se modifique el bit ZF sólo se necesita comprobar si tal número es cero y modificar los bits de estado.
Para este cometido el procesador dispone de las instrucciones de comparación y comprobación.

5.4.6. Instrucciones de comparación y comprobación

Las instrucciones CMP (comparación) y TEST (comprobación) realizan sendas operaciones aritméticas de las que no se guarda
el resultado obtenido sino que únicamente se modifican los bits de estado.
La instrucción CMP recibe dos operandos. El primero de ellos puede ser de tipo constante, registro u operando en memoria. El
segundo puede ser únicamente de tipo registro u operando en memoria. La instrucción no permite que ambos operandos estén
en memoria. Al ejecutar esta instrucción se resta el primer operando del segundo. El valor resultante no se almacena en lugar
alguno, pero sí se modifican los bits de estado del procesador.
Considérese el código mostrado en el ejemplo 5.5. La instrucción de comparación modifica los bits de estado para que la instruc-
ción de salto los interprete y decida si debe saltar o continuar ejecutando la instrucción ADD.

Ejemplo 5.5 Instrucción de comparación antes de salto condicional


CMP $0, %eax # Se calcula %eax - 0
JE destino
ADD %eax, %ebx

La instrucción JE produce un salto cuando el bit de estado ZF tiene el valor 1. Este bit, a su vez se pone a uno si los operandos
de la instrucción CMP son iguales. Por tanto, la instrucción JE, cuando va a continuación de una instrucción de comparación, se
puede interpretar como ‘salto si los operandos (de la instrucción anterior) son iguales’.
En la mayoría de las instrucciones de salto condicional detalladas en la sección 5.4.5, las últimas letras del nombre hacen
referencia a la condición que se comprueba cuando se ejecutan a continuación de una instrucción de comparación. Por ejemplo,
la instrucción JLE produce un salto cuando los bits de condición cumplen ZF = 1 o SF != OF. Si esta instrucción va precedida de
una instrucción de comparación, ZF es igual a 1 si los dos operandos son iguales. Si SF es diferente a OF la resta ha producido
un bit de signo, y el bit de desbordamiento con valores diferentes. Esta situación se produce si el segundo operando es menor
que el primero, de ahí el sufijo LE (del inglés less or equal) en la instrucción de salto. La Tabla 5.11 muestra las combinaciones
obtenidas del bit de desbordamiento y la resta para el caso de enteros representados con 2 bits.
arquitectura IA-32
106 / 198

B
OF, A-B
(-2) 10 (-1) 11 (0) 00 (1) 01
(-2) 10 0, 00 0, 11 0, 10 1, 01
(-1) 11 0, 01 0, 00 0, 11 0, 10
A
(0) 00 1, 10 0, 01 0, 00 0, 11
(1) 01 1, 11 1, 10 0, 01 0, 00

Tabla 5.11: Resta y bit de desbordamiento de dos enteros de 2 bits

El bit de signo y el de desbordamiento tienen valores diferentes únicamente en el caso en que el primer operando de la resta es
menor que el segundo. Por tanto, la instrucción JLE si se ejecuta a continuación de una instrucción CMP se garantiza que el salto
se lleva a cabo si el segundo operando es menor que el primero.
Las instrucciones de salto cuya condición puede interpretarse con respecto a la instrucción de comparación que le precede son
las que en la descripción mostrada en la tabla Tabla 5.10 incluyen una comparación. Aunque estas instrucciones no debe ir
necesariamente precedidas por una instrucción de comparación porque la condición se evalúa con respecto a los bits de estado,
generalmente se utilizan acompañadas de éstas.
Para interpretar el comportamiento de una instrucción de comparación seguida de una de salto condicional se puede utilizar la
siguiente regla mnemotécnica:

Salto condicional precedido de comparación


Dada la siguiente secuencia de dos instrucciones en ensamblador:
CMP B, A
Jcond

donde A y B son cualquier operando y cond es cualquiera de las condiciones posibles, el salto se lleva a cabo si se cumple A
cond B.

Por ejemplo, si la instrucción CMP $4, %eax va seguida del salto condicional JL destino, el procesador saltará a destino
si %eax < 4.
La Tabla 5.12 muestra posibles secuencias de instrucciones de comparación y salto condicional.
La posibilidad de saltar a una posición de código dependiendo de una condición está presente en la mayoría de lenguajes de
programación de alto nivel. Por ejemplo, en el lenguaje Java, la construcción if () {} else {} se implementa a nivel de
ensamblador basado en instrucción de salto condicional.
La instrucción de comprobación TEST es similar a la de comparación, también consta de dos operandos, el segundo de ellos
puede ser únicamente de tipo registro o memoria y no se permite que ambos sean de tipo memoria. La diferencia con CMP es que
se realiza una conjunción bit a bit de ambos operandos. El resultado de esta conjunción tampoco se almacena, pero sí modifica
los bits de estado OF, CF (ambos se ponen a cero), SF, ZF y PF.
La Tabla 5.13 muestra posibles secuencias de instrucciones de comprobación y salto condicional.

5.4.7. Instrucciones de llamada y retorno de subrutina

Una de las construcciones más comunes en la ejecución de programas es la invocación de porciones de código denominadas
subrutinas con un conjunto de parámetros. Este mecanismo es en el que está basada la invocación de procedimientos, métodos o
funciones en los lenguajes de programación de alto nivel.
Para implementar este mecanismo, el procesador dispone de dos instrucciones. La instrucción CALL tiene un único parámetro
que es la posición de memoria de la primera instrucción de una subrutina. El efecto de esta instrucción es similar a la de salto
incondicional con la diferencia de que el procesador guarda ciertos datos en lugares para facilitar el retorno una vez terminada la
la ejecución de la subrutina.
arquitectura IA-32
107 / 198

Código Comentario

inicio: inc %eax


cmp $128, %eax El salto a final se produce si el registro %eax contiene
jae final un valor mayor o igual a 128. La condición del salto es
... para operandos sin signo, es decir, el resultado de la
jmp inicio comparación se interpreta como si los operandos fuesen
final: mov $’A, %cl números naturales.
...

cmp $12, %eax


jle menor
mov $10, %eax
El salto a menor se produce si el registro %eax es menor o
....
igual que 12. La condición del salto es para operandos con
jmp final
menor: mov $100, %eax signo (números enteros).
...
final: inc %ebx

Tabla 5.12: Secuencias de instrucciones de comparación y salto condicional

Código Comentario

testl $0x0080, contador El salto a ignora se produce si el operando de 32 bits


jz ignora almacenado en memoria a partir de la posición contador
.... tiene su octavo bit igual a cero. Esta instrucción precisa el
ignora: incl %ebx sufijo de tamaño.

test 0xFF00FF00, %eax


jnz pl
El salto a pl se produce si alguno de los bits en las
....
posiciones 8 a 15 o 24 a 31 del registro %eax es igual a
jmp final
pl: mov %eax uno.
...

Tabla 5.13: Secuencias de instrucciones de comprobación y salto condicional


arquitectura IA-32
108 / 198

La instrucción RET es la que se utiliza al final de una subrutina para retomar la ejecución en el punto anterior a la invocación me-
diante la instrucción CALL. No recibe ningún parámetro y el procesador gestiona internamente el lugar en el que debe continuar
la ejecución.
En el capítulo 8 se estudia con todo detalle la utilización de estas instrucciones para implementar construcciones presentes en
lenguajes de programación de alto nivel.

5.5. Ejercicios

1. Utilizando cualquier buscador de internet, localiza los tres volúmenes del documento IA-32 Intel Architecture Software
Developer’s Manual. Utilizando el volumen 2, responde a las siguientes preguntas:

a. Una duda común sobre la instrucción de pila POP es la siguiente. El incremento del registro apuntador de pila %esp,
¿se hace antes o después de escribir el dato de la cima de la pila en el lugar indicado en la instrucción?
b. ¿Qué código de operación en hexadecimal tiene la instrucción PUSH $4?
c. ¿Qué hace la instrucción LAHF? ¿Cuántos operandos recibe?
d. ¿Qué hace la operación NOP? ¿Qué diferencia hay entre la instrucción NOP y la instrucción XCHG %eax, %eax?
e. ¿Qué hace la instrucción STC?
f. ¿Qué flags de la palabra de estado modifica la ejecución de una instrucción de resta?

2. Pensar una situación en un programa en la que la única posibilidad de multiplicar dos números sea mediante la instrucción
con un único operando.
3. Enunciar las condiciones que deben cumplir los operandos para que las instrucciones SAL y SHL se comporten de forma
idéntica. Enunciar estas condiciones para las instrucciones SAR y SHR.
arquitectura IA-32
109 / 198

Capítulo 6

El programa ensamblador

Los programas escritos en lenguaje ensamblador, a pesar de representar instrucciones del lenguaje máquina del procesador, no
son directamente ejecutables por éste sino que es necesario traducirlas a su codificación en binario. Este proceso de traducción es
fácilmente automatizable, y por tanto se dispone de programas denominados ensambladores (o más genéricamente compiladores
que se encargan de esta tarea.
El ensamblador es un programa que recibe como datos de entrada uno o varios ficheros de texto plano con un conjunto de
instrucciones y datos escritos en lenguaje ensamblador y produce un fichero binario y ejecutable que contiene la codificación
binaria del programa. La figura 6.1 muestra el funcionamiento del programa ensamblador.

Figura 6.1: El programa ensamblador

En general, a los programas encargados de traducir de un lenguaje de programación a otro se les denomina ‘compiladores’ y todos
ellos trabajan de forma similar. Dado un conjunto de ficheros escritos en un lenguaje, producen como resultado otro fichero que
contiene la traducción a un segundo lenguaje. En el caso del ensamblador, la traducción es de lenguaje ensamblador a lenguaje
máquina.
En adelante se utilizarán los términos ‘compilador’ y ‘ensamblador’ de forma indistinta y siempre en referencia al programa que
traduce de lenguaje ensamblador a lenguaje máquina.
Así como el lenguaje máquina de un procesador es único e inmutable (a no ser que se rediseñe el procesador), pueden coexistir
múltiples lenguajes ensamblador que representen el mismo lenguaje máquina. La representación de las instrucciones mediante
cadenas alfanuméricas es un convenio utilizado para facilitar su escritura, por lo que pueden existir múltiples convenios de este
tipo siempre y cuando se disponga del ensamblador los que traduzca al lenguaje máquina del procesador.
En el caso concreto del sistema operativo Linux, se incluye como parte de las herramientas del sistema un compilador capaz
de traducir de lenguaje ensamblador a lenguaje máquina. Su nombre es as. En la práctica este programa lo suelen invocar otros
compiladores tales como gcc que es un compilador del lenguaje de alto nivel C a lenguaje máquina, pero también permite la
traducción de ficheros con código ensamblador invocando internamente el programa as.
arquitectura IA-32
110 / 198

6.1. Creación de un programa ejecutable en ensamblador

La figura 6.2 muestra un programa en lenguaje ensamblador creado mediante un editor de texto plano, un programa que guarda
únicamente el texto codificado en formato ASCII o UNICODE sin información alguna sobre estilo. El primer paso, por tanto,
para la obtención de un programa ejecutable es la creación de un fichero de texto que contenga el código.

Figura 6.2: Estructura de un programa en ensamblador

Un programa consta de varias secciones separadas cada una de ellas por palabras clave que comienzan por el símbolo ‘.’. La
palabra .data que aparece en la primera línea no tiene traducción alguna para la ejecución, sino que es la forma de notificar al
ensamblador que a continuación se encuentran definidos conjunto de datos. A este tipo de palabras que comienzan por punto se
les denomina ‘directivas’.
El programa tiene definido un único dato que se representa como una secuencia de caracteres. La línea que contiene .asciz
(también una directiva) seguida del string entre comillas es la que instruye al ensamblador para crear una zona de memoria con
datos, y almacenar en ella el string que se muestra terminado por un byte con valor cero. El efecto de la directiva .asciz es
que, al comienzo de la ejecución de programa, este string esté almacenado en memoria.
Antes de la directiva .asciz se incluye la palabra dato seguida por dos puntos. Esta es la forma de definir una etiqueta o
nombre que luego se utilizará en el código para acceder a estos datos.
La línea siguiente contiene la directiva .text que denota el comienzo de la sección de código. La directiva .global main
comunica al ensamblador que la etiqueta main es globalmente accesible desde cualquier otro programa.
A continuación se encuentran las instrucciones en ensamblador propiamente dichas. Al comienzo del código se define la etiqueta
main que identifica el punto de arranque del programa.
Una vez creado y guardado el fichero de texto con el editor, se debe invocar el compilador. En una ventana en el que se ejecute un
intérprete de comandos y situados en el mismo directorio en el que se encuentra el fichero ejemplo.s se ejecuta el siguiente
comando:
gcc -o ejemplo ejemplo.s
arquitectura IA-32
111 / 198

El compilador realiza una tarea similar a la de un compilador de un lenguaje de alto nivel como Java. Si hay algún error en el
programa se muestra la línea y el motivo. Si el proceso de traducción es correcto, se crea un fichero ejecutable. En el comando
anterior, se ha instruido al ensamblador, por medio de la opción -o ejemplo para que el programa resultante se deposite en el
fichero con nombre ejemplo.
El compilador también es capaz de procesar más de un fichero de forma simultanea. Esto es útil cuando el código de un programa
es muy extenso y está fraccionado en varios ficheros que deben combinarse para obtener un único ejecutable. En tal caso el
comando para compilar debe incluir el nombre de todos los ficheros necesarios.
Si el compilador no detecta ningún error en la traducción, el fichero ejemplo está listo para ser ejecutado por el procesador. Para
ello simplemente se escribe su nombre en el intérprete de comandos (en la siguiente línea, el símbolo $ representa el mensaje
que imprime siempre el intérprete de comandos):
$ ejemplo
Mi Primer Programa Ensamblador
$

Todo programa ensamblador debe seguir el siguiente patrón:


.data # Comienzo del segmento de datos

<datos del programa>

.text # Comienzo del código


.global main # Obligatorio

main:
<Instrucciones>

ret # Obligatorio

Se pueden incluir comentarios en el código a partir de símbolo ‘#’ hasta el final de línea y son ignorados por el compilador.
Basado en este patrón, el programa de la figura 6.2 ha ejecutado las instrucciones:
push %eax
push %ecx
push %edx

push $dato
call printf
add $4, %esp

pop %edx
pop %ecx
pop %eax

ret

Las primeras tres instrucciones depositan los valores de los registros %eax, %ecx y %edx en la pila. Las tres instrucciones
siguientes se encargan de poner la dirección del string también en la pila (instrucción push), invocar la rutina externa printf
que imprime el string (instrucción call) y sumar una constante al registro %esp para restaurar el valor inicial del puntero a la
cima de la pila. Las tres últimas instrucciones restauran el valor original en los registros previamente guardados en la pila.
A continuación se estudia en detalle la sintaxis de las diferentes construcciones permitidas en el lenguaje ensamblador.

6.2. Definición de datos

Como todo lenguaje de programación, se permiten definir tipos de datos así como su contenido. En el caso del ensamblador,
estos tipos no permiten estructuras complejas ni heterogéneas. Todas las definiciones deben incluirse en una sección del código
arquitectura IA-32
112 / 198

que comience por la directiva .data. Los datos se almacenan en posiciones contiguas de memoria, es decir, dos definiciones
seguidas hacen que los datos se almacenen uno a continuación de otro.
La principal dificultad para manipular los datos en ensamblador es que cuando el procesador accede a ellos, no se realiza ningún
tipo de comprobación. Aunque se definan datos con cierto tamaño y estructura en memoria, el procesador trata estos datos como
una simple secuencia de bytes. Esta es una diferencia sustancial con los lenguajes de programación de alto nivel tales como
Java. La definición de datos en ensamblador se realiza a través de directivas (descritas a continuación) que únicamente reservan
espacio en memoria con los datos pertinentes, pero no se almacena ningún tipo de información sobre su tamaño.
Los lenguajes de alto nivel contienen lo que se conoce como un ‘sistema de tipos’ que consiste en un conjunto de reglas que
permiten la definición de tipos de datos así como el mecanismo para comprobar su corrección. En ensamblador, al tratarse de
los datos que manipula directamente el procesador, no se dispone de tal sistema, y por tanto se manejan como si fuese simples
secuencias de bytes.

6.2.1. Definición de bytes

La definición de valores numéricos almacenados en bytes se realiza mediante la directiva .byte seguida de uno o varios valores
separados por comas. Cuando el programa comienza la ejecución, se han inicializado tantas posiciones en memoria como indica
la directiva con los valores dados. El ejemplo 6.1 muestra ejemplos de utilización de la directiva .byte así como los valores
almacenados en memoria.

Ejemplo 6.1 Definición de bytes en ensamblador y sus valores en memoria

datos: .byte 38, 0b11011101, 0xFF, ’A, ’ -


b

Si el valor numérico especificado es menor que cero o mayor que 255 el compilador notifica la anomalía con un error.

6.2.2. Definición de enteros

La definición de enteros de 32 bits se hace mediante la directiva .int seguida de un número o una lista de números enteros
separados por comas. Los números se codifican con 4 bytes almacenados en little endian. El ejemplo 6.2 muestra ejemplos de
definiciones de enteros.

Ejemplo 6.2 Definiciones de números enteros y sus valores en memoria

nums: .int 3, 4, 5
.int 0x12AB, 0x10ab, 0b111000, 0 -
B111000
.int 21
.int 07772

La directiva .long es un sinónimo de .int y también define enteros de 32 bits. Las directivas .word y .quad son análogas
a las anteriores pero definen enteros de 16 y 64 bits respectivamente.
arquitectura IA-32
113 / 198

6.2.3. Definición de strings

La definición de strings se puede hacer con dos formatos diferentes mediante la utilización de tres directivas. La directiva .as-
cii permite la definición de uno o más strings entre comillas y separadas por comas. Cada símbolo de cada strings codifica con
un byte en ASCII utilizando posiciones consecutivas de memoria. Se utilizan tantos bytes como la suma de los símbolos de cada
string.
La directiva .asciz es similar a la anterior, se escribe seguida de uno o más strings separados por comas, pero cada uno de
ellos se codifica añadiendo un byte con valor cero a final del string. Este formato se suele utilizar para detectar el final del string.
La directiva .string es un sinónimo de la directiva .asciz.
El ejemplo 6.3 muestra la utilización de las directivas de definición de strings y los valores que se almacenan en memoria. Los
bytes resaltados corresponden son los que añaden las directivas .asciz y .string al final de cada string.

Ejemplo 6.3 Definición de strings y sus valores en memoria

msg: .ascii "S 1", "S 2"


.asciz "S 3", "S 4"
.string "S final"

6.2.4. Definición de espacio en blanco

La directiva .space seguida de dos números separados por una coma permite la reserva de espacio en memoria. El primer valor
denota el número de bytes que se reservan y el segundo es el valor que se utiliza para inicializar dichos bytes y debe estar entre
0 y 255. En el caso de que este parámetro se omita, la memoria se reserva inicializada al valor cero.
El uso principal de esta directiva es para reservar espacio que, o se debe inicializar al mismo valor, o su valor será calculado y
modificado por el propio programa. El ejemplo 6.4 muestra el uso de la directiva así como su efecto en memoria.

Ejemplo 6.4 Definiciones de espacio en blanco y su valor en memoria

result: .space 4, 0
.space 4
.space 8, 0xFF

6.3. Uso de etiquetas

En lenguaje ensamblador se permite la definición de un conjunto de datos y las instrucciones para manipularlos que se traducen a
su codificación binaria y se produce un fichero ejecutable. Antes de comenzar la ejecución del programa, los datos e instrucciones
en binario se cargan en la memoria RAM del sistema. Pero ¿en qué posición de memoria está almacenado el programa?
El valor de esta dirección de memoria, o de la dirección en la que está almacenado cualquier dato o instrucción, no se sabe hasta
el momento en el que se ejecuta el programa porque es el sistema operativo el que lo decide, y tal decisión se aplaza hasta el
arquitectura IA-32
114 / 198

último instante para así poder ubicar cada programa en el lugar más conveniente en memoria. El sistema operativo está ejecutando
múltiples programas de forma simultánea, y por tanto, necesita esta flexibilidad para poder hacer un mejor uso de la memoria.
Pero, si no se sabe el valor de la dirección de memoria de ningún dato ni instrucción, ¿cómo se puede, por ejemplo, acceder a un
dato en memoria? Para ello se precisa su dirección, pero el valor numérico de esta no se sabe cuando se escribe un programa.
El lenguaje ensamblador soluciona este problema mediante el uso de ‘etiquetas’. Las etiquetas no son más que nombres que se
ponen al comienzo de una línea (ya sea definición de datos o una instrucción) seguido por dos puntos. Dicho nombre representa
la posición de memoria en la que está almacenado el dato o instrucción definido justo a continuación.
Estas etiquetas son, por tanto, un punto de referencia en memoria que el ensamblador sabe interpretar de forma correcta y que en
el momento de ejecución serán reemplazados por el valor numérico de la dirección de memoria pertinente.
La definición de una etiqueta no sólo permite referirse a los datos almacenados en esa posición, sino que ofrece un mecanismo
por el que acceder a los datos en posiciones cercanas a ella mediante simples operaciones aritméticas sobre la dirección que
representa. Considérese de nuevo la representación en memoria de los enteros definidos en el ejemplo 6.2. La figura 6.3 ilustra
como se pueden deducir las direcciones de los demás enteros en base al símbolo nums:.

Figura 6.3: Etiqueta y direcciones relativas a ella

Dado que la directiva .int define valores enteros representados por 32 bits, de la definición de la etiqueta nums se pueden
deducir los valores de las direcciones en las que se almacenan el resto de números enteros definidos. Para efectuar estos cálculos
es imprescindible saber el tamaño de la información almacenada a partir de la etiqueta.
Las etiquetas, por tanto, se pueden definir en cualquier lugar del código y son únicamente símbolos que representan una dirección
de memoria cuyo valor no se sabe y se utilizan como puntos de referencia para acceder a los datos en memoria de su alrededor.
Pero su uso en ensamblador tiene dos versiones igualmente útiles. La primera es acceder al valor contenido en la posición de
memoria a la que se refieren. Para ello se incluye en las instrucciones ensamblador el nombre de la etiqueta tal cual se ha definido
(sin los dos puntos).
Pero a menudo es necesario manipular la propia dirección de memoria que representa dicha etiqueta. Aunque dicho valor es
desconocido, nada impide que se escriban instrucciones máquina que operen con él. El ensamblador permite referirse al valor de
la dirección de memoria que representa una etiqueta precediendo su nombre del símbolo $.
Si una instrucción ensamblador contiene como operando el nombre de una etiqueta, este operando es de tipo dirección de memoria
(ver la sección 5.3.3). En cambio, si el operando es el nombre de una etiqueta precedido por $, este operando es de tipo constante.
Esta nomenclatura para diferenciar entre el valor al que apunta una etiqueta y su valor como dirección en memoria es consistente
con la nomenclatura de operandos. Dada una etiqueta, de los dos valores, al que apunta en memoria y su dirección, es este último
el que permanece constante a lo largo de la ejecución, y por tanto se representa con el prefijo $. En cambio, el valor en memoria
al que apunta es variable y por ello se representa únicamente por el nombre de la etiqueta.
El ejemplo 6.5 muestra una porción de código en la que se define una etiqueta y se manipula mediante instrucciones máquina.
arquitectura IA-32
115 / 198

Ejemplo 6.5 Definición y manipulación de etiquetas


.data
dato: .int 3, 4
.string "Mensaje"
.byte 17
...
mov dato, %eax
add $5, %eax
mov %eax, dato
movl $4, dato
...
mov $dato, %ebx
add $8, %ebx
...

La etiqueta dato corresponde con la dirección de memoria en la que está almacenado el entero de 32 bits con valor 3. En el
primer grupo de instrucciones, la instrucción mov dato, %eax mueve el número 3 al registro %eax. Nótese que el operando
carece del prefijo $ y por tanto se refiere al valor almacenado en memoria. A continuación se suma la constante 5 y se transfiere
el valor en %eax de nuevo a la posición de memoria referida por dato.
La instrucción movl $4, dato requiere especial atención. El sufijo de tamaño es necesario para desambiguarla porque ni la
constante $4 ni el segundo operando contienen información sobre su tamaño.La información de una etiqueta es únicamente la
dirección a la que representa sin ningún tipo de información sobre el tamaño de los datos. Por tanto, a pesar de que dato ha sido
definida en una línea en la que se reserva espacio para enteros, cuando se utiliza en una instrucción y el otro operando tampoco
ofrece información sobre el tamaño, requiere el sufijo.
En el segundo grupo de instrucciones, la instrucción mov $dato, %ebx carga en el registro %ebx el valor de la dirección de
memoria que representa la etiqueta. Este valor es imposible de saber en tiempo de programación, pero se puede manipular al
igual que cualquier otro número. Tras ejecutar la última instrucción, el registro %ebx contiene la dirección de memoria en la que
está almacenada la primera letra del string. Esto se deduce de las definiciones de datos y sus tamaños. Los dos números ocupan
8 bytes, con lo que en la posición $dato + 8 se encuentra la letra ‘M’ del string Mensaje.
Las etiquetas no sólo se utilizan en las definiciones de datos sino también en instrucciones del código. Los destinos de los saltos
reciben como operando una dirección de memoria, que por tanto debe ser una etiqueta.
Se pueden definir tantas etiquetas como sea preciso en un programa sin que por ello se incremente el tamaño del programa. Las
etiquetas son símbolos que utiliza el programa ensamblador para utilizar en lugar de los valores numéricos de las direcciones que
se sabrán cuando el programa comience su ejecución.
El compilador gcc utilizado para traducir de lenguaje ensamblador a lenguaje máquina asume que el punto de comienzo de
programa está marcado por la presencia de la etiqueta con nombre main. Por tanto, al escribir un programa que sea traducido
por gcc se debe definir la etiqueta main en el lugar del código que contenga su primera instrucción máquina.

6.4. Gestión de la pila

El desarrollo de programas en ensamblador tiene una serie de particularidades derivadas de la proximidad al procesador con la
que se trabaja. Uno de los cometidos de los lenguajes de programación de alto nivel tales como Java es precisamente el ofrecer
al programador un entorno en el que se oculten los aspectos más complejos de la programación en ensamblador.
La pila se utiliza como depósito temporal de datos del programa en ejecución. Las operaciones push y pop permiten depositar
y obtener datos de la pila, pero no son las únicas que modifican su contenido. El propio procesador también utiliza la pila para
almacenar datos temporales durante la ejecución. Esto implica que los programas en ensamblador tienen ciertas restricciones al
manipular la pila.
La más importante de ellas es que la cima de la pila debe ser exactamente la misma antes del comienzo de la primera instrucción
de un programa y antes de la instrucción RET que termina su ejecución. Cuando se arranca un programa, el sistema operativo
reserva espacio para la pila y almacena el valor pertinente en el registro %esp. Por motivos que se explican en detalle en el
capítulo 8, el valor de este registro debe ser el mismo al terminar la ejecución de un programa.
arquitectura IA-32
116 / 198

Nótese que el respetar esta regla no implica que la pila no pueda utilizarse. Al contrario, como la cima debe ser idéntica al
comienzo y final del programa, las instrucciones intermedias sí pueden manipular su contenido siempre y cuando al final del
programa se restaure el valor de la cima que tenía al comienzo.
En un programa, esta limitación se traduce en que cada dato que se deposite en la pila debe ser descargado antes de que finalice
el programa. En el medio del código, la pila puede almacenar los datos que el programador considere oportunos.
Además de inicializar el registro %esp, el sistema operativo también deposita valores en los registros de propósito general. La
ejecución del programa escrito en ensamblador la inicia el sistema mediante una llamada a la subrutina con nombre main (de
ahí que éste sea el punto de comienzo del programa) y por tanto, los registros tienen todos ciertos valores iniciales.
La regla a respetar en los programas ensamblador es que al término de la ejecución de un programa, el valor de los registros de
propósito general debe ser exactamente el mismo que tenían cuando se comenzó la ejecución (en la práctica no todos los registros
deben ser restaurados, pero por simplicidad se ha adoptado la regla para todos ellos).
De nuevo, el que el valor de los registros tenga que ser idéntico al comienzo y al final de un programa no quiere decir que no se
puedan utilizar. Simplemente se deben guardar los valores iniciales de aquellos registros que se utilicen y restaurarlos antes de
terminar la ejecución.
El lugar más apropiado para guardar los valores iniciales de estos registros es precisamente la pila. No es preciso reservar
espacio de antemano, pues la pila ya lo tiene reservado, y mediante varias instrucciones PUSH se depositan los registros que se
modifiquen en el código antes de ejecutar las instrucciones propias del cálculo. Luego, justo antes del final de la ejecución se
restauran mediante las instrucciones POP. El ejemplo 6.6 muestra una porción de un programa que modifica los registros %ea-
x, %ebx, %ecx y %edx,

Ejemplo 6.6 Instrucciones para salvar y restaurar registros


main: push %eax
push %ebx
push %ecx
push %edx

# Instrucciones del programa que modifican los 4 registros

pop %edx
pop %ecx
pop %ebx
pop %eax

ret

Las instrucciones para guardar la copia de los registros que se modifican se realiza justo al principio del código. De forma
análoga, las instrucciones para restaurar estos valores se realizan justo antes de la instrucción RET. Asimismo, el orden en el que
se salvan y restauran los registros es el inverso debido a cómo se almacenan en la pila. El orden en el que se depositan los datos
en la pila es irrelevante, tan sólo se deben restaurar en orden inverso al que se han depositado.
Todo programa ensamblador, por tanto, debe comenzar y terminar con instrucciones de PUSH y POP de los registros que se
modifiquen en su interior.
La utilización de la pila para almacenar los valores de los registros modificados respeta el convenio de mantener la cima de la
pila idéntica al comienzo y final del programa. Como el número de operaciones PUSH es idéntico al número de operaciones POP,
mientras que en el código interno del programa todo dato que se deposite en la pila se extraiga, la cima de la pila es idéntica al
comienzo y final.
A la hora de desarrollar programas en ensamblador, se recomienda primero escribir el código interno de un programa y cuando
dicho código se suponga correcto completarlo con las instrucciones que salvan y restauran los registros que se modifican. En
general, los datos que se almacenan en la pila se hace de forma temporal y deben eliminarse una vez terminada la tarea para la
que se han almacenado.
arquitectura IA-32
117 / 198

6.5. Desarrollo de programas en ensamblador

El desarrollo de programas en ensamblador requiere un conocimiento en detalle de la arquitectura del procesador y una meticu-
losidad extrema a la hora de decidir qué instrucciones y datos utilizar. Al trabajar con el lenguaje máquina del procesador, la
comprobación de errores de ejecución es prácticamente inexistente. Si se ejecuta una instrucción con operandos incorrectos, el
procesador los interpretará tal y como estipula su lenguaje máquina, con lo que es posible que la ejecución del programa produzca
resultados inesperados.
Desafortunadamente no existe un conjunto de reglas que garanticen un desarrollo simple de los programas. Esta destreza se
adquiere mediante la práctica y, más importante, mediante el análisis detenido de los errores, pues ponen de manifiesto aspectos
de la programación que se han ignorado.
Las recomendaciones que se hacen para el desarrollo de programas en lenguaje de alto nivel adquieren todavía más relevancia
en el contexto del lenguaje ensamblador. Sin ser de ninguna manera una lista exhaustiva, se incluyen a continuación las más
relevantes.

El valor de los registros y el puntero de pila antes de ejecutar la última instrucción del programa deben ser idénticos a los
valores que tenían al comienzo.
Se deben evitar las operaciones innecesarias. Por ejemplo, salvar y restaurar todos los registros independientemente de si son
utilizados o no.
Debido al gran número de instrucciones disponibles y a su simplicidad siempre existen múltiples forma de realizar una ope-
ración. Generalmente elige aquella que proporciona una mayor eficiencia en términos de tiempo de ejecución, utilización de
memoria o registros, etc.
Mantener un estilo de escritura de código que facilite su legibilidad. Escribir las etiquetas a principio de línea, las instrucciones
todas a la misma altura (generalmente mediante ocho espacios), separar los operandos por una coma seguida de un espacio,
etc.

La documentación en el código es imprescindible en cualquier lenguaje de programación, pero en el caso del ensamblador,
es crucial. Hacer uso extensivo de los comentarios en el código facilita la comprensión del mismo además de simplificar
la detección de errores. Los comentarios deben ser lo más detallados posible evitando comentar instrucciones triviales. Es
preferible incluir comentarios de alto nivel sobre la estructura global del programa y los datos manipulados.
La mayor parte de errores se detectan cuando el programa se ejecuta. No existe una técnica concreta para detectar y corregir
un error, pero se debe analizar el código escrito de manera minuciosa. En ensamblador un simple error en el nombre de un
registro puede producir que un programa sea incorrecto.

6.6. Ejemplo de programa en ensamblador

El ejemplo 6.7 muestra un programa de ejemplo escrito en ensamblador que dados cuatro enteros almacenados en memoria,
suma sus valores y deposita el resultado en el lugar que denota la etiqueta result.
arquitectura IA-32
118 / 198

Ejemplo 6.7 Programa que suma cuatro enteros


.data # Comienza la sección de datos
num1: .int 10
num2: .int 23
num3: .int 34
num4: .int 43
result: .space 4 # Deposita aquí el resultado

.text # Comienza la sección de código


.global main # main es un símbolo global

main:
push %eax # Salva registros

mov num1, %eax # Carga primer número y acumula


add num2, %eax
add num3, %eax
add num4, %eax

mov %eax, result # Almacena resultado

pop %eax # Restaura registros


ret

La sección de definición de datos contiene los cuatro enteros con sus respectivas etiquetas y los cuatro bytes de espacio vacío en
el que se almacenará su suma. La directiva .space tiene un único parámetro, con lo que se reserva espacio en memoria pero no
se inicializa.
El programa utiliza el registro %eax para acumular los valores de los números, por lo que necesita ser salvado en la pila al
comienzo y restaurado al final.
Tras salvar %eax en la pila la siguiente instrucción simplemente mueve el primer número al registro %eax. No es posible sumar
dos números que están almacenados en memoria, por lo que el programa carga el primer valor en un registro y luego suma los
restantes números a este registro.
Finalmente, el programa almacena el resultado de la suma en la posición de memoria con etiqueta result, restaura los registros
utilizados (en este caso sólo %eax) y termina la ejecución con la instrucción RET.

6.7. Ejercicios

1. Escribir el equivalente de las siguientes definiciones de datos en ensamblador pero utilizando únicamente la directiva
.byte.

.int 12, 0x34, ’A

.space 4, 0b10101010

.ascii "MSG."

.asciz "MSG. "

2. Dada la siguiente definición de datos:


dato: .int 0x10203040, 0b10
.string "Mensaje en ASCII"
.ascii "Segundo mensaje"
arquitectura IA-32
119 / 198

Si la etiqueta dato ser refiere a la posición de memoria 0x00001000, calcular la dirección de memoria de los siguientes
datos:

El byte con valor 0x30 del primer entero definido.


El byte de más peso del segundo número entero definido.
La letra ‘A’ del primer string definido.
El espacio en blanco entre las dos palabras de la última definición.
arquitectura IA-32
120 / 198

Capítulo 7

Modos de Direccionamiento

En este capítulo se estudia una parte concreta de la ejecución de cada instrucción máquina: la obtención de los operandos. Tras
recibir una instrucción el procesador debe obtener los operandos necesarios que están almacenados en registros de propósito
general, en la propia instrucción o en memoria. El acceso a los dos primeros es sencillo, pero el acceso a memoria puede ser
arbitrariamente complejo.
A pesar de que cada dato almacenado en memoria tiene una dirección, en la práctica, se suelen utilizar un conjunto de operaciones
aritméticas para obtenerla. La figura 7.1 muestra un ejemplo de las operaciones necesarias para obtener la dirección de un
elemento en una tabla de enteros.

Figura 7.1: Dirección de un elemento en una tabla de enteros

Supóngase una tabla de números enteros de 32 bits almacenados a partir de la posición 100 de memoria. El único dato del que se
dispone es dicho valor. ¿Cómo se puede acceder al elemento con índice 3 de la tabla? Sabiendo que los enteros tienen 4 bytes de
tamaño, sumando a la dirección base de la tabla el tamaño de los 3 números anteriores se obtiene la dirección del entero deseado.
Los cálculos para obtener una dirección de memoria suelen requerir operaciones de suma, resta, multiplicación y división y por
tanto pueden realizarse utilizando las instrucciones aritméticas del procesador. Para acceder a un operando en memoria se calcula
primero su dirección con las operaciones pertinentes y luego se accede a memoria.
Pero estos dos pasos resultan ser extremadamente frecuentes en la ejecución de los programas convencionales. Una parte impor-
tante de las instrucciones ejecutadas por un procesador están destinadas al cálculo de la dirección de un operando que se necesita
para una operación posterior.
A la vista de esta situación, el diseño de los procesadores ha ido incorporando a la propia instrucción máquina la posibilidad de
realizar ciertos cálculos sencillos para la obtención de la dirección de sus operandos. La estrategia consiste en incluir los cálculos
más comunes como parte de la instrucción y de esta forma conseguir secuencias de instrucciones más compactas, puesto que las
arquitectura IA-32
121 / 198

operaciones aritméticas se hacen igual pero sin necesidad de ejecutar múltiples instrucciones, y por consiguiente se obtiene una
mayor eficiencia en la ejecución.
Para ilustrar la ventaja de los modos de direccionamiento considérese la situación en la que el procesador debe acceder a un
operando en memoria cuya dirección d se obtiene mediante la ecuación 7.1. Supóngase que se debe sumar el valor 300 al número
de 32 bits almacenado en esta posición. Una posible secuencia de instrucciones para realizar tal operación se ilustra en el ejemplo
7.1

d = 1000 + %eax + ( %ebx ⇤ 4)


E QUATION 7.1: Expresión de la dirección de un dato en memoria

Ejemplo 7.1 Cálculo de la dirección de un operando mediante instrucciones


mov %ebx, %ecx # %ecx = %ebx
sal $2, %ecx # %ecx = ( %ebx * 4)
add %eax, %ecx # %ecx = %eax + ( %ebx * 4)
add $1000, %ecx # %ecx = 1000 + %eax + ( %ebx * 4)
addl $300, ( %ecx) # Sumar 300 a la posición indicada por %ecx

Las cuatro primeras instrucciones calculan el valor de la dirección del operando y es únicamente la última la que realiza el acceso
y la suma de la constante $300. Esta última instrucción está accediendo a memoria de una forma especial. La expresión de su
segundo operando ( %ecx) indica que el operando está en memoria en la posición contenida en el registro %ecx. Se precisa
el sufijo de tamaño porque el primer operando es una constante, y el segundo operando, a pesar de contener el nombre de un
registro, en realidad especifica una dirección de memoria.
Supóngase ahora que el lenguaje máquina del procesador permite escribir la siguiente instrucción:
addl $300, 1000( %eax, %ebx, 4)

El efecto de esta instrucción es exactamente el mismo que el de las cinco instrucciones del ejemplo 7.1. Se realiza la suma de
300 y el número de 32 bits almacenado en memoria en la posición obtenida al sumar 1000, el contenido del registro %eax y el
contenido del registro %ebx previamente multiplicado por cuatro. Este segundo operando es a la vez fuente y destino.
La existencia de tal instrucción en el lenguaje máquina tiene la ventaja de que en una instrucción el procesador recibe mucha más
información sobre la operación a realizar. En el primer caso se precisan cinco instrucciones, o lo que es lo mismo, cinco ciclos
de ejecución como los descritos en la sección 4.2. En el segundo caso, con una única instrucción el procesador dispone de todos
los ingredientes para realizar los cálculos. La mejora en rendimiento no se deriva del número de operaciones aritméticas, pues
son exactamente las mismas en ambos casos, sino del número de instrucciones que se ejecutan.
Pero a cambio, el principal inconveniente de esta solución es que la codificación y longitud de las instrucciones se complica tanto
como complejas sean las operaciones que se permiten en una instrucción. En este caso se realizan dos sumas y una multiplicación
para obtener la dirección de un operando que a su vez participa en la suma final.
Al conjunto de métodos que el lenguaje máquina de un procesador ofrece para acceder a sus operandos se les denomina modos de
direccionamiento. El número de métodos diferentes depende de cada procesador y varía enormemente entre diferentes diseños.
Los modos de direccionamiento de la arquitectura IA-32 contemplan el acceso a operandos como los utilizados en la instrucción
addl $300, 1000( %eax, %ebx, 4).
La figura 7.2 ilustra el funcionamiento de los modos de direccionamiento de un procesador.
arquitectura IA-32
122 / 198

Figura 7.2: Funcionalidad de los modos de direccionamiento

En general un modo de direccionamiento es un procedimiento que dado un conjunto de bits o campos de la instrucción calcula
el lugar en el que se encuentra almacenado un operando.

7.1. Notación

Para especificar en detalle el funcionamiento de los modos de direccionamiento se precisa una notación para referirse a los
componentes de las operaciones necesarias.
La ‘dirección efectiva de un operando’ que se denota por de es el lugar en el que se encuentra almacenado un operando. Esta
dirección no tiene por qué ser una posición de memoria. Existen modos de direccionamiento en los que la dirección efectiva de
un operando es un registro de propósito general.
La instrucción de la que se obtienen los datos necesarios para calcular de se denota por inst, la dirección de memoria a partir
de la cual está almacenada es @inst , y los diferentes campos de esta instrucción se denotan por instc1 , ..., instcn . Por campos
se entienden aquellos bits que forman parte de la instrucción y que codifican los diferentes elementos necesarios para el cálculo
de la dirección efectiva.
Cuando el campo instci codifica uno de los registros de propósito general, dicho registro se denota por Rci . La expresión
MEM[n] denota el contenido de memoria almacenado a partir de la posición n. La acción de modificar el contenido del registro
R con el dato d se denota R d.
A continuación se estudian en detalle los modos de direccionamiento disponibles en la arquitectura IA-32 así como su sintaxis en
ensamblador. Para cada uno de ellos se especifica la fórmula utilizada para obtener la dirección efectiva del operando y su valor.

7.2. Modos del direccionamiento de la arquitectura IA-32

Tal y como se ha visto en la sección 5.3.3, los operandos de una instrucción se dividen en cuatro categorías: constantes, registros,
direcciones de memoria y operandos implícitos. Excepto estos últimos, el resto se obtienen a través de diferentes modos de
direccionamiento. A continuación se presentan en orden creciente de complejidad para finalmente comparar todos ellos con el
más complejo.

7.2.1. Modo inmediato

Es el modo de direccionamiento utilizado para obtener operandos de tipo constante, es decir, aquellos que tienen el prefijo $ en
ensamblador. El operando está incluido en la propia instrucción. La expresión de su dirección efectiva se muestra en la ecuación
7.2.

de = @inst + k
operando = MEM[@inst + k]

E QUATION 7.2: Dirección efectiva y operando del modo inmediato


arquitectura IA-32
123 / 198

El valor k representa el número de bytes a partir de la dirección en la que está almacenada instrucción en la que se codifica la
constante. La figura 7.3 ilustra el funcionamiento de este modo de direccionamiento así como un ejemplo.

Figura 7.3: Acceso a operando con modo inmediato

El primer operando de la instrucción es la constante $3 que tal y como se ve en la figura, en lenguaje máquina se codifica en
el último byte. Por tanto, la dirección efectiva del operando es de = @inst + 4. Cuando el procesador necesita este operando, lo
obtiene directamente de los bytes de la instrucción.
A pesar de que el sufijo de la instrucción indica que los operandos han de ser de 32 bits, el primer operando se codifica con un
único byte. Esta aparente inconsistencia no es más que un mecanismo que utiliza el procesador para que el código máquina sea
más compacto. Cuando en una instrucción se codifica un operando en modo inmediato se utilizan 1, 2 o 4 bytes dependiendo de
su valor. En este ejemplo, como el valor es $3 sólo precisa un byte.

7.2.2. Modo registro

Es el modo de direccionamiento utilizado para obtener operandos almacenados en uno de los ocho registros de propósito general.
La instrucción contiene un campo instc1 de 3 bits que codifica los ocho posibles registros. La expresión de la dirección efectiva
y el valor del operando se muestran en la ecuación 7.3.

de = instc1
operando = Rc1

E QUATION 7.3: Dirección efectiva y operando del modo registro

La dirección efectiva del operando, en este caso, no es una dirección de memoria, sino la de uno de los registros de propósito
general. La figura 7.4 muestra el funcionamiento de este modo de direccionamiento y un ejemplo.
arquitectura IA-32
124 / 198

Figura 7.4: Acceso a operando con modo registro

En la figura, el código de operación 0x89 no sólo indica que se realiza una operación de mover, sino que el primer operando es
de tipo registro. El nombre del registro está codificado en el segundo byte. También en este byte, se codifica el tipo del segundo
operando, que es igualmente de tipo registro. Para ello se utiliza el campo R/M del byte ModR/M.

7.2.3. Modo absoluto

Los modos de direccionamiento restantes se refieren todos ellos a operandos almacenados en memoria y se diferencian en los
cálculos para obtener la dirección efectiva de .
En este modo de direccionamiento la dirección efectiva corresponde con una dirección de memoria y forma parte de los bytes
que codifican la instrucción. En otras palabras, la propia instrucción, en su codificación incluye una dirección de la que obtener
uno de sus operandos. La expresión de la dirección efectiva y el valor del operando se muestran en la ecuación 7.4.

de = MEM[@inst + k]
operando = MEM[MEM[@inst + k]]

E QUATION 7.4: Dirección efectiva y operando del modo absoluto

Como la instrucción contiene la dirección efectiva, ésta está contenida en memoria desplazada k bytes con respecto a la dirección
@inst . El operando está en memoria en la posición que indica de , de ahí la doble expresión MEM[]. La figura 7.5 muestra el
funcionamiento de este modo de direccionamiento.
arquitectura IA-32
125 / 198

Figura 7.5: Acceso a operando con modo absoluto

Como muestra la figura, la dirección de memoria ocupa 4 de los bytes que codifican la instrucción. En el ejemplo que se muestra,
la dirección es 0x0000059A pues los datos se almacenan en little endian. La representación en ensamblador de este modo
de direccionamiento es mediante una etiqueta. El ensamblador asigna a cada una de ellas un valor, y cuando se utiliza en una
instrucción se reemplaza el símbolo por el valor de su dirección de memoria.

7.2.4. Modo registro indirecto

El modo registro indirecto accede a un operando en memoria utilizando como dirección el valor contenido en uno de los registros
de propósito general. La palabra ‘indirecto’ hace referencia a que primero se obtiene el valor del registro y luego se utiliza dicho
valor como dirección de memoria. La expresión de la dirección efectiva y el valor del operando se muestran en la ecuación 7.5.

de = Rc1
operando = MEM[Rc1 ]

E QUATION 7.5: Dirección efectiva y operando del modo indirecto

Para codificar este modo de direccionamiento sólo es preciso incluir el código del registro de propósito general a utilizar, por
tanto con 3 bits es suficiente. La responsabilidad de que en el registro utilizado para la indirección esté contenida una dirección de
memoria correcta recae totalmente en el programador. La figura 7.6 ilustra el funcionamiento de este modo de direccionamiento
así como un ejemplo.
arquitectura IA-32
126 / 198

Figura 7.6: Acceso a operando con modo registro indirecto

Tal y como muestra la figura, la sintaxis para denotar este modo de direccionamiento en una instrucción es con el nombre del
registro escrito entre paréntesis. El operando se obtiene de memoria mediante la dirección almacenada en el registro. La principal
ventaja de este modo de direccionamiento es que, al estar almacenada la dirección en un registro, esta se puede manipular con
las operaciones aritméticas que ofrece el procesador como cualquier otro dato.
Por ejemplo, la instrucción MOV ( %esp), %eax carga en el registro %eax el valor almacenado en la cima de la pila. La
instrucción es correcta porque el registro %esp contiene la dirección de memoria de la cima de la pila.
Considérese la secuencia de instrucciones del ejemplo 7.2. La primera instrucción simplemente copia el valor del puntero de
pila en el registro %eax. La siguiente instrucción suma la constante $4 al valor almacenado en la cima de la pila. La instrucción
necesita el sufijo de tamaño porque el segundo operando es un registro indirecto, con lo que especifica la dirección de memoria
del operando, pero no su tamaño.

Ejemplo 7.2 Acceso a los elementos de la pila con el modo registro indirecto
mov %esp, %eax
addl $4, ( %eax)
add $4, %eax
addl $4, ( %eax)

La instrucción add $4, %eax muestra como una dirección de memoria se puede manipular como un dato numérico. Al sumarle
$4 el nuevo valor obtenido es la dirección de memoria del elemento almacenado en la pila justo debajo de la cima. La última
instrucción suma la constante $4 al operando almacenado en memoria en la dirección contenida en el registro %eax, o lo que es
lo mismo, al dato almacenado debajo de la cima de la pila.
En el ejemplo anterior, se ha manipulado el contenido de los datos almacenados en la pila sin modificar el puntero a su cima.
Esto es posible gracias a que se han hecho los cálculos con una dirección de memoria que es una copia del puntero a la cima.
El modo registro indirecto se puede utilizar para acceder a cualquier dato en memoria. En el ejemplo 7.3 se muestra una secuencia
de instrucciones que acceden a elementos de una tabla de datos de 16 bits almacenados a partir de la dirección de memoria
representada por la etiqueta tabla.
arquitectura IA-32
127 / 198

Ejemplo 7.3 Acceso a los elementos de una tabla con el modo registro indirecto
mov $tabla, %eax
mov ( %eax), %bx
add $2, %eax
mov ( %eax), %cx
add $6, %eax
mov ( %eax), %dx

La primera instrucción almacena en el registro %eax la dirección de memoria representada por la etiqueta tabla. El valor
exacto que se carga en el registro es imposible saberlo de antemano, pues depende de dónde en memoria estén almacenados los
datos, pero igualmente se puede utilizar con los modos de direccionamiento.
La segunda instrucción accede al primer elemento de la tabla con el modo registro indirecto. El operando destino es el registro %-
bx que por tanto, fija el tamaño de dato a ser transferido a 16 bits y no es necesario el sufijo de tamaño. La siguiente instrucción
suma la constante $2 al registro %eax y se accede de nuevo a un elemento de la tabla mediante registro indirecto. En este caso
se almacena en el registro %cx el número de 16 bits almacenado en segunda posición. La instrucción add $6, %eax hace que
el registro contenga la dirección del elemento en la quinta posición, o lo que es equivalente, con índice 4. La última instrucción
accede a este elemento de la tabla y lo almacena en %dx.

7.2.5. Modo auto-incremento

El funcionamiento del modo auto-incremento es similar al modo registro indirecto, con la salvedad de que el registro, tras ser
utilizado para el acceso a memoria, incrementa su valor en una constante. La expresión de la dirección efectiva y el valor del
operando se muestran en la ecuación 7.6

de = Rc1
operando = MEM[Rc1 ]
Rc1 Rc1 + 4

E QUATION 7.6: Dirección efectiva y operando del modo auto-incremento

El efecto de la modificación del valor contenido en el registro es que la dirección pasa ahora a apuntar a una posición de memoria
cuatro bytes más alta que el valor anterior. En principio, cualquier registro de propósito general puede ser utilizado, pero en el
caso concreto de la arquitectura IA-32, este modo únicamente se utiliza en la instrucción POP y con el registro %esp. La figura
7.7 ilustra el funcionamiento de este modo de direccionamiento así como un ejemplo de la instrucción POP.
arquitectura IA-32
128 / 198

Figura 7.7: Acceso a operando con modo auto-incremento

Al tratarse de la única instrucción de la arquitectura IA-32 que utiliza este modo de direccionamiento y que únicamente se utiliza
el registro %esp, la codificación en la instrucción de este modo de direccionamiento es un caso especial, pues está implícita
en el código de operación. El byte con valor 0x58 no sólo codifica la operación POP sino también la utilización del modo
auto-incremento con el registro %esp para obtener el dato a almacenar.
El efecto de este modo de direccionamiento corresponde con el comportamiento de la instrucción POP. El operando implícito es
el dato de la cima de la pila que se obtiene mediante la dirección de memoria almacenada en %esp. Este operando se almacena
donde indica el único operando explícito de la instrucción, y a continuación se incrementa el valor de %esp de forma automática
en 4 unidades. Como consecuencia, el puntero de pila apunta a la nueva cima.

7.2.6. Modo auto-decremento

El modo auto-decremento es similar al auto-incremento pues realiza una indirección y modifica el registro utilizado, pero la
modificación de la dirección se realiza antes de acceder a memoria. La funcionalidad de este modo de direccionamiento se puede
considerar complementaria a la anterior. La expresión de la dirección efectiva y el valor del operando se muestran en la ecuación
7.7

de = Rc1 4
operando = MEM[Rc1 4]
Rc1 Rc1 4

E QUATION 7.7: Dirección efectiva y operando del modo auto-decremento

La dirección efectiva no está directamente contenida en el registro especificado, sino que previamente se resta la constante 4 a
su valor y se accede a memoria con el valor resultante. Además, este valor, tras acceder a memoria, se almacena de nuevo en
el registro. En principio cualquier registro de propósito general puede utilizarse para este modo de direccionamiento, pero en
el caso de la arquitectura IA-32, este modo únicamente se utiliza en la instrucción PUSH y con el registro %esp. La figura 7.8
ilustra el funcionamiento de este modo de direccionamiento así como un ejemplo de la instrucción PUSH.
arquitectura IA-32
129 / 198

Figura 7.8: Acceso a operando con modo auto-decremento

Al igual que en el caso del modo auto-incremento, al ser PUSH la única instrucción que utiliza este modo en la arquitectura
IA-32, su codificación está implícita en el código de operación 0x6A. El operando implícito, por tanto es la dirección de la nueva
cima en la que almacenar el valor dado como operando explícito y que se obtiene restando 4 del registro %esp.

7.2.7. Modo base + desplazamiento

El modo de direccionamiento base + desplazamiento obtiene la dirección efectiva del operando mediante la utilización de dos
elementos. Este es el primer ejemplo en el que el procesador, como paso previo para la obtención de los operandos, obtiene más
de un dato de la instrucción y lo utiliza para calcular la dirección efectiva. La expresión de la dirección efectiva y el valor del
operando se muestran en la ecuación 7.8.

de = Rc1 + instc2
operando = MEM[Rc1 + instc2 ]

E QUATION 7.8: Dirección efectiva y operando del modo base + desplazamiento

En este modo, la dirección efectiva no está contenida en ningún lugar, sino que es el resultado de la suma del contenido de un
registro y de un valor almacenado como parte de la instrucción. El procesador obtiene estos dos valores, los suma, y el resultado
lo utiliza para acceder a memoria y obtener el operando. El nombre de este modo se deriva de que al registro utilizado se le conoce
como el nombre de ‘base’ mientras que el valor numérico adicional se conoce como ‘desplazamiento’. Como la operación que
se realiza entre ambos es la suma, se puede considerar que la dirección efectiva del operando se obtiene partiendo de un registro
base cuyo valor se desplaza tanto como indica la constante.
La sintaxis para especificar este modo de direccionamiento tiene dos posibles formatos. El primero consiste en escribir el nombre
del registro entre paréntesis precedido de una constante entera sin prefijo alguno, como por ejemplo la instrucción INCL 12-
( %ecx). La constante puede ser escrita en cualquiera de los formatos permitidos por el ensamblador: decimal, binario, octal,
hexadecimal o letra.
El segundo formato permitido consiste en escribir el nombre del registro entre paréntesis precedido por el nombre de una etiqueta
previamente definida, como por ejemplo la instrucción SUB %eax, dato( %ecx). El valor dato hace referencia a la dirección
de memoria en la que esta etiqueta ha sido definida, por lo que la dirección efectiva se obtiene sumando la dirección de la etiqueta
con el valor contenido en el registro base. En los programas ensamblador se utilizan ambas notaciones de forma muy frecuente.
La figura 7.9 ilustra el funcionamiento de este modo de direccionamiento así como dos ejemplos.
arquitectura IA-32
130 / 198

Figura 7.9: Acceso a operando con modo base + desplazamiento

La instrucción ADD %eax, 16( %ebx) utiliza el primer formato de este modo en su segundo operando. La instrucción codifica
el desplazamiento con sólo 8 bits al tener un valor entre -128 y 127. El byte ModR/M con valor 0x43 codifica que el modo
de direccionamiento del primer operando es registro y que el del segundo es base + desplazamiento con el registro %ebx y una
constante de 8 bits.
La instrucción ADD %eax, contador( %ecx) tiene una codificación de 6 bytes. Los cuatro últimos codifican la dirección de
memoria que representa la etiqueta contador. El byte ModR/M con valor 0x81 en este caso codifica el modo de direcciona-
miento del primer operando que es registro y que el del segundo es con registro base %ebx y desplazamiento de 32 bits.
Este modo de direccionamiento ofrece un mecanismo muy eficiente para acceder a tablas de elementos. Supóngase que se ha
definido una tabla de números enteros de 32 bits y se escribe el código que se muestra en el ejemplo 7.4.
arquitectura IA-32
131 / 198

Ejemplo 7.4 Acceso a una tabla de enteros con modo base + desplazamiento
.data
tabla: .int 12, 32, -34, -1, 1232, 435
.text
.global main
main:
...
mov $0, %ebx
mov $0, %ecx
ADD tabla( %ebx), %ecx
ADD $4, %ebx
ADD tabla( %ebx), %ecx
ADD $4, %ebx
ADD tabla( %ebx), %ecx
ADD $4, %ebx
...

Los enteros se almacenan a partir de la posición de memoria que representa la etiqueta tabla. Las dos primeras instrucciones
cargan el valor 0 en los registros %ebx y %ecx. Las siguientes instrucciones suman el valor de los tres primeros de la tabla y
depositan el resultado en el registro %ecx. Para ello, el registro %ebx contiene el valor que sumado a la dirección de tabla se
obtiene la dirección de los sucesivos elementos. La instrucción ADD tabla( %ebx), %ecx se repite sin modificación alguna
y accede a elementos sucesivos porque el registro base va cambiando su valor. Este tipo de instrucciones son muy eficientes si se
quiere procesar todos los elementos de una tabla, pues basta con escribir un bucle que vaya incrementando, en este caso, el valor
del registro %ebx.
La utilización de constantes como desplazamiento se utiliza generalmente para acceder a elementos almacenados en posiciones
alrededor de una dirección dada que se almacena en el registro base. Supóngase que el registro %edx contiene la dirección de me-
moria a partir de la cual están almacenados, por este orden, dos números enteros y cuatro letras ASCII. El código que se muestra
en la ejemplo 7.5 accede a los enteros y las cuatro letras con el registro %edx como base y con diferentes desplazamientos.

Ejemplo 7.5 Definición y acceso a una tabla de enteros


.data
.int 34
dato: .int 12, 24
.ascii "abcd"
.text
.global main
main:
...
mov $dato, %edx
mov 0( %edx), %ecx
add 4( %edx), %ecx
mov 8( %edx), %ah
mov 9( %edx), %al
mov 10( %edx), %bh
mov 11( %edx), %bl
add -4( %edx), %ecx
...

La primera instrucción almacena en %edx la dirección de memoria que representa la etiqueta dato. En la segunda instrucción
se utiliza el modo de direccionamiento base + desplazamiento pero con un desplazamiento igual a cero. El entero con valor 12
se almacena en el registro %ecx. Esta instrucción pone de manifiesto que el modo base + desplazamiento con un desplazamiento
igual a cero, es equivalente al modo registro indirecto.
La siguiente instrucción add 4( %edx), %ecx suma el valor del segundo entero al registro %ecx. El desplazamiento tiene
el valor 4 debido a que los enteros almacenados a partir de la etiqueta son de 4 bytes. La siguiente instrucción accede a la
arquitectura IA-32
132 / 198

primera letra y la almacena en el registro de 8 bits %ah. En este caso el desplazamiento es 8 porque las letras están almacenadas
en la posición siguiente a la del último byte del último entero de la definición anterior. Las siguientes instrucciones cargan las
siguientes letras en los registros de 8 bits %al, %bh y %bl.
En este modo de direccionamiento, el número que precede al registro entre paréntesis es un entero, y por tanto puede tener valor
negativo. La última instrucción muestra un ejemplo de este caso. De igual forma que se acceden a posiciones a continuación de la
especificada por una etiqueta, también se puede acceder a posiciones previas mediante desplazamientos negativos. El procesador
suma este desplazamiento al valor del registro y accede a esa posición de memoria. Con las definiciones del ejemplo la instrucción
está sumando al registro %ecx el número 34 almacenado justo antes de la etiqueta.

7.2.8. Modo base + índice

El modo de direccionamiento base + índice es similar al anterior puesto que el procesador obtiene la dirección efectiva sumando
de nuevo dos números, pero la diferencia es que ambos números se obtienen de registros de propósito general. La expresión de
la dirección efectiva y el valor del operando se muestran en la ecuación 7.9.

de = Rc1 + Rc2
operando = MEM[Rc1 + Rc2 ]

E QUATION 7.9: Dirección efectiva y operando del modo base + índice

Al igual que en caso del modo de direccionamiento anterior, la dirección efectiva no tiene por qué encontrarse en ningún registro
sino que se obtiene sumando el valor contenido en los dos registros especificados. No existe restricción alguna sobre los valores
que contienen los registros, el procesador realiza la suma y obtiene el operando de la posición de memoria obtenida como
resultado. La sintaxis para especificar este modo de direccionamiento es mediante dos registros separados por una coma y entre
paréntesis, por ejemplo CMPL $32, ( %eax, %esi).
A pesar de que este modo se denomina base + índice, los dos registros especificados son idénticos a todos los efectos, con
lo que cualquiera de los dos puede ser el registro base o el índice. La figura 7.10 ilustra el funcionamiento de este modo de
direccionamiento así como un ejemplo.

Figura 7.10: Acceso a operando con modo base + índice

La dirección efectiva se obtiene sumando los registros %ebx y %edi que previamente deben tener los valores pertinentes para
que la dirección de memoria resultante sea la correcta. Supóngase una tabla con 100 elementos, cada uno de ellos, a su vez es
una tabla de 5 enteros. Para acceder a un número se precisan dos índices, el primero para seleccionar uno de los 100 elementos,
arquitectura IA-32
133 / 198

y el segundo para seleccionar uno de los 5 posibles enteros. Se quiere acceder a los cinco números del elemento tabla[32]
almacenados a partir de la dirección de memoria contenida en el registro %eax. El ejemplo 7.6 muestra cómo acceder a estos
elementos utilizando el modo de direccionamiento base + índice.

Ejemplo 7.6 Acceso a los enteros de un elemento de una tabla


....
mov $0, %ebx
mov ( %eax, %ebx), %ecx
add $4, %ebx
add ( %eax, %ebx), %ecx
add $4, %ebx
add ( %eax, %ebx), %ecx
add $4, %ebx
add ( %eax, %ebx), %ecx
add $4, %ebx
add ( %eax, %ebx), %ecx
...

La primera instrucción carga el valor cero en %ebx. A continuación se accede al primer entero del elemento de la tabla sumando
la dirección a partir de donde están almacenados, que está contenida en %eax y el valor en %ebx. Como este último registro
tiene el valor cero, el acceso es idéntico al obtenido si se utiliza el modo registro indirecto.
A continuación se suma 4 al registro %ebx y se accede de nuevo con el mismo modo en este caso para sumar el contenido en
memoria al registro %ecx. Tras ejecutar esta instrucción en %ecx se obtiene la suma de los dos primeros elementos. Mediante
sucesivos incrementos del registro %ebx y luego accediendo a los elementos con el modo base + índice se calcula la suma de los
cinco números en el registro %ecx.
La misma secuencia de instrucciones tendría un efecto idéntico si se intercambian los nombres de los registros en el paréntesis
que especifica el modo base + índice.

7.2.9. Modo índice escalado + desplazamiento

En este modo de direccionamiento toman parte tres elementos de la instrucción para obtener la dirección efectiva del operando.
Un registro, denominado el índice, ofrece un valor que se multiplica por un factor de escala especificado por una constante, y el
resultado se suma a una segunda constante entera denominada desplazamiento. El factor de escala puede tener únicamente los
valores 1, 2, 4 y 8. La expresión de la dirección efectiva y el valor del operando se muestran en la ecuación 7.10.

de = (Rc1 ⇤ instc2 ) + instc3


operando = MEM[(Rc1 ⇤ instc2 ) + instc3 ]
instc2 2 {1, 2, 4, 8}

E QUATION 7.10: Dirección efectiva y operando del modo índice escalado + desplazamiento

La característica más importante de este modo de direccionamiento es el producto entre el registro índice y la escala. Este último
factor, en lugar de ser un número entero, tan sólo puede una de las cuatro primeras potencias de dos. La razón para esta restricción
es que la operación de multiplicación de enteros es extremadamente costosa en tiempo como para que forme parte del cálculo
de la dirección efectiva. En cambio, la multiplicación por estas potencias de dos es muy eficiente pues el resultado se obtiene
mediante el desplazamiento del valor del registro. Si el factor de escala utilizado es 1, este modo de direccionamiento es idéntico
al modo base + desplazamiento.
La sintaxis de este modo es ligeramente contra-intuitiva, pues se especifica el registro índice y el factor de escala separados
por coma pero precedidos por una coma adicional, entre paréntesis y este a su vez precedido por el entero que codifica el
desplazamiento. La figura 7.11 ilustra el funcionamiento de este modo de direccionamiento así como un ejemplo.
arquitectura IA-32
134 / 198

Figura 7.11: Acceso a operando con modo índice escalado + desplazamiento

La dirección efectiva del segundo operando de la instrucción de la figura se obtiene multiplicando el contenido de %eax por el
factor de escala 8 y sumando el desplazamiento. Al igual que en el caso del modo base + desplazamiento, este último elemento
puede ser un número entero o una etiqueta previamente definida. Considérese el ejemplo de una tabla de enteros de 64 bits
almacenados a partir de la etiqueta coeficientes. El ejemplo 7.7 muestra la definición y manipulación de estos datos con el
modo índice escalado + desplazamiento.
arquitectura IA-32
135 / 198

Ejemplo 7.7 Acceso a una tabla de enteros de 64 bits con modo índice escalado + desplazamiento
.data
coeficientes:
.quad 21, 34, 56, 98
.text
.global main
main:
...
mov $0, %eax
mov coeficientes(, %eax, 8), %ebx
inc %eax
add coeficientes(, %eax, 8), %ebx
inc %eax
add coeficientes(, %eax, 8), %ebx
inc %eax
add coeficientes(, %eax, 8), %ebx
...

Los números definidos por la directiva .quad son de 64 bits y por tanto ocupan 8 bytes. Tras cargar el valor 0 en el registro %e-
ax, las siguientes instrucciones acceden a los números de forma sucesiva utilizando este registro como índice. La multiplicación
del registro índice por el factor de escala 8 que coincide con el tamaño de los datos hace que el índice coincida con el valor que
se utilizaría en un lenguaje de alto nivel como Java para acceder a los números: coeficientes[3] se accede mediante la
expresión coeficientes(, %eax, 8) si %eax contiene el valor 3.

7.2.10. Modo base + índice escalado + desplazamiento

Este modo de direccionamiento es el más complejo que ofrece el procesador y se puede considerar como la combinación de los
modos base + desplazamiento e índice escalado + desplazamiento. La dirección efectiva se calcula sumando tres números: el
desplazamiento, el contenido de un registro base y la multiplicación de un registro índice por un factor de escala que puede tener
los valores 1, 2, 4 u 8. La expresión de la dirección efectiva y el valor del operando se muestran en la ecuación 7.11.

de = Rc1 + (Rc2 ⇤ instc3 ) + instc4


operando = MEM[Rc1 + (Rc2 ⇤ instc3 ) + instc4 ]
instc3 2 {1, 2, 4, 8}

E QUATION 7.11: Dirección efectiva y operando del modo base + índice escalado + desplazamiento

Este modo de direccionamiento precisa cuatro elementos que están contenidos en la codificación de la instrucción. El campo que
más espacio requiere es el desplazamiento que puede ser un valor entero o el nombre de una etiqueta previamente definida, por
lo que se precisan 32 bits. El factor de escala, al poder tomar únicamente cuatro valores, se puede codificar con 2 bits.
La sintaxis de este modo es también una combinación de los anteriores. Entre paréntesis se escribe el registro base, el registro
índice y el factor de escala, por este orden y separados por comas. El paréntesis va precedido del valor del desplazamiento. La
figura 7.12 ilustra el funcionamiento de este modo de direccionamiento así como un ejemplo.
arquitectura IA-32
136 / 198

Figura 7.12: Acceso a operando con modo base + índice escalado + desplazamiento

El efecto de este modo de direccionamiento es que la dirección efectiva se obtiene sumando un registro, una constante (el
desplazamiento) y un segundo registro susceptible de ser escalado. Al igual que en el modo índice escalado + desplazamiento,
la multiplicación por la escala se realiza desplazando el operando cero, una, dos o tres posiciones hacia su bit de más peso. Al
igual que en el resto de modos que utilizan un desplazamiento, este puede ser un número entero o una etiqueta previamente
definida. En la instrucción DIVL 4( %ecx, %edi, 4) mostrada en la figura, la dirección efectiva del operando es 4 + %ecx
+ ( %edi * 4), por lo que se asume que el registro %ecx contiene el valor de una dirección con respecto a la cual se accede
al operando. En la instrucción INCB contador( %ecx, %edi, 4) el desplazamiento es una dirección de memoria en base
de la cual se obtiene la dirección efectiva del operando.
Como ejemplo de utilización de este modo de direccionamiento considérese que se ha definido una tabla de 10 enteros en Java a
partir de la posición de memoria con etiqueta num. A continuación se quiere acceder al elemento de la tabla con índice almace-
nado en el registro %ecx e incrementar su valor en una unidad. El código mostrado en el ejemplo 7.8 muestra las instrucciones
necesarias para esta operación.
arquitectura IA-32
137 / 198

Ejemplo 7.8 Acceso a una tabla de enteros en Java con modo base + índice escalado + desplazamiento
.data
num: .int 10, -1, 32, 345, -3556, 4, 21, 23, 15, 6543, 23
.text
.global main
main:
...
mov $4, %ebx
incl num( %ebx, %ecx, 4)
...

Como Java almacena el tamaño de una tabla en los cuatro primeros bytes, la dirección del elemento con índice en %ecx se
obtiene mediante la expresión num + 4 + ( %ecx * 4) que se puede calcular de forma eficiente utilizando el modo base + índice
escalado + desplazamiento. Si se quisiese incrementar el valor de todos los elementos de la tabla se puede escribir un bucle que
incremente el valor del registro %ecx desde cero hasta el tamaño de la tabla menos uno e incremente cada uno de ellos con una
instrucción idéntica a la del ejemplo.

7.2.11. Utilización de los modos de direccionamiento

Se han estudiado los modos de direccionamiento que ofrece el procesador como mecanismo eficiente de acceso a memoria. No
todos los cálculos de la dirección efectiva de un operando pueden realizarse en una sola instrucción por medio de estos modos,
tan sólo aquellos que requieran el tipo de operaciones que ofrece alguno de ellos. Los modos de direccionamiento son por tanto,
un recurso que el procesador ofrece para ganar eficiencia en la ejecución de programas, pero que de ninguna forma limita la
forma de obtener la dirección efectiva.
A la hora de programar en ensamblador y acceder a datos en memoria, la técnica para acceder a los datos es tener en cuenta qué
operaciones se deben realizar para obtener su dirección, y si éstas pueden ser incluidas en una misma instrucción como modo de
direccionamiento mejor que realizar estos cálculos con instrucciones máquina adicionales.
Supóngase que se define una matriz de enteros con m filas y n columnas a partir de la posición representada por la etiqueta matr-
iz. Los valores m y n son enteros y están almacenados en memoria con etiquetas del mismo nombre. Las matrices se almacenan
en memoria utilizando múltiples estrategias. Las dos más comunes son por filas y por columnas. En el primer formato, se
almacenan los elementos de una fila en posiciones contiguas de memoria y a continuación los de la siguiente fila. En el segundo
formato, los elementos de una columna ocupan posiciones de memoria consecutivas, y a continuación se almacena la siguiente
columna. Supóngase que los elementos de esta matriz están almacenados por filas. La figura 7.13 muestra la distribución de los
datos en memoria mediante su definición en ensamblador. Cada posición de la matriz contiene un número formado por el número
de fila seguido del número de columna.

Figura 7.13: Definición de una matriz de enteros almacenada por filas

Para acceder a un elemento de la matriz se precisan dos índices (i, j), donde 0 i < m y 0 j < n. Dados los índices (i, j), la
expresión de la dirección efectiva de este elemento según la definición de la figura 7.13 se muestra en la ecuación 7.12.

de = $matriz + (i ⇤ n ⇤ 4) + ( j ⇤ 4)

E QUATION 7.12: Dirección de un elemento en una matriz de enteros


arquitectura IA-32
138 / 198

Supóngase que se tiene que acceder al elemento en la posición que indican los registros %eax y %ebx para incrementar en una
unidad su valor mediante la instrucción INC. Dada la funcionalidad ofrecida en los modos de direccionamiento, no es posible
acceder al elemento con una única instrucción, pues el cálculo de su dirección efectiva requiere operaciones no contempladas.
Pero una porción de la ecuación 7.12 sí puede ser calculada por el modo de direccionamiento base + índice escalado + despla-
zamiento. Como desplazamiento se utiliza el valor de la etiqueta matriz, la segunda multiplicación se puede ejecutar como un
índice escalado, por lo que tan sólo es preciso obtener el resultado de (i * n * 4) y almacenarlo en un registro. El ejemplo 7.9
muestra una posible secuencia de instrucciones para acceder e incrementar el elemento.

Ejemplo 7.9 Instrucciones para incrementar un elemento de una matriz de enteros


...
mull n
sal $2, %eax
incl matriz( %eax, %ebx, 4)
...

La instrucción mull n multiplica el número de columnas por el índice que indica el número de fila. Tal y como funciona esta
instrucción, al especificar un operando de 32 bits mediante el sufijo, resultado se almacena en el registro de 64 bits obtenido
al concatenar %edx: %eax. Se asume que el resultado no sobrepasa los 32 bits de %eax. A continuación la instrucción sal
$2, %eax desplaza el registro dos posiciones a su izquierda que equivale a multiplicar por cuatro. Con esto se obtiene en %eax
el término de la ecuación 7.12 que falta para poder utilizar el modo base + índice escalado + desplazamiento tal y como muestra
la instrucción incl matriz( %eax, %ebx, 4).
Como efectos colaterales de este cálculo se ha perdido el valor inicial del índice almacenado en %eax así como el valor del
registro %edx ambos modificados por la instrucción de multiplicación. A modo de comparación, el ejemplo 7.10 muestra una
secuencia alternativa de instrucciones para realizar la misma operación pero que únicamente utiliza el modo registro indirecto.

Ejemplo 7.10 Instrucciones para incrementar un elemento de una matriz de enteros utilizando el modo registro indirecto
...
mull n
sal $2, %eax
sal $2, %ebx
add %ebx, %eax
add $matrix, %eax
incl ( %eax)
...

Las instrucciones adicionales realizan los cálculos equivalentes al modo base + índice escalado + desplazamiento solo que
utilizando instrucciones máquina del procesador. El resultado de esta secuencia es casi idéntico al anterior (en este caso se ha
perdido también el valor dado en el %ebx) pero se ha utilizado un número muy superior de instrucciones, con lo que su ejecución
es mucho menos eficiente.
Este ejemplo pone de manifiesto cómo el acceso a los operandos puede realizarse de múltiples formas, pero para obtener una
ejecución eficiente y código compacto debe seleccionarse aquella que haga uso de los modos de direccionamiento ofrecidos por
el procesador.

7.3. Hardware para el cálculo de la dirección efectiva

Una vez estudiados los modos de direccionamiento que ofrece el procesador se puede intuir el tipo de circuito digital utilizado
para calcular la dirección efectiva de un operando almacenado en memoria. La figura 7.14 muestra una posible implementación.
arquitectura IA-32
139 / 198

Figura 7.14: Circuito para el cálculo de la dirección efectiva

Este circuito calcula la dirección efectiva para los modos de direccionamiento cuyo operando se encuentra en memoria, es decir,
todos excepto inmediato y registro. De los bits que codifican la instrucción se obtienen los cuatro posibles elementos el cálculo
de la dirección efectiva y mediante la utilización de las señales de control b, i, e y d se activa la participación de cada una de
ellos. Por ejemplo, el modo índice escalado + desplazamiento requiere que la señal i seleccione la entrada del multiplexor que
procede del banco de registros, la señal d seleccione el valor obtenido de la instrucción, y las dos señales restantes seleccionen la
entrada constante de sus respectivos multiplexores.

7.4. Resumen de los modos de direccionamiento

Los modos de direccionamiento son los diferentes procedimientos que utiliza el procesador dentro de la ejecución de una ins-
trucción para acceder a sus operandos. Las diferentes formas que permite la arquitectura IA-32 para acceder a sus operandos se
muestran en la Tabla 7.1.

Modo de
Dirección efectiva Operando Condiciones adicionales
direccionamiento
Inmediato @inst + k MEM[@inst + k]
Registro instc1 Rc1
Absoluto MEM[@inst + k] MEM[MEM[@inst + k]]
Registro indirecto Rc1 MEM[Rc1 ]
Auto-incremento Rc1 MEM[Rc1 ] Rc1 Rc1 + 4
Auto-decremento Rc1 4 MEM[Rc1 4] Rc1 Rc1 4
Base + desplazamiento Rc1 + instc2 MEM[Rc1 + instc2 ]
Base + índice Rc1 + Rc2 MEM[Rc1 + Rc2 ]
Índice escalado + MEM[(Rc1 ⇤ instc2 ) +
(Rc1 ⇤ instc2 ) + instc3 instc2 2 {1, 2, 4, 8}
desplazamiento instc3 ]
Base + índice escalado + Rc1 + (Rc2 ⇤ instc3 ) + MEM[Rc1 + (Rc2 ⇤
instc3 2 {1, 2, 4, 8}
desplazamiento instc4 instc3 ) + instc4 ]

Tabla 7.1: Modos de direccionamiento de la arquitectura IA-32

7.5. Ejercicios

1. Asumiendo que los campos de una instrucción máquina son ci1 , ci2 , ci3 , ci4 ,... escribir la fórmula del cálculo de la dirección
efectiva del operando y explicar su significado para los siguientes modos de direccionamiento: (Utilícese la notación (x)
para denotar ‘el contenido de x’).
arquitectura IA-32
140 / 198

a. Registro Indirecto:
b. Absoluto:
c. Base + Índice:
d. Base + Índice Escalado + Desplazamiento:

2. Supóngase que de todos los modos de direccionamiento de la arquitectura IA-32, los únicos que se pueden utilizar son
el modo registro, modo inmediato y el modo registro indirecto. Escribir la secuencia de instrucciones equivalentes a las
siguientes: (es decir que si se reemplaza la instrucción con las instrucciones de cada respuesta, el programa resultante es
idéntico).
a.
MOV matrix( %ebx), %eax

b.
MOV table(, %ecx, 4), %eax

3. Un procesador llamado PDP-11 contiene en su juego de instrucciones dos modos de direccionamiento que no posee la
arquitectura IA-32.

Modo Autoincremento Indirecto: Se representa como [Reg]++. El procesador accede a la posición de memoria conte-
nida en el registro Reg y de dicha posición de memoria obtiene la dirección de memoria del operando. El registro Reg
queda incrementado en cuatro unidades.
Modo Indexado Indirecto: Se representa como $desp[Reg]. El procesador accede a la posición de memoria resultante
de sumar Reg y $desp, y de dicha posición de memoria obtiene la dirección de memoria del operando.

Especificar cómo deben traducirse las siguientes instrucciones del PDP-11 a instrucciones de la arquitectura IA-32 para
que la ejecución sea equivalente.

MOV [ %eax]++, %ebx


MOV %ecx, $desp[ %ecx]

4. Considerando el circuito de la figura 7.14, rellenar los valores de las señales b, i, e y d para cada uno de los modos de
direccionamientos de la siguiente tabla. La entrada constante de los multiplexores se selecciona poniendo la señal de
control con valor idéntico a esta.

Modo de
Valor de b Valor de i Valor de e Valor de d
direccionamiento
Absoluto 0 0 1 1
Registro indirecto 1 0 1 0
Base +
1 0 1 1
desplazamiento
Base + índice 1 1 1 0
Índice escalado +
0 1 0 1
desplazamiento
Base + índice
escalado + 1 1 0 1
desplazamiento
arquitectura IA-32
141 / 198

Capítulo 8

Construcciones de alto nivel

Las aplicaciones que se ejecutan en un ordenador están generalmente programadas en alguno de los denominados lenguajes de
alto nivel que los compiladores traducen a ejecutables que contienen secuencias de instrucciones máquina del procesador. Para
facilitar el desarrollo de estas aplicaciones se necesitan mecanismos adicionales tanto a nivel de procesador como de sistema
operativo. Por ejemplo, la posibilidad de fragmentar el código en múltiples ficheros, gestión del acceso de símbolos en otros
ficheros, etc.
En este capítulo se estudian los mecanismos que facilitan la traducción de programas en lenguajes de alto nivel a programas
en ensamblador. Además de la fragmentación de código en múltiples ficheros, se estudia en detalle el procedimiento para la
invocación, paso de parámetros y ejecución de subrutinas, y la traducción de estructuras de control presentes en lenguajes de
programación a lenguaje ensamblador.

8.1. Desarrollo de aplicaciones en múltiples ficheros

La funcionalidad que ofrece un procesador está basada en sus instrucciones máquina, órdenes muy simples para manipular datos.
Pero para programar operaciones más complejas se precisa un lenguaje que sea más intuitivo y que abstraiga o esconda los
detalles de la arquitectura del procesador. A este tipo de lenguajes se les denomina ‘lenguajes de alto nivel’ por contraposición
al lenguaje ensamblador cuya estructura y construcciones están directamente relacionadas con la arquitectura del procesador que
lo ejecuta. La traducción de las operaciones en un lenguaje de alto nivel a secuencias de instrucciones máquina se lleva a cabo
por el compilador.
Las principales limitaciones que se derivan del uso del lenguaje ensamblador son:

Las aplicaciones que contengan manejo de datos u operaciones complejas requieren secuencias de instrucciones extremada-
mente largas, y por tanto, es muy fácil que se introduzcan errores.
El lenguaje ensamblador carece de tipos de datos. A pesar de que existen directivas para definir datos, su efecto no es más
que almacenar una secuencia de bytes en memoria. El procesador accede a estos datos como una secuencia de bytes sin
información de tamaño ni de su tipo.
Las subrutinas ofrecen un mecanismo básico para ejecutar porciones de código de forma repetida y con diferentes datos, pero
no se realiza ningún tipo de comprobación de su correcta invocación.

Los lenguajes de alto nivel solventan estas limitaciones ofreciendo un conjunto de mecanismos para definir y manipular datos.
Cada lenguaje tiene su propia sintaxis, o forma de escribir las órdenes, y su semántica, o cómo esas órdenes son traducidas a
secuencias de instrucciones máquina.
Al conjunto de reglas para definir y manipular los tipos de datos en un lenguaje de alto nivel se le denomina el ‘sistema de tipos de
datos’. Aquellos lenguajes que estructuran sus datos en base a objetos que se crean a partir de una definición genérica denominada
‘clase’ se les conoce como ‘lenguajes orientados a objeto’. Java, Smaltalk y C++ son algunos de los múltiples lenguajes con esta
característica.
arquitectura IA-32
142 / 198

El proceso de compilación de los programas escritos en lenguajes de alto nivel es similar al de traducción de lenguaje ensamblador
a lenguaje máquina. Dado un conjunto de ficheros escritos en el lenguaje de entrada, se produce un ejecutable que contiene la
traducción de todos ellos a instrucciones máquina y definiciones de datos. La figura 8.1 muestra el procedimiento por el que dado
un conjunto de ficheros en lenguaje de alto nivel, el compilador obtiene un fichero ejecutable.

Figura 8.1: Compilación de un programa escrito en un lenguaje de alto nivel

El lenguaje de programación Java merece una mención especial, pues no sigue el patrón de traducción que se muestra en la
figura 8.1. Los programas se escriben en múltiples ficheros que contienen la definición de clases con sus campos y métodos. El
proceso de compilación no produce directamente un ejecutable sino un fichero con formato ‘class’ o ‘bytecode’. Este formato
no corresponde con instrucciones máquina del procesador sino con instrucciones de lo que se conoce como ‘máquina virtual de
java’ o JVM (Java Virtual Machine).
La traducción a código de la JVM se realiza para garantizar la ‘portabilidad’ de un programa, es decir, que el fichero generado se
pueda ejecutar sin modificaciones en cualquier procesador. La máquina virtual lee el código escrito en formato class y lo traduce
a instrucciones máquina del procesador sobre el que se ejecuta. Esta traducción se hace en el momento en el que se ejecuta un
programa. Por este motivo se dice que Java es un lenguaje parcialmente compilado y parcialmente interpretado. La compilación
traduce el código inicial a formato bytecode que a su vez es interpretado en tiempo de ejecución por la máquina virtual.
Mediante la presencia de esta máquina virtual, se garantiza la compatibilidad de los programas Java en cualquier procesador.
Para ello es preciso crear una máquina virtual diferente para cada uno de los procesadores existentes en el mercado. Una vez
implementada esta máquina virtual, todos los programas escritos en Java son ejecutables en esa plataforma.
Existen otro tipo de lenguajes de alto nivel que no precisan de un paso previo de compilación para obtener un ejecutable, sino
que se ejecutan directamente a través de un programa auxiliar denominado ‘intérprete’ cuyo cometido es similar al compilador
sólo que su tarea la hace justo en el instante que el programa debe ser ejecutado, y no como paso previo. A estos lenguajes de
alto nivel se les denomina ‘interpretados’ pues el proceso que se lleva a cabo en el momento de la ejecución es una interpretación
del código fuente y su traducción instantánea a código máquina. Perl, Python y TCL son algunos ejemplos de lenguajes de
programación interpretados.

8.2. Programas en múltiples ficheros

La generación de un programa a partir de un conjunto de ficheros con código fuente procesándolos se realiza en dos pasos. En
el primero se traduce cada fichero por separado a código máquina. En el segundo paso denominado de ‘enlazado’ (ver sección
1.5.2) se combinan las porciones de código generadas en el paso anterior y se crea el fichero ejecutable. Para ello se precisan dos
mecanismos:

Política de acceso a los símbolos definidos en cada uno de los ficheros


Ejecución parcial de un fragmento de código en un fichero diferente al que se está ejecutando y su retorno al mismo punto una
vez terminado.
arquitectura IA-32
143 / 198

Cada fichero ensamblador contiene un conjunto de etiquetas que representan diferentes posiciones de memoria. Para que un
programa pueda ser fragmentado debe ser posible referirse a un símbolo definido en otro fichero. Por ejemplo, una instrucción
de salto debe poder especificar como destino un punto en el código de otro fichero.
Pero si una aplicación consta de múltiples ficheros cada uno de ellos con un número muy elevado de etiquetas definidas, tareas
como la ampliación de un programa se vuelven muy difíciles. Si los símbolos definidos en los ficheros son todos ellos globales,
no se puede utilizar un nombre para una variable o una posición en el código que esté presente en otro fichero.
Para solucionar este problema se adopta la política de gestión opuesta para el ámbito de los símbolos. Todo símbolo definido en
un fichero tiene como ámbito de validez únicamente el propio fichero a no ser que se especifique lo contrario con la directiva de
ensamblador .global. El ensamblador permite definir una etiqueta que coincide con el nombre de otra definida como global.
En este caso, el símbolo local toma precedencia y por tanto el global no es accesible. De esta forma, cuando se escribe código
ensamblador en un fichero, en principio se puede utilizar cualquier nombre para una etiqueta.
En el primer paso de la traducción, todo símbolo que no esta definido en el fichero que se procesa se considera externo, y por
tanto su posición es desconocida. Es en el paso de entrelazado en el que los símbolos son todos conocidos y se pueden traducir
a sus correspondientes valores. El compilador incluye en cada fichero obtenido en el primer paso dos conjuntos de símbolos: el
primero corresponde con las etiquetas definidas en la zona de datos o de código que han sido declaradas globales, mientras que
el segundo contiene aquellos que se utilizan pero cuya definición no se ha encontrado en el fichero. La figura 8.2 muestra un
ejemplo de programa escrito en dos ficheros en los que se producen referencias a símbolos externos.

Figura 8.2: Referencia a símbolos en dos ficheros

En la fase de entrelazado, para cada fichero, el compilador busca los símbolos externos en la lista de símbolos globales del resto
de ficheros. En el caso de que un símbolo externo no esté definido en ninguno de ellos se muestra un mensaje de error. Si dos
ficheros definen el mismo símbolo como global también se muestra un mensaje de error. En ambos casos no se produce fichero
ejecutable.
Además de los símbolos contenidos en cada uno de los ficheros, en la fase de entrelazado el compilador dispone de código
auxiliar en ficheros denominados ‘bibliotecas’ en los que se incluyen rutinas para realizar tareas comunes de cualquier programa
como lectura/escritura de datos a través de diferentes dispositivos (teclado, pantalla, ficheros, etc).
Otro aspecto que debe solventar el compilador para generar un ejecutable a partir de múltiples ficheros fuente es el de la ‘reubi-
cación de código’. Al traducir el código ensamblador contenido en cada fichero, el código binario resultante se almacena a partir
de la posición cero de memoria, pues no se sabe la posición exacta que ocupará a la hora de ejecutar. Pero cuando el código está
en múltiples ficheros, en la fase de entrelazado sólo el código de uno de ellos puede estar en la posición inicial, el resto debe ser
reubicado. La figura 8.3 muestra un ejemplo en el que el ejecutable se obtiene a partir de tres ficheros. El código de dos de ellos
debe ser reubicado.
arquitectura IA-32
144 / 198

Figura 8.3: Reubicación de símbolos en la fase de entrelazado

La reubicación de código consiste en que toda instrucción que contenga en su codificación el valor de una dirección de memoria
(por ejemplo, las que utilizan el modo de direccionamiento absoluto) deben ser modificadas para referirse a la posición de
memoria tras la reubicación. El compilador recorre de nuevo las instrucciones máquina generadas y suma a toda dirección de
memoria un factor de reubicación que corresponde con la dirección utilizada al comienzo del fichero.
Considérese la instrucción call metodo1 que invoca a la subrutina método definida en otro fichero. En la primera fase
de compilación esta instrucción se traduce por el código 0xE8FCFFFFFF. Los cuatro últimos bytes denotan la dirección de
memoria representada por la etiqueta método. En el ejecutable obtenido, el código de esta instrucción pasa a ser 0xE8FA000-
000 que conserva el primer byte que corresponde con el código de operación pero cambia los cuatro últimos bytes que codifican
la dirección de memoria.

8.3. Traducción de construcciones de alto nivel a ensamblador

Las construcciones que ofrecen los lenguajes de alto nivel como Java para escribir programas distan mucho de la funcionalidad
ofrecida por el lenguaje máquina del procesador. Por ejemplo, en Java se permite ejecutar una porción de código de forma
iterativa mediante las construcciones for o while hasta que una condición se deje de cumplir. El compilador es el encargado
de producir el código ensamblador tal que su ejecución sea equivalente a la especificada en el lenguaje Java.
A continuación se muestra cómo la funcionalidad ofrecida por el procesador es suficiente para traducir estas construcciones a
secuencias de instrucciones ensamblador con idéntico significado.

8.3.1. Traducción de un if/then/else

La figura 8.4 muestra las tres partes de las que consta un bloque if/then/else. La palabra reservada if va seguida de una expresión
booleana entre paréntesis. A continuación entre llaves una primera porción de código que puede ir seguida opcionalmente de una
segunda porción también entre llaves y con el prefijo else.
arquitectura IA-32
145 / 198

if (expresión booleana) {
Bloque A
} else {
Bloque B
}

Figura 8.4: Estructura de un if/then/else

Lo más importante para traducir un bloque a ensamblador es saber su significado o semántica. La semántica del bloque if/then/else
es que se evalua la expresión booleana y si el resultado es verdadero se ejecuta el bloque A de código y se ignora el bloque B, y
si es falsa, se ignora el bloque A y se ejecuta el bloque B.
El elemento clave para traducir esta construcción a ensamblador es la instrucción de salto condicional. Este tipo de instrucciones
permiten saltar a un destino si una condición es cierta o seguir la secuencia de ejecución en caso de que sea falsa. Lo único que
se necesita es traducir la expresión booleana de alto nivel a una condición que pueda ser comprobada por una de las instrucciones
de salto condicional ofrecida por el procesador. Supóngase que la expresión es falsa si el resultado de la evaluación es cero y
cierta en caso contrario. Además, tras ejecutar las instrucciones de evaluación, el resultado se almacena en %eax. En la figura
8.5 se muestra la estructura genérica en lenguaje ensamblador resultante de traducir un if/then/else en este supuesto.

... # Evaluar la expresión booleana


... # Resultado en %eax
cmp $0, %eax
je bloqueb
... # Traducción del bloque A
... # Fin del bloque A
jmp finifthenelse
bloqueb: # Traducción del bloque B
...
... # Fin del bloque B
finifthenelse:
... # Resto del programa

Figura 8.5: Traducción de un if/then/else a ensamblador

Tras la evaluación de la condición, el resultado previamente almacenado en %eax se compara, y si es igual a cero se ejecuta el
salto que evita la ejecución del bloque A. En el caso de un if/then/else sin el bloque B, el salto sería a la etiqueta finifthen-
else.
En un bloque genérico de este tipo no es preciso asumir que el resultado de la condición está almacenado en %eax. El ejemplo
8.1 muestra la traducción de un if/then/else con una condición booleana con múltiples operaciones. Se asume que las variables
x, i y j son de tipo entero y están almacenadas en memoria con etiquetas con el mismo nombre.
arquitectura IA-32
146 / 198

Ejemplo 8.1 Traducción de un if/then/else a ensamblador

Código de alto nivel Código ensamblador

if ((x <= 3) && (i == (j + 1))) {


Bloque A cmpl $3, x # Comparar si x <= -
} else { 3
Bloque B jg bloqueB # Si falso ir a -
bloque B
}
mov j, %eax # Obtener j + 1
inc %eax
cmp %eax, i # Comparar i == (j -
+ 1)
jne bloqueB # Si falso ir a -
bloque B
... # Traducción del -
bloque A
...
jmp finifthenelse # Evitar bloque B
bloqueB:
... # Traducción del -
bloque B
...
finifthenelse: # Final de -
traducción

La primer expresión de la conjunción se traduce en una única instrucción cmp. Si esta comparación no es cierta, al tratarse de
una conjunción, se debe ejecutar el bloque B sin necesidad de seguir evaluando. Esto se consigue con el salto jg que contiene la
condición contraria a la del código (x <= 3).
Si la primera parte de la conjunción evalúa a cierto, se pasa a evaluar la segunda. Primero se obtiene el valor de j, se copia en un
registro y se incrementa, pues el código de alto nivel no modifica el valor almacenado en memoria. A continuación se compara
con la variable i y de nuevo, mediante un salto condicional, se ejecuta el bloque pertinente.

8.3.2. Traducción de un switch

A menudo en programación es preciso realizar una operación y ejecutar diferentes bloques de código dependiendo del valor
obtenido. La construcción switch mostrada en la figura 8.6 ofrece exactamente esta funcionalidad

switch (expresión) {
case valor A:
Bloque A
break; // Opcional
case valor B:
Bloque B
break; // Opcional
...
default:
Bloque por defecto // Opcional
}

Figura 8.6: Estructura de un switch

La semántica de esta construcción establece que primero se evalúa la condición y a continuación se compara el resultado con los
valores de cada bloque precedido por la palabra clave case. Esta comparación se realiza en el mismo orden en el que se definen
arquitectura IA-32
147 / 198

en el código y si alguna de estas comparaciones es cierta, se pasa a ejecutar el código restante en el bloque (incluyendo el resto
de casos). Si ninguna de las comparaciones es cierta se ejecuta (si está presente) el caso con etiqueta default. La palabra clave
break se puede utilizar para transferir el control a la instrucción que sigue al bloque switch.
La estructura del código ensamblador para implementar esta construcción debe comenzar por el cálculo del valor de la expresión.
A continuación se compara con los valores de los casos siguiendo el orden en el que aparecen en el código. Si una comparación
tiene éxito, se ejecuta el bloque de código que le sigue. Si se encuentra la orden break se debe saltar al final del bloque. En
el caso de que ninguna comparación tenga éxito, se debe pasar a ejecutar el bloque default. Supóngase que la evaluación
de la expresión es un valor que se almacena en el registro %eax. En la figura 8.7 se muestra la estructura genérica en lenguaje
ensamblador resultante de traducir un switch en este supuesto.

... # Evaluar la expresión


... # Resultado en %eax
cmp $valorA, %eax # Caso A
je bloquea
cmp $valorB, %eax # Caso B
je bloqueb
...
jmp default
bloquea:
... # Traducción del bloque A
... #
jmp finswitch # Si bloque A tiene break
bloqueb:
...
...
jmp finswitch # Si bloque B tiene break
...
...
default:
... # Caso por defecto
...
finswitch:
... # Resto del programa

Figura 8.7: Traducción de un switch a ensamblador

En este esquema se asume que tras obtener el valor de la expresión, éste se mantiene en el registro %eax. La presencia de la línea
break corresponde directamente con la instrucción jmp finswitch. El ejemplo 8.2 muestra la traducción de un switch. Se
asume que las variables son todas de tipo entero y están almacenadas en memoria con etiquetas con el mismo nombre.
arquitectura IA-32
148 / 198

Ejemplo 8.2 Traducción de un switch a ensamblador

Código de alto nivel Código ensamblador

switch (x + i + 3 + j) {
case 12: mov x, %eax # Evaluar -
Bloque A la expresión
break; add i, %eax
case 14: add $3, %eax
case 16: add j, %eax
Bloque B cmp $12, %eax # Caso 12
case 18: je bloquea
Bloque C cmp $14, %eax # Caso 14
break; je bloqueb
default: cmp $16, %eax # Caso 16
Bloque D je bloqueb
cmp $18, %eax # Caso 18
}
je bloquec
jmp default
bloquea:
... # -
Traducción del bloque A
... #
jmp finswitch # Bloque -
A tiene break
bloqueb:
... # -
Traducción del bloque B
...
bloquec:
... # -
Traducción del bloque C
...
jmp finswitch # Bloque -
C tiene break
default:
... # Bloque -
D
...
finswitch:
... # Resto -
del programa

La condición del ejemplo es una suma de cuatro operandos, con lo que las instrucciones ensamblador correspondientes obtienen
los operandos y acumulan su suma en %eax. A continuación se comparan los sucesivos casos. Si alguna de ellas tiene éxito se
pasa a ejecutar el correspondiente bloque de código. Si todas ellas fallan, se ejecuta el salto incondicional al bloque default.
En cada uno de los bloques, si está presente la palabra reservada break, ésta se traduce en un salto incondicional al final del
bloque.

8.3.3. Traducción de un bucle while

Una de las construcciones más comunes en lenguajes de alto nivel para ejecutar código de forma iterativa es el bucle while. La
figura 8.8 muestra su estructura. La palabra reservada while da paso a una expresión booleana entre paréntesis que se evalúa y
en caso de ser cierta pasa a ejecutar el bloque del código interno tras cuyo final se vuelve de nuevo a evaluar la condición.
arquitectura IA-32
149 / 198

while (expresión booleana) {


Código interno
}

Figura 8.8: Estructura de un bucle while

En este bloque es importante tener en cuenta que la expresión booleana se evalúa al menos una vez y se continúa evaluando hasta
que sea falsa. Supóngase que la evaluación de la expresión es cero en caso de ser falsa y diferente de cero si es cierta y el valor
resultante se almacena en %eax. En la figura 8.9 se muestra la estructura genérica en lenguaje ensamblador resultante de traducir
un bucle while en este supuesto.

eval: ... # Evaluar la expresión booleana


... # Resultado en %eax
cmp $0, %eax
je finwhile
... # Traducción del código interno
...
jmp eval
finwhile:
... # Resto del programa

Figura 8.9: Traducción de un bucle while a ensamblador

Tras evaluar la condición se ejecuta una instrucción que salta al final del bloque si es falsa. En caso de ser cierta se ejecuta el
bloque de código y tras él un salto incondicional a la primera instrucción con la que comenzó la evaluación de la condición.
El destino de este salto no puede ser la instrucción de comparación porque es muy posible que las variables que intervienen en
la condición hayan sido modificadas por lo que la evaluación se debe hacer a partir de estos valores. El ejemplo 8.3 muestra
la traducción de un bucle while con una de estas condiciones. Se asume que las variables x, i y j son de tipo entero y están
almacenadas en memoria con etiquetas con el mismo nombre.

Ejemplo 8.3 Traducción de un bucle while a ensamblador

Código de alto nivel Código ensamblador

while ((x == i) || (y < x)) {


Código interno eval: # Comienza -
} evaluación
mov x, %eax
cmp i, %eax # Comparar si x == -
i
je codigointerno # Si cierto -
ejecutar código
cmp y, %eax
jle finwhile # Si falso ir al -
final
codigointerno:
... # Código interno
...
jmp eval # Evaluar de nuevo
finwhile: # Final de -
traducción

En este caso la condición del bucle es una disyunción con lo que si una de las condiciones es cierta, se puede ejecutar el código
arquitectura IA-32
150 / 198

interno del bucle sin evaluar la segunda. Por este motivo se utiliza el salto condicional je tras la primera comparación. En caso
de éxito se pasa a ejecutar directamente el bloque de código interno al bucle. Si la primera condición es falsa se evalúa la segunda.
El correspondiente salto condicional en este caso tiene una condición inversa a la incluida en el código, pues si ésta es falsa, se
debe transferir el control al final del bucle.

8.3.4. Traducción de un bucle for

El bucle for, aunque con semántica similar al anterior, tiene una estructura más compleja tal y como se muestra en la figura 8.10.

for (Bloque A; expresión booleana; Bloque B) {


Código interno
}

Figura 8.10: Estructura de un bucle for

El bloque A se ejecuta una única vez antes del bucle, a continuación se evalúa la expresión booleana. En caso de ser cierta se pasa
a ejecutar el código interno del bucle, y si no, se termina la ejecución del bucle. El bloque B se ejecuta a continuación del código
interno y justo antes de saltar de nuevo a la evaluación de la expresión booleana. Supóngase que la evaluación de la expresión
booleana es cero en caso de ser falsa y diferente de cero si es cierta y el valor resultante se almacena en %eax. En la figura 8.11
se muestra la estructura genérica en lenguaje ensamblador resultante de traducir un bucle for en este supuesto.

... # Traducción del bloque A


...
eval: ... # Evaluar la expresión booleana
... # Resultado en %eax
cmp $0, %eax
je finfor
... # Traducción del código interno
...
... # Traducción del bloque B
...
jmp eval
finfor:
... # Resto del programa

Figura 8.11: Traducción de un bucle for a ensamblador

Las primeras instrucciones corresponden a la traducción del bloque A seguidas de las que evalúan la condición. Se necesita
la etiqueta eval como destino del salto incondicional al final del bloque B. A continuación se comprueba el resultado de la
comparación, y si es falso se salta al final del bucle. En caso contrario se ejecuta el código interno que finaliza con las instrucciones
del bloque B y un salto incondicional para que se evalúe de nuevo la condición. El ejemplo 8.4 muestra la traducción de un bucle
for con una de estas condiciones. Se asume que las variables i y j son de tipo entero y están almacenadas en memoria con
etiquetas con el mismo nombre.
arquitectura IA-32
151 / 198

Ejemplo 8.4 Traducción de un bucle for a ensamblador

Código de alto nivel Código ensamblador

for (i = 0; i <= --j; i++) {


Código interno movl $0, i # Bloque A
} eval: # Expresión booleana
mov i, %eax
decl j
cmp j, %eax # Comparar si x <= --j
jg finfor # Si falso ir al final
codigointerno:
... # Código interno
...
incl i # Bloque B
jmp eval # Evaluar de nuevo
finfor: # Final de traducción

La traducción del bloque A es una única instrucción que almacena un cero en memoria. La expresión booleana incluye el
decremento de la variable j antes de ser utilizada por la comparación. La traducción del bloque B también requiere una única
instrucción para incrementar el valor de la variable i.

8.4. Ejecución de subrutinas

En la sección anterior se ha mostrado cómo se obtiene una traducción automática de un programa arbitrariamente complejo. El
compilador primero traduce un bloque a su estructura genérica, luego traduce los bloques internos, y una vez terminado, pasa
al bloque siguiente. La estructura global de un programa es una combinación de bloques para los cuales existe una traducción
sistemática. Esta y la codificación de los datos son las dos principales tareas de un compilador para obtener un ejecutable.
El mecanismo que merece un estudio aparte es el de llamada a subrutinas. El desarrollo de programas modulares se basa en
la posibilidad de ejecutar un bloque de código múltiples veces con diferentes valores de un conjunto de variables denominadas
‘parámetros’ que produce un resultado. Este mecanismo, con diferentes matices, es lo que se denomina como procedimientos,
funciones, subprogramas o métodos y están presentes en prácticamente todos los lenguajes de programación de alto nivel.
En el contexto del lenguaje ensamblador se define una subrutina como una porción de código que realiza una operación en base
a un conjunto de valores dados como parámetros de forma independiente al resto del programa y que puede ser invocado desde
cualquier lugar del código, incluso desde dentro de ella misma.
La ejecución de subrutinas tiene las siguientes ventajas:

Evita código redundante. Durante el diseño de un programa suelen existir ciertos cálculos que deben realizarse en diferentes
lugares del código. La alternativa a replicar las instrucciones es encapsularlas en una subrutina e invocar esta cada vez que sea
necesario lo cual se traduce en código más compacto.
Facilita la descomposición de tareas. La descomposición de tareas complejas en secuencias de subtareas más simples facilita
enormemente el desarrollo de programas. Esta técnica se suele aplicar de forma sucesiva en lo que se denomina ‘diseño
descendente’ de programas. Cada subtarea se implementa como una rutina.
Facilita el encapsulado de código. El agrupar una operación y sus datos en una subrutina y comunicarse con el resto de un
programa a través de sus parámetros y resultados, hace que si en algún momento se cambia su implementación interna, el resto
del programa no requiera cambio alguno.

Además de estas ventajas, el encapsulado de código también facilita la reutilización de su funcionalidad en más de un programa
mediante el uso de ‘bibliotecas’. Una biblioteca de funciones es un conjunto de subrutinas que realizan cálculos muy comunes
arquitectura IA-32
152 / 198

en la ejecución de programas y que pueden ser utilizados por éstos. Java es un ejemplo de lenguaje que dispone de bibliotecas de
clases que en su interior ofrecen multitud de métodos.
La desventaja de las subrutinas es que es necesario establecer un protocolo que defina dónde y cómo se realiza esta transferencia
de datos para la que se requieren múltiples instrucciones máquina adicionales.

8.4.1. Las instrucciones de llamada y retorno de una subrutina

En ensamblador la llamada a una subrutina se realiza mediante la instrucción CALL cuyo único operando es la dirección de me-
moria, generalmente una etiqueta, en la que comienza su código. Tras ejecutar esta instrucción el procesador continua ejecutando
la primera instrucción de la subrutina hasta que encuentra la instrucción RET que no tiene operandos y transfiere la ejecución a
la instrucción siguiente al CALL que inició el proceso. La figura 8.12 ilustra esta secuencia.

Figura 8.12: Llamada y retorno de una subrutina

La instrucción CALL tiene una funcionalidad similar a un salto incondicional, su único operando denota la siguiente instrucción
a ejecutar. La instrucción RET no tiene operandos explícitos pero su efecto, el retorno a la siguiente instrucción tras la llamada,
requiere la utilización de operandos implícitos.
Pero la dirección a la que debe retornar el procesador no puede ser un valor fijo para la instrucción RET puesto que depende
del lugar desde donde ha sido invocada la subrutina. Considérese, por ejemplo, una subrutina que se invoca desde dos lugares
diferentes de un programa. La instrucción RET con la que se termina su ejecución es idéntica en ambos casos pero su dirección
de retorno no. Otra característica de las subrutinas es que su invocación se puede hacer de forma anidada, es decir, que desde una
subrutina se invoca a otra y desde ésta a su vez a otra, hasta una profundidad arbitraria. La figura 8.13 muestra un ejemplo de
invocación anidada de subrutinas y se puede comprobar como la subrutina B es invocada desde diferentes lugares del código.
arquitectura IA-32
153 / 198

Figura 8.13: Invocación anidada de subrutinas

La instrucción RET de la subrutina B retorna la ejecución a la subrutina A en su primera ejecución (denotada por la flecha
número 3) y al programa principal en su segunda ejecución (denotada por la flecha número 6). Esto hace suponer, por tanto, que
la dirección de retorno no puede ser decidida cuando se ejecuta esta instrucción sino en un momento anterior.
El instante en el que se sabe dónde ha de retomarse la ejecución tras una subrutina es precisamente en el momento de su
invocación. Cuando el procesador está ejecutando la instrucción CALL obtiene la dirección de retorno como la de la instrucción
siguiente en la secuencia.
Por tanto, el procesador, además de modificar la secuencia, al ejecutar la instrucción CALL debe guardar la dirección de retorno en
un lugar prefijado del cual será obtenido por la instrucción RET. Pero, durante la ejecución de un programa es preciso almacenar
múltiples direcciones de retorno de forma simultanea.
El que las subrutinas se puedan invocar de forma anidada hace que la utilización de los registros de propósito general para
almacenar la dirección de retorno no sea factible. La alternativa es almacenarlas en memoria, pero la instrucción RET debe tener
acceso a su operando implícito siempre en el mismo lugar. Además, esta zona de memoria debe poder almacenar un número
arbitrario de direcciones de retorno, pues la invocación de subrutinas se puede anidar hasta niveles arbitrarios de profundidad.
Por tanto, se necesita un área de memoria que pueda almacenar tantas direcciones de retorno como subrutinas están siendo
invocadas de forma anidada en un momento de la ejecución de un programa. La propiedad que tienen estas direcciones es que se
almacenan por la instrucción CALL en un cierto orden, y son utilizadas por la instrucción RET en el orden inverso.
La estructura especialmente concebida para este propósito es la pila. En ella se almacena la dirección de retorno mientras se
ejecuta el cuerpo de una subrutina. En caso de invocaciones anidadas, las direcciones de retorno pertinentes se guardan en la pila
y están disponibles para la instrucción RET en el orden preciso.
La instrucción CALL, por tanto, realiza dos tareas: pasa a ejecutar la instrucción en la dirección dada como operando y almacena
en la cima de la pila la dirección de la instrucción siguiente (al igual que lo haría una instrucción PUSH) que será la instrucción
de retorno. Por su parte, la instrucción RET obtiene el dato de la cima de la pila (igual que lo haría la instrucción POP) y ejecuta
un salto incondicional al lugar que indica. Ambas instrucciones, por tanto, modifican el contador de programa.
Del funcionamiento de estas instrucciones se concluye que la cima de la pila justo antes de la ejecución de la primera instrucción
de una subrutina contiene la dirección de retorno, y por tanto, antes de ejecutar la instrucción RET debe apuntar exactamente a la
misma posición. Aunque esta condición es esencial para que el retorno de la subrutina se haga al lugar correcto, los procesadores
no realizan comprobación alguna de que así se produce. Por lo tanto, es responsabilidad del programador en ensamblador el
manipular la pila en una subrutina de forma que la cima de la pila al comienzo de la ejecución sea exactamente la misma que
justo antes de ejecutar la última instrucción.
Durante la ejecución de la subrutina se pueden hacer las operaciones necesarias sobre la pila siempre y cuando se conserve la
dirección de retorno. Esta es la explicación de por qué en la sección 6.4 se estipuló la regla de que la pila al comienzo y final de
un programa debe ser la misma. El programa en ensamblador que comienza a ejecutar a partir de la etiqueta main también es
arquitectura IA-32
154 / 198

una subrutina que invoca el sistema operativo, y por lo tanto se debe garantizar que la cima es idéntica al comienzo y al final del
programa pues contiene la dirección de retorno.

8.4.2. Paso de parámetros y devolución de resultados

En general una subrutina consiste en una porción de código que realiza una operación con un conjunto de valores proporcionados
por el programa que la invoca denominados parámetros, y que devuelve un resultado. Los parámetros son copias de ciertos
valores que se ponen a disposición de la subrutina y que tras acabar su ejecución se descartan. El resultado, en cambio, es un
valor que la subrutina calcula y copia en un lugar para que el programa invocador lo utilice. La figura 8.14 ilustra la manipulación
de parámetros y resultado.

Figura 8.14: Parámetros y resultado de una subrutina

Se necesita establecer las reglas que estipulen cómo y dónde deposita el programa que invoca una subrutina estos valores y cómo
y dónde se deposita el resultado. En adelante, a la porción de código que realiza la llamada a la subrutina se le denominará
‘programa llamador’ mientras que al código de la subrutina se le denominará ‘programa llamado’. Las llamadas a subrutinas se
puede hacer de forma ‘anidada’, es decir, un programa llamado invoca a su vez a otra subrutina con lo que pasa a comportarse
como programa llamador.

8.4.2.1. Paso de parámetros a través de registro

El paso de parámetros a través de registro consiste en que el programa llamador y el llamado asumen que los parámetros se alma-
cenan en ciertos registros específicos. Antes de la instrucción de llamada el programa llamador deposita los valores pertinentes
en estos registros y la subrutina comienza a procesarlos directamente.
En general, dada una rutina que recibe n parámetros y devuelve m resultados, se necesita definir en qué registro deposita el
programa llamador la copia de cada uno de los n parámetros, y en qué registro deposita la subrutina la copia del resultado
obtenido. El ejemplo 8.5 muestra las instrucciones necesarias en el caso de una subrutina que recibe como parámetros dos
enteros a través de los registros %eax y %ebx y devuelve el resultado a través del registro %ecx.
arquitectura IA-32
155 / 198

Ejemplo 8.5 Paso de parámetros a través de registros

Programa llamador Programa llamado

mov param1, %eax


mov param2, %ebx subrutina:
call subrutina push %... # Salvar registros -
mov %ecx, resultado utilizados
push %... # excepto %eax, %ebx y -
%ecx
... # Realizar cálculos

mov ..., %ecx # Poner resultado en % -


ecx
pop %... # Restaurar registros
pop %...
ret

Al utilizar los registros %eax y %ebx para pasar los parámetros la subrutina no salva su contenido pues dispone de esos valores
como si fuesen suyos. El registro %ecx, al contener el resultado, tampoco se debe salvar ni restaurar.
El principal inconveniente que tiene este esquema es el número limitado de registros de propósito general. En los lenguajes de
alto nivel no hay límite en el número de parámetros que puede tener una función o método en su definición, y por tanto, si este
número es muy alto, el procesador puede no tener registros suficientes.
A pesar de esta limitación, en el caso de subrutinas con muy pocos parámetros y que devuelve un único resultado, este mecanismo
es muy eficiente pues el procesador no precisa almacenar datos en memoria. Los sistemas operativos suele utilizar esta técnica
para invocaciones de subrutinas internas de estas características.

8.4.2.2. Paso de parámetros a través de memoria

El paso de parámetros a través de memoria consiste en definir una zona de memoria conocida tanto para el programa llamador
como para el llamado y en ella se copia el valor de los parámetros y el del resultado para su intercambio. La ventaja de esta técnica
radica en que permite tener un número arbitrario de parámetros, pues tan sólo se requiere una zona más grande de memoria.
En general, para una subrutina que recibe n parámetros y devuelve m resultados se define una zona de memoria cuyo tamaño es
la suma de los tamaños de todos ellos así como el orden en el que estarán almacenados. El ejemplo 8.6 muestra la instrucciones
necesarias para el caso de una subrutina que precisa tres parámetros de tamaño 32 bits y devuelve dos resultados de tamaño 8
bits. Se asume que la zona de memoria está definida a partir de la etiqueta params.
arquitectura IA-32
156 / 198

Ejemplo 8.6 Paso de parámetros a través de memoria

Programa llamador Programa llamado

mov params, %eax


mov v1, %ebx subrutina:
mov %ebx, ( %eax) push %... # Salvar registros -
mov v2, %ebx utilizados
mov %ebx, 4( %eax) push %...
mov v3, %ebx
mov %ebx, 8( %eax) mov params, %ebx # Acceso a los -
call subrutina parámetros
mov ( %ebx), ...
mov 12( %eax), %ah
mov 4( %ebx), ...
mov 13( %eax), %ah
mov 8( %ebx), ...
... # Realizar cálculos

mov %dh, 12( %ebx) # Poner resultado


mov %dl, 13( %ebx)

pop %... # Restaurar -


registros
pop %...
ret

El principal inconveniente de esta técnica es que necesita tener estas zonas de memoria previamente definidas. Además, en el
caso de invocación anidada de subrutinas, se necesitan múltiples espacios de parámetros y resultados pues mientras la ejecución
de una subrutina no termina, éstos siguen teniendo validez.
El incluir esta definición junto con el código de una subrutina parecería una solución idónea, pues al escribir sus instrucciones se
sabe el número y tamaño de parámetros y resultados. Pero existen subrutinas denominadas ‘recursivas’ que se caracterizan por
contener una invocación a ellas mismas con un conjunto de parámetros diferente.
La conclusión es que se precisan tantas zonas para almacenar parámetros y devolver resultados como invocaciones pendientes de
terminar en cada momento de la ejecución. Pero este requisito de vigencia es idéntico al que tiene la dirección de retorno de una
subrutina. Es más, la dirección de retorno se puede considerar un valor más que el programa llamador pasa al llamado para que
éste lo utilice. En esta observación se basa la siguiente técnica de paso de parámetros.

8.4.2.3. Paso de parámetros a través de la pila

El paso de parámetros a través de la pila tiene múltiples ventajas. En primer lugar, tanto parámetros como resultados se pueden
considerar resultados temporales que tienen validez en un período muy concreto de la ejecución de un programa por lo que la
pila favorece su manipulación. Además, dada una secuencia de llamadas a subrutinas, el orden de creación y destrucción de estos
parámetros es el inverso tal y como permiten las instrucciones de gestión de la pila.
En general, para una subrutina que recibe n parámetro y devuelve m resultados el programa llamador reserva espacio en la cima
de la pila para almacenar estos datos justo antes de ejecutar la instrucción CALL y lo elimina justo a continuación.
Pero en la subrutina es necesario un mecanismo eficiente para acceder a la zona de parámetros y resultados. Al estar ubicada en
la pila lo más intuitivo es utilizar el registro %esp que apunta a la cima y el modo de direccionamiento base + desplazamiento
mediante la utilización de los desplazamientos pertinentes. Pero el inconveniente de este método es que la cima de la pila puede
fluctuar a lo largo de la ejecución de la subrutina y por tanto los desplazamientos a utilizar varían.
Para que el acceso a los parámetros no dependa de la posición de la cima de la pila y se realice con desplazamientos constantes a
lo largo de la ejecución de la subrutina, las primeras instrucciones almacenan una copia del puntero de pila en otro registro (ge-
neralmente %ebp) y al fijar su valor, los accesos a la zona de parámetros y resultados se realizan con desplazamientos constantes.
Pero para preservar el valor de los registros, antes de crear este duplicado es preciso guardar en la pila una copia de este registro.
arquitectura IA-32
157 / 198

El ejemplo 8.7 muestra las instrucciones necesarias para el caso de una subrutina que precisa tres parámetros de tamaño 32 bits
y devuelve un resultado de 8 bits.

Ejemplo 8.7 Paso de parámetros a través de la pila

Programa llamador Programa llamado

sub $4, %esp


push v3 subrutina:
push v2 push %ebp # Guardar registro -
push v1 %ebp
call subrutina mov %esp, %ebp # Apuntar a punto -
add $12, %esp fijo en pila
push %... # Salvar registros -
pop resultado
utilizados
push %...

mov 8( %ebp), ... # Acceso a los -


parámetros
mov 12( %ebp), ...
mov 16( %ebp), ...
... # Realizar cálculos

mov ..., 20( %ebx) # Poner resultado

pop %... # Restaurar -


registros
pop %...
mov %ebp, %esp # Restaurar %esp y -
%ebp
pop %ebp
ret

La primera instrucción del programa llamador modifica el puntero de la pila para reservar espacio donde almacenar el resultado.
Al ser una posición de memoria sobre la que se escribirá el resultado no es preciso escribir ningún valor inicial, de ahí que no se
utilice la instrucción push.
A continuación se depositan en la pila los valores de los parámetros. El orden en que se almacenan debe ser conocido por el
programa llamador y el llamado. Tras la ejecución de la subrutina se eliminan de la pila los parámetros, que al desempeñar
ningún papel, basta con corregir el valor de la cima dejando la pila preparada para obtener el valor del resultado.
Por su parte, el programa llamado guarda la copia del registro %ebp para justo a continuación copiar el valor de %esp y por
tanto fija su valor a la cima de la pila. A partir de este instante, cualquier dato que se ponga en la pila no afecta el valor de %-
ebp y el desplazamiento para acceder a los parámetros es respectivamente de 8, 12 y 16 pues en la posición debajo de la cima
se encuentra la dirección de retorno. Para depositar el resultado se utiliza el desplazamiento 20. Tras terminar el cálculo del
resultado se procede a deshacer la estructura de datos creada en la pila en orden inverso. Primero se descargan de la pila los
registros salvados y a continuación se restaura el valor del registro %ebp dejando en la cima la dirección de retorno que necesita
la instrucción ret.
A la porción de memoria en la pila que contiene el espacio para la devolución de resultados, los parámetros, la dirección de
retorno, la copia de %ebp se le denomina el ‘bloque de activación’. Al registro %ebp que ofrece un punto fijo de referencia a los
datos se le denomina el ‘puntero’ al bloque de activación.

8.4.2.4. Almacenamiento de variables locales a una subrutina

Además de la capacidad de definir y ejecutar subrutinas, los lenguajes de programación de alto nivel permiten la definición de
variables locales. El ámbito de validez se reduce al instante en que se está ejecutando el código de la subrutina. De nuevo se
arquitectura IA-32
158 / 198

precisa un mecanismo que gestione de forma eficiente estas variables. El ejemplo 8.8 muestra la definición de un método en Java
en el que las variables i, str y p son de ámbito local.

Ejemplo 8.8 Definición de variables locales a un método


int traducir(Representante r, int radix) {
int i; // Variables locales
String str;
Punto p;

i = 0;
str = new String(...);
punto = ...
...
return i;
}

El ámbito de estas variables no impide que el valor de alguna de ellas sea devuelto como resultado tal y como muestra el método
del ejemplo. La última línea copia el valor de la variable local en el lugar en el que se devuelve el resultado, y por tanto está
disponible para el programa llamador.
El ámbito de estas variables es idéntico al de los parámetros y al de la dirección de retorno, por lo que para almacenar estas
variables se pueden utilizar cualquiera de las tres técnicas descritas anteriormente: en registros, en posiciones arbitrarias de
memoria y en la pila.
El almacenamiento en la pila se hace en el bloque de activación justo a continuación de haber establecido el registro %ebp
como puntero al bloque de activación. De esta forma, como el número de variables locales es siempre el mismo, utilizando
desplazamientos con valores negativos y el registro base %ebp se accede a ellas desde cualquier punto de la subrutina.

8.5. Gestión del bloque de activación

De las técnicas descritas para la invocación de subrutinas, la que crea el bloque de activación en la pila es la más utilizada por los
lenguajes de alto nivel. En la Tabla 8.1 se muestran los pasos a seguir por el programa llamador y el llamado para crear y destruir
el bloque de activación.
Tras restaurar el valor de los registros utilizados por la subrutina, el estado de la pila es tal que en la cima se encuentra el
espacio para las variables locales y a continuación la copia del valor anterior de %ebp. Como el propio %ebp apunta a esa misma
posición, la forma más fácil de restaurar la cima de la pila al valor correcto es asignándole a %esp el valor de %ebp. De esta
forma no es preciso tener en cuenta el tamaño de la zona reservada para las variables locales. Esta técnica funciona incluso en el
caso de que una subrutina no tenga variables locales.

8.6. Ejemplo de evolución del bloque de activación

Considérese un programa que invoca a la subrutina cuenta que dada la dirección de un string terminado en cero y un carácter,
devuelve el número de veces que el carácter aparece en el string como entero. La pila que recibe la subrutina tiene, en orden
creciente de desplazamiento desde el puntero al bloque de activación, la dirección de retorno (siempre está presente como primer
valor), la dirección del string, el carácter a comprobar como byte menos significativo del operando en la pila y el espacio para el
resultado. El fragmento de código para invocar a esta subrutina se muestra en el ejemplo 8.9. Se asume que la letra a buscar está
almacenada en la etiqueta letra y el string en la etiqueta mensaje.
arquitectura IA-32
159 / 198

Programa llamador Programa llamado

1. Reserva espacio en la pila para almacenar los 1. Salva el valor de %ebp para utilizar como puntero al
resultados. bloque de activación.
2. Carga los parámetros en orden en la pila. 2. Copia el valor de %esp en %ebp.
3. Ejecuta la instrucción de llamada a subrutina. 3. Reserva espacio en la pila para variables locales.

4. Descarga los parámetros de la pila. 4. Salva los registros que se utilizan en la subrutina.
5. Obtiene el resultado de la pila. 5. Ejecuta el código de la subrutina.
6. Deposita el resultado en el espacio reservado a tal
efecto.

7. Restaura el valor de los registros salvados.


8. Iguala la cima de la pila al puntero al bloque de
activación.
9. Restaura el valor del registro %ebp.

10. Ejecuta la instrucción de retorno de subrutina.

Tabla 8.1: Pasos para la gestión del bloque de activación

Ejemplo 8.9 Invocación de la rutina cuenta


...
sub $4, %esp # Espacio para el resultado
push letra # Parámetros en el orden correcto
push mensaje
call cuenta # Invocación de la subrutina
add $8, %esp # Descarga del espacio para parámetros
pop %eax # Resultado en %eax
...

La instrucción push letra tiene por operando una etiqueta que apunta a un dato de tamaño byte. Como los operandos de
la pila son de 4 bytes, en ella se depositan la letra y los tres siguientes bytes. Esto no tiene importancia porque la subrutina
únicamente accede al byte de menos peso tal y como se ha especificado en su definición. La figura 8.15 muestra la evolución de
la pila desde el punto de vista del programa llamador.
arquitectura IA-32
160 / 198

Figura 8.15: Evolución de la pila desde el punto de vista del programa llamador

El código de la subrutina cuenta se muestra en el ejemplo 8.10.


arquitectura IA-32
161 / 198

Ejemplo 8.10 Código de la rutina cuenta


cuenta: push %ebp # Salvar %ebp
mov %esp, %ebp # Crear puntero a bloque de activación

sub $4, %esp # Espacio para variable local: contador

push %eax # Salvar registros utilizados


push %ebx
push %ecx

movl $0, -4( %ebp) # Contador = 0


mov 8( %ebp), %eax # Dirección base del string
mov 12( %ebp), %cl # Letra a comparar en %cl
mov $0, %ebx # Registro índice

bucle: cmpb $0, ( %eax, %ebx) # Detectar final de string


je res

cmpb %cl, ( %eax, %ebx) # Compara letra dada con letra en string
jne incr # Si iguales incrementar contador
incl -4( %ebp)

incr: inc %ebx # Incrementar registro índice


jmp bucle

res: mov -4( %ebp), %eax # Mover contador a resultado


mov %eax, 16( %ebp)

pop %ecx # Restaurar registros


pop %ebx
pop %eax

mov %ebp, %esp # Eliminar variables locales


pop %ebp # Restaurar %ebp
ret

La subrutina almacena el número de veces que aparece la letra en el string dado como una variable local en la pila. Tras finalizar
el bucle, su valor se transfiere al lugar en el que lo espera el programa llamador. La figura 8.16 muestra la evolución de la pila
durante la ejecución de la subrutina.
arquitectura IA-32
162 / 198

Figura 8.16: Evolución de la pila durante la llamada a cuenta

8.7. Ejercicios

1. Partiendo del código del ejemplo 8.1, traducir la nueva versión que se muestra en la siguiente figura con la modificación
en la última parte de la condición:

Código de alto nivel

if ((x <= 3) && (i == j++)) {


Bloque A
} else {
Bloque B
}

¿En qué lugar se debe insertar la instrucción que incrementa la variable j para mantener el significado de esta construc-
ción?
Si la primera sub-expresión de la conjunción es falsa, ¿se incrementa la variable j?

2. Traducir el siguiente bucle en lenguaje de alto nivel a la porción de código equivalente en lenguaje ensamblador de la
arquitectura IA-32. Se asume que todas las variables son de tipo entero.
arquitectura IA-32
163 / 198

for (i = a + b; ((c + d) >= 10) && (i <= (d + e)); i = i + f - g - h ) {


g--;
f++;
a = b + 10;
}

Las variables han sido definidas de la siguiente forma:


a: .int 10
b: .int 20
c: .int 30
d: .int 40
e: .int 50
f: .int 60
g: .int 70
h: .int 80
i: .int 10

La solución puede utilizar cualquier modo de direccionamiento excepto el modo absoluto. Al tratarse de una porción de
código aislada, no es preciso salvar ni restaurar el contenido de los registros. La solución debe ser lo más corta posible y
utilizar el menor número de registros de propósito general.
3. La mayor parte de los lenguajes de alto nivel que ofrecen bucles de tipo while también ofrecen otro bucle de estructura
similar denominado do/while. La diferencia es que el bloque comienza por la palabra clave do seguida de un bloque de
código entre llaves y termina con la palabra while seguida de una condición booleana entre paréntesis.
El bloque de código se ejecuta al menos una vez y tras él se evalúa la condición para determinar si se continúa iterando.
Escribir la estructura en codigo ensamblador resultante de traducir esta construcción. Se puede asumir que el resultado de
evaluar la condición está almacenado en %eax.
4. Escribir las reglas para el paso de parámetros y devolución de resultado a través de la pila:

Por parte del programa que llama a la subrutina:


Por parte de la subrutina:

5. Un programador ha escrito la siguiente rutina:


rutina:
push %ebp
mov %esp, %ebp
add $13, 4( %ebp)
pop %ebp
ret

El programa principal que invoca a esta rutina contiene las siguientes instrucciones tal y como las muestra el compilador
en su informe sobre el restulado del ensamblaje:
1 .data
2 0000 50726F67 msg: .asciz "Programa terminado.\n"
2 72616D61
2 20746572
2 6D696E61
2 646F2E0A
3
4 .text
5 .globl main
6 main:
7 0000 50 push %eax
8 0001 51 push %ecx
9 0002 52 push %edx
10
arquitectura IA-32
164 / 198

11 0003 E8FCFFFF call rutina # Llamada a rutina dada


11 FF
12
13 0008 68000000 push $msg
13 00
14 000d E8FCFFFF call printf
14 FF
15 0012 83C404 add $4, %esp
16
17 0015 5A pop %edx
18 0016 59 pop %ecx
19 0017 58 pop %eax
20 0018 C3 ret

Explicar detalladamente cuál es el resultado de la ejecución de este programa. ¿Ejecuta correctamente? ¿Qué escribe por
pantalla? ¿Por qué?
6. El paso de resultados y devolución de resultados se puede hacer a través de registros, memoria o la pila, pero nada impide
utilizar una técnica diferente para parámetros y resultados. Describir las reglas de creación del bloque de activación para
los siguientes supuestos.

a. Parámetros en la pila y resultados a través de registros.


b. Parámetros en registro y resultados a través de la pila.
arquitectura IA-32
165 / 198

Apéndice A

Subconjunto de instrucciones de la arquitectura


IA-32

La arquitectura IA-32 dispone de un lenguaje máquina con cientos de instrucciones para múltiples tipos de tareas diferentes. En
este apéndice se describen en detalla tan sólo un subconjunto mínimo que permite realizar operaciones sencillas sobre tipos de
datos tales como strings y enteros.

A.1. Nomenclatura

Para la descripción detallada del subconjunto de instrucciones de esta arquitectura se utiliza la siguiente nomenclatura:

%reg: Denota cualquiera de los ocho registros de propósito general.


inm: Denota una constante numérica. En ensamblador, el valor numérico debe ir precedido por el símbolo $. También se
incluyen en esta categoría las expresiones $etiq, donde etiq corresponde con el nombre de una de las etiquetas definidas
en el código.
mem: Denota el nombre de una etiqueta definida en el código. Nótese que en este caso no se utiliza el prefijo $ pues si fuese
así se trataría de una constante.
INSs: Cuando el código de operación de una instrucción termina en s esto denota que la instrucción requiere un sufijo de
tamaño B, W o L.

A.2. Instrucciones de movimiento de datos

A.2.1. MOV: Movimiento de datos

Instrucción Descripción

MOV %regA, %regB Mueve el contenido de %regA al registro %regB.

MOV $inm, %reg Mueve inm al registro %reg.

Mueve el contenido almacenado en la posición mem al


MOV mem, %reg registro %reg.
arquitectura IA-32
166 / 198

Instrucción Descripción

MOV %reg, mem Mueve el contenido de %reg a la posición mem.

Mueve inm, codificado con los bits especificados por el


MOVs $inm, mem sufijo s al dato cuyo tamaño está especificado por el sufijo
s y que está almacenado a partir de la posición mem.

La instrucción de mover recibe dos operandos y mueve el primero al lugar donde indica el segundo. Esta instrucción no modifica
ninguno de los flags de la palabra de estado.

A.2.2. PUSH: Instrucción de carga sobre la pila

Instrucción Descripción

Almacena el contenido de %reg en la posición anterior a la


PUSH %reg que apunta el puntero de pila.

Almacena el dato de 32 bits que está almacenado a partir


PUSH mem de la posición mem en la posición anterior a la que apunta
el puntero de pila.

Almacena inm codificado con 32 bits en la posición


PUSH $inm anterior a la que apunta el puntero de pila.

Esta instrucción recibe un único operando y manipula siempre operandos de 32 bits, por lo tanto no es preciso utilizar ningún
sufijo de tamaño. El procesador toma el valor del registro puntero de pila, le resta 4 y almacena el operando dado en los cuatro
bytes de memoria a partir de la posición del puntero de pila. Esta instrucción no modifica ninguno de los flags de la palabra de
estado.

A.2.3. POP: Instrucción de descarga de la pila

Instrucción Descripción
Almacena el contenido al que apunta el puntero de pila
POP %reg en %reg. Modifica el puntero a la cima para que apunte a
la siguiente posición de la pila.
Almacena los 32 bits a los que apunta el puntero de pila a
POP mem partir de la posición mem. Modifica el puntero a la cima
para que apunte a la siguiente posición de la pila.

Esta instrucción recibe un único operando y manipula siempre operandos de 32 bits, por lo tanto no es preciso utilizar ningún
sufijo de tamaño. El procesador toma el valor del registro puntero de pila y mueve ese dato al lugar que le indique el operando
de la instrucción. Tras esta transferencia, se suma el valor 4 al registro puntero de pila. Esta instrucción no modifica ninguno de
los flags de la palabra de estado.

A.2.4. XCHG: Instrucción de intercabmio


arquitectura IA-32
167 / 198

Instrucción Descripción

XCHG %regA, %regB Intercambia los valores de sus dos operandos. Cuando un
operando está en memoria, se intercambia el dato
XCHG %reg, mem almacenado a partir de la posición de memoria dada. Al
menos uno de los operandos debe ser de tipo registro.
XCHG mem, %reg

Esta instrucción utiliza un registro temporal interno del procesador para intercambiar los operandos. No se modifica ninguno de
los flags de la palabra de estado.

A.3. Instrucciones aritméticas

A.3.1. ADD: Instrucción de suma

Instrucción Descripción

ADD %regA, %regB Suma el contenido de %regA al contenido de %regB.

ADD $inm, %reg Suma inm al contenido de %reg.

Suma el contenido almacenado en la posición mem al


ADD mem, %reg contenido de %reg.

Suma el contenido de %reg al contenido almacenado en la


ADD %reg, mem posición mem.

Suma inm, codificado con los bits especificados por el


ADDs $inm, mem sufijo s al dato cuyo tamaño está especificado por el sufijo
s y que está almacenado a partir de la posición mem.

La instrucción de suma recibe dos operandos, los suma y deposita el resultado en el lugar especificado por el segundo operando.
Se pierde, por tanto, el valor del segundo operando. Los flags de la palabra de estado OF, SF, ZF, PF y CF se modifican de
acuerdo al resultado obtenido.

A.3.2. SUB: Instrucción de resta

Instrucción Descripción

SUB %regA, %regB Resta el contenido de %regA del contenido de %regB.

SUB $inm, %reg Resta inm del contenido de %reg.


arquitectura IA-32
168 / 198

Instrucción Descripción

Resta el contenido almacenado en la posición mem del


SUB mem, %reg contenido de %reg.

Resta el contenido de %reg del contenido almacenado en


SUB %reg, mem la posición mem.

Resta inm, codificado con los bits especificados por el


SUBs $inm, mem sufijo s del dato cuyo tamaño está especificado por el
sufijo s y que está almacenado a partir de la posición mem.

La instrucción de resta recibe dos operandos op1 y op2, realiza la operación op2 - op1 y almacena el resultado en el lugar
especificado por el segundo operando. Se pierde, por tanto, el valor del segundo operando. Los flags de la palabra de estado OF,
SF, ZF, PF y CF se modifican de acuerdo al resultado obtenido.

A.3.3. INC: Instrucción de incremento

Instrucción Descripción

INC %reg Suma uno al contenido de %reg.

Suma uno, codificado con los bits especificados por el


INCs mem sufijo s al dato cuyo tamaño está especificado por el sufijo
s y que está almacenado a partir de la posición mem.

La instrucción de incremento recibe un único operando al que le suma el valor 1. Esta instrucción tiene la particularidad de que
no modifica el flag de acarreo (CF). Los flags de la palabra de estado OF, SF, PF y ZF sí se modifican de acuerdo al resultado
obtenido.

A.3.4. DEC: Instrucción de decremento

Instrucción Descripción

DEC %reg Resta uno del contenido de %reg.

Resta uno, codificado con los bits especificados por el


DECs mem sufijo s del dato cuyo tamaño está especificado por el
sufijo s y que está almacenado a partir de la posición mem.

La instrucción de decremento recibe un único operando al que le resta el valor 1. Esta instrucción tiene la particularidad de que
no modifica el flag de acarreo (CF). Los flags de la palabra de estado OF, SF, PF y ZF sí se modifican de acuerdo al resultado
obtenido.

A.3.5. NEG: Instrucción de cambio de signo


arquitectura IA-32
169 / 198

Instrucción Descripción

Toma el contenido de %reg, cambia su signo y se


NEG %reg almacena en %reg.

Toma el número codificado con los bits especificados por el


NEGs mem sufijo s y almacenado a partir de la posición mem y le
cambia el signo.

La instrucción de decremento recibe un único operando y la operación que realiza es equivalente a multiplicar por -1. El operando
se asume que está codificado en complemento a 2. Esta instrucción tiene la particularidad de que asigna directamente el valor 1
al flag de acarreo (CF) excepto si el operando tiene el valor 0. Los flags de la palabra de estado OF, SF, PF y ZF sí se modifican
de acuerdo al resultado obtenido.

A.3.6. MUL: Instrucción de multiplicación sin signo

Instrucción Descripción
Multiplica el contenido de %reg por el registro %al, %ax
o %eax dependiendo de si el tamaño del operando es de 8,
16 o 32 bits respectivamente. El resultado se almacena
en %ax, el registro de 32 bits resultante de
MUL %reg concatenar %dx: %ax ( %dx el más significativo) o el
registro de 64 bits resultante de concatenar %edx: %eax
( %edx el más significativo) dependiendo de si el operando
dado es de 8, 16 o 32 bits respectivamente.
Multiplica el dato codificado con los bits especificado por
el sufijo s y almacenado a partir de la posición mem por el
registro %al, %ax o %eax dependiendo de si el tamaño del
operando es de 8, 16 o 32 bits respectivamente. El resultado
MULs mem se almacena en %ax, el registro de 32 bits resultante de
concatenar %dx: %ax ( %dx el más significativo) o el
registro de 64 bits resultante de concatenar %edx: %eax
( %edx el más significativo) dependiendo de si el sufijo
especifica 8, 16 o 32 bits respectivamente.

Esta instrucción utiliza dos operandos, pero uno de ellos es implícito, es decir, no aparece en la instrucción. Este multiplicando
se obtiene de %al, %ax o %eax dependiendo de si la operación debe utilizar datos de 8, 16 o 32 bits.
Se permite, por tanto, la utilización de 3 posibles tamaños de operandos: 8, 16 y 32 bits. Ambos operandos tienen idéntico
tamaño. El problema de la multiplicación es que si en general se multiplican dos números de tamaño n y m bits, se precisan
n + m bits para representar el restulado. Como consecuencia de esto, el resultado de esta instrucción se almacena en %ax si la
multiplicación es de dos números de 8 bits, en el registro de 32 bits resultante de concatenar %dx: %ax, con %dx como parte
más significativa, si se multiplican dos números de 16 bits, y en caso de multiplicación de dos números de 32 bits, se almacena
en la concatenación %edx: %ecx como un registro de 64 bits con %edx como parte más significativa.
La razón por la que no se utiliza uno de los registros de 32 bits para almacenar el producto de dos operandos de 16 bits es
histórica. Han existido procesadores anteriores a este en el que sólo había registros de 16 bits. Para almacenar un operando de
32 bits había que realizar esta concatenación. Los nuevos procesadores disponen de registros de 32 bits, pero por mantener el
lenguaje máquina compatible con las versiones anteriores, esta instrucción conserva la descripción anterior. La Tabla A.1 muestra
la relación entre el tamaño de los operandos y el lugar en el que se almacena el resultado.
Los únicos flags que se modifican con esta instrucción son CF y OF. Ambos bits se ponen a 1 cuando la mitad más significativa
del resultado tiene alguno de sus bits a 1. El resto de flags tienen valores no definidos tras ejecutar esta instrucción.
arquitectura IA-32
170 / 198

Tamaño de Operando Operando Implícito Operando Explícito Resultado


Byte %al %reg ó mem %ax
Word (2 bytes) %ax %reg ó mem %dx: %ax
Doubleword (4 bytes) %eax %reg ó mem %edx: %eax

Tabla A.1: Opciones de la multiplicación sin signo

A.3.7. DIV: Instrucción de división sin signo

Instrucción Descripción
Esta instrucción tiene tres versiones posibles. Divide el
registro %ax, los 32 bits obtenidos al concatenar los
registros %dx: %ax o los 64 bits obtenidos al concatenar
DIV %reg los registros %edx: %eax entre %reg. Como resultado se
deposita el cociente en %al, %ax o %eax y el resto
en %ah, %dx o %edx respectivamente. Se consideran todos
los operandos como números naturales.
Esta instrucción tiene tres versiones posibles. Divide el
registro %ax, los 32 bits obtenidos al concatenar los
registros %dx: %ax o los 64 bits obtenidos al concatenar
DIVs mem los registros %edx: %eax entre el dato codificado según el
sufijo s y almacenado a partir de la posición de memoria
mem. Como resultado se deposita el cociente en %al, %ax
o %eax y el resto en %ah, %dx o %edx respectivamente.

Esta instrucción utiliza dos operandos: dividendo y divisor. Tan sólo se especifica en la instrucción el divisor. El dividendo es
implícito y tiene tamaño doble al del divisor y se obtiene de %ax (16 bits), la concatenación de los registros %dx: %ax (32 bits)
o la concatenación de los registros %edx: %eax (64 bits) dependiendo de si el divisor es de 8, 16 o 32 bits respectivamente.
La instrucción devuelve dos resultados: cociente y resto. El cociente se devuelve en %al, %ax o %eax y el resto en %ah, %dx
o %edx. La Tabla A.2 muestra la relación entre el tamaño de los operandos y el lugar en el que se almacena el resultado.

Tamaño de Valor Máximo


Dividendo Divisor Cociente Resto
Operandos Cociente
1 byte %reg ó
Word/Byte %ax %al %ah 255
mem
2 bytes %reg ó
Doubleword/Word %dx: %ax %ax %dx 65.535
mem
4 bytes %reg ó
Quadword/Doubleword%edx: %eax %eax %edx 232 - 1
mem

Tabla A.2: Opciones de la división sin signo

Ninguno de los flags de la palabra de estado tiene valor definido tras ejecutar esta instrucción.

A.3.8. IMUL: Instrucción de multiplicación con signo

La instrucción para multiplicar dos enteros con signo tiene tres posibles formatos dependiendo del número de operandos explíci-
tos.
El formato con un único operando se interpreta de forma idéntica a la instrucción de multiplicación sin signo MUL (ver sección
A.3.6). El segundo operando es implícito y tiene idéntico tamaño al explícito. El resultado tiene tamaño doble que los operandos.

Instrucción Descripción
Multiplica el contenido de %reg por el registro %al, %ax
o %eax dependiendo de si el tamaño del operando es de 8,
16 o 32 bits respectivamente. El resultado se almacena
en %ax, el registro de 32 bits resultante de
IMUL %reg concatenar %dx: %ax ( %dx el más significativo) o el
arquitectura IA-32
171 / 198

Instrucción Descripción
Multiplica el dato codificado con los bits especificado por
el sufijo s y almacenado a partir de la posición mem por el
registro %al, %ax o %eax dependiendo de si el tamaño del
operando es de 8, 16 o 32 bits respectivamente. El resultado
IMULs mem se almacena en %ax, el registro de 32 bits resultante de
concatenar %dx: %ax ( %dx el más significativo) o el
registro de 64 bits resultante de concatenar %edx: %eax
( %edx el más significativo) dependiendo de si el sufijo
especifica 8, 16 o 32 bits respectivamente.

La segunda versión de esta instrucción contiene dos operandos. El segundo de ellos es a la vez multiplicando y destino del
resultado y debe ser uno de los registro de propósito general. En esta versión, tanto operandos como resultado tienen idéntico
tamaño, con lo que hay una mayor probabilidad de overflow.

Instrucción Descripción
Multiplica el contenido de %regA por el contenido
MUL %regA, %regB de %regB. Los registros deben tener idéntico tamaño y
sólo pueden ser de 16 o 32 bits, no de 8.

Multiplica inm por el contenido de %reg. El registro sólo


MUL $inm, %reg puede ser de 16 o 32 bits.

Multiplica el contenido almacenado a partir de la posición


MUL mem, %reg mem por el contenido de %reg. El registro sólo puede ser
de 16 o 32 bits.

La tercera versión de la instrucción de multiplicación consta de tres operandos. El primero es un multiplicando y debe ser una
constante, el segundo es también un multiplicando y debe ser una posición de memoria o un registro. El tercer operando es donde
se guarda el resultado y debe ser un registro de propósito general.

Instrucción Descripción
Multiplica $inm por el contenido de %regA y almacena el
MUL $inm, %regA, %regB restulado en %regB. Los registros deben tener idéntico
tamaño y sólo pueden ser de 16 o 32 bits, no de 8.
Multiplica $inm por el dato almacenado a partir de la
MUL $inm, mem, %reg posición mem y almacena el resultado en %reg. El registro
sólo puede ser de 16 o 32 bits.

De las tres versiones posibles para esta operación, sólo la primera deposita el resultado del tamaño apropiado para evitar desbor-
damientos. El formato con dos y tres operandos realiza la multiplicación, obtiene todos los bits del resultado y posteriormente
los trunca para almacenar en destino. Los únicos flags que se modifican con esta instrucción son CF y OF. Ambos bits se ponen
a 1 cuando la mitad más significativa del resultado tiene alguno de sus bits a 1. Nótese que estos dos flags son los que indican, en
el caso de la instrucción con dos y tres operandos, si el resultado obtenido ha sido truncado para almacenarse en destino. El resto
de flags tienen valores no definidos tras ejecutar esta instrucción.

A.3.9. IDIV: Instrucción de división con signo

El comportamiento de esta instrucción es idéntico al de la instrucción DIV (ver sección A.3.7) con la diferencia de que los
operandos son números enteros.
arquitectura IA-32
172 / 198

Instrucción Descripción
Esta instrucción tiene tres versiones posibles. Divide el
registro %ax, los 32 bits obtenidos al concatenar los
registros %dx: %ax o los 64 bits obtenidos al concatenar
IDIV %reg los registros %edx: %eax entre %reg. Como resultado se
deposita el cociente en %al, %ax o %eax y el resto
en %ah, %dx o %edx respectivamente. Los operandos se
tratan como números enteros.
Esta instrucción tiene tres versiones posibles. Divide el
registro %ax, los 32 bits obtenidos al concatenar los
registros %dx: %ax o los 64 bits obtenidos al concatenar
los registros %edx: %eax entre el dato codificado según el
IDIVs mem sufijo s y almacenado a partir de la posición de memoria
mem. Como resultado se deposita el cociente en %al, %ax
o %eax y el resto en %ah, %dx o %edx respectivamente.
Los operandos se tratan como números enteros.

Al igual que la instrucción DIV, esta instrucción utiliza dos operandos: dividendo y divisor. Tan sólo se especifica en la instrucción
el divisor. El dividendo es implícito y tiene tamaño doble al del divisor y se obtiene de %ax (16 bits), la concatenación de los
registros %dx: %ax (32 bits) o la concatenación de los registros %edx: %eax (64 bits) dependiendo de si el divisor es de 8, 16
o 32 bits respectivamente.
La instrucción devuelve dos resultados: cociente y resto. El cociente se devuelve en %al, %ax o %eax y el resto en %ah, %dx
o %edx. La Tabla A.2 muestra la relación entre el tamaño de los operandos y el lugar en el que se almacena el resultado.

Tamaño de Rango del


Dividendo Divisor Cociente Resto
Operandos Cociente
1 byte %reg ó
Word/Byte %ax %al %ah -128 a 127
mem
2 bytes %reg ó
Doubleword/Word %dx: %ax %ax %dx -32.768 a 32.767
mem
4 bytes %reg ó
Quadword/Doubleword%edx: %eax %eax %edx -231 - 1 a 231
mem

Tabla A.3: Opciones de la división con signo

Ninguno de los flags en la palabra de estado tiene valores definidos tras ejecutar esta instrucción.

A.4. Instrucciones lógicas

A.4.1. AND: Instrucción de conjunción

Instrucción Descripción

Realiza la conjunción bit a bit del contenido de %regA con


AND %regA, %regB el contenido de %regB. Deposita el resultado en %regB.

Realiza la conjunción bit a bit entre inm y el contenido


AND $inm, %reg de %reg. Deposita el resultado en %reg.

Realiza la conjunción bit a bit entre el contenido


AND mem, %reg almacenado en la posición mem y el contenido de %reg.
Deposita el resultado en %reg.
arquitectura IA-32
173 / 198

Instrucción Descripción
Realiza la conjunción bit a bit entre el contenido de %reg y
el contenido almacenado a partir de la posición mem. El
AND %reg, mem resultado se almacena en memoria a partir de la posición
mem.
Realiza la conjunción bit a bit entre inm, codificado con
los bits especificados por el sufijo s con el dato cuyo
ANDs $inm, mem tamaño está especificado por el sufijo s y que está
almacenado a partir de la posición mem. El resultado se
almacena en memoria a partir de la posición mem.

La conjunción bit a bit significa que ambos operandos deben tener el mismo tamaño y que cada bit del resultado se calcula
haciendo la conjunción de los correspondientes bits de ambos operandos. Los flags OF y CF se ponen a cero. Los flags SF, ZF y
PF se modifican de acuerdo con el resultado.

A.4.2. OR: Instrucción de disyunción

Instrucción Descripción

Realiza la disyunción bit a bit del contenido de %regA con


OR %regA, %regB el contenido de %regB. Deposita el resultado en %regB.

Realiza la disyunción bit a bit entre inm y el contenido


OR $inm, %reg de %reg. Deposita el resultado en %reg.

Realiza la disyunción bit a bit entre el contenido


OR mem, %reg almacenado en la posición mem y el contenido de %reg.
Deposita el resultado en %reg.
Realiza la disyunción bit a bit entre el contenido de %reg y
el contenido almacenado a partir de la posición mem. El
OR %reg, mem resultado se almacena en memoria a partir de la posición
mem.
Realiza la disyunción bit a bit entre inm, codificado con
los bits especificados por el sufijo s con el dato cuyo
ORs $inm, mem tamaño está especificado por el sufijo s y que está
almacenado a partir de la posición mem. El resultado se
almacena en memoria a partir de la posición mem.

La disyunción bit a bit significa que ambos operandos deben tener el mismo tamaño y que cada bit del resultado se calcula
haciendo la disyunción de los correspondientes bits de ambos operandos. Los flags OF y CF se ponen a cero. Los flags SF, ZF y
PF se modifican de acuerdo con el resultado.

A.4.3. XOR: Instrucción de disyunción exclusiva

Instrucción Descripción
Realiza la disyunción exclusiva bit a bit del contenido
XOR %regA, %regB de %regA con el contenido de %regB. Deposita el
resultado en %regB.

Realiza la disyunción exclusiva bit a bit entre inm y el


XOR $inm, %reg contenido de %reg. Deposita el resultado en %reg.
arquitectura IA-32
174 / 198

Instrucción Descripción
Realiza la disyunción exclusiva bit a bit entre el contenido
XOR mem, %reg almacenado en la posición mem y el contenido de %reg.
Deposita el resultado en %reg.
Realiza la disyunción exclusiva bit a bit entre el contenido
de %reg y el contenido almacenado a partir de la posición
XOR %reg, mem mem. El resultado se almacena en memoria a partir de la
posición mem.
Realiza la disyunción exclusiva bit a bit entre inm,
codificado con los bits especificados por el sufijo s con el
XORs $inm, mem dato cuyo tamaño está especificado por el sufijo s y que
está almacenado a partir de la posición mem. El resultado
se almacena en memoria a partir de la posición mem.

La disyunción exclusiva bit a bit significa que ambos operandos deben tener el mismo tamaño y que cada bit del resultado se
calcula haciendo la disyunción de los correspondientes bits de ambos operandos. Los flags OF y CF se ponen a cero. Los flags
SF, ZF y PF se modifican de acuerdo con el resultado.

A.4.4. NOT: Instrucción de negación

Instrucción Descripción

NOT %reg Niega bit a bit el contenido de %reg.

Niega bit a bit al dato cuyo tamaño está especificado por el


NOTs mem sufijo s y que está almacenado a partir de la posición mem.

Esta instrucción no modifica ninguno de los flags de la palabra de estado.

A.5. Instrucciones de desplazamiento

A.5.1. SAL/SAR: Desplazamiento aritmético

Instrucción Descripción
Desplaza el contenido de %reg a la izquierda tantas
posiciones como indica inm. Para cada desplazamiento, el
SAL $inm, %reg bit más significativo se carga en el flag CF y el menos
significativo se pone a cero.
Desplaza el contenido de %reg a la derecha tantas
posiciones como indica inm. Para cada desplazamiento el
SAR $inm, %reg bit menos significativo se carga en el flag CF y el nuevo bit
más significativo se pone al mismo valor del anterior
(extensión de signo).
Desplaza el contenido de %reg a la izquierda tantas
posiciones como indica el registro %cl. Para cada
SAL %cl, %reg desplazamiento, el bit más significativo se carga en el flag
CF y el menos significativo se pone a cero.
arquitectura IA-32
175 / 198

Instrucción Descripción
Desplaza el contenido de %reg a la derecha tantas
posiciones como indica %cl. Para cada desplazamiento el
SAR %cl, %reg bit menos significativo se carga en el flag CF y el nuevo bit
más significativo se pone al mismo valor del anterior
(extensión de signo).
Desplaza el contenido del dato cuyo tamaño lo especifica el
sufijo s y que está almacenado a partir de la posición de
memoria mem a la izquierda tantas posiciones como indica
SALs $inm, mem inm. Para cada desplazamiento, el bit más significativo del
segundo operando se carga en el flag CF y el menos
significativo del resultado se pone a cero.
Desplaza el contenido del dato cuyo tamaño lo especifica el
sufijo s y que está almacenado a partir de la posición de
memoria mem a la derecha tantas posiciones como indica
SARs $inm, mem inm. Para cada desplazamiento el bit menos significativo
del segundo operando se carga en el flag CF y el nuevo bit
más significativo del resultado se pone al mismo valor del
anterior (extensión de signo).
Desplaza el contenido del dato cuyo tamaño lo especifica el
sufijo s y que está almacenado a partir de la posición de
memoria mem a la izquierda tantas posiciones como indica
SALs %cl, mem el registro %cl. Para cada desplazamiento, el bit más
significativo del segundo operando se carga en el flag CF y
el menos significativo del resultado se pone a cero.
Desplaza el contenido del dato cuyo tamaño lo especifica el
sufijo s y que está almacenado a partir de la posición de
memoria mem a la derecha tantas posiciones como indica el
SARs %cl, mem registro %cl. Para cada desplazamiento el bit menos
significativo del segundo operando se carga en el flag CF y
el nuevo bit más significativo del resultado se pone al
mismo valor del anterior (extensión de signo).

El flag CF contiene el valor del último bit que ha sido desplazado. El flag OF se modifica sólo en el caso de desplazamientos de 1
bit. Para desplazamientos a izquierda, este flag se pone a cero si los dos bits más significativos del operando antes de desplazarse
son ambos cero. En caso contrario se pone a 1. Si el desplazamiento es a la derecha, el valor es siempre cero. Los flags SF, ZF y
PF se modifican de acuerdo con el resultado obtenido.

A.5.2. SHL/SHR: Desplazamiento lógico

Instrucción Descripción
Desplaza el contenido de %reg a la izquierda tantas
posiciones como indica inm. Para cada desplazamiento, el
SHL $inm, %reg bit más significativo se carga en el flag CF y el menos
significativo se pone a cero.
Desplaza el contenido de %reg a la derecha tantas
posiciones como indica inm. Para cada desplazamiento el
SHR $inm, %reg bit menos significativo se carga en el flag CF y el nuevo bit
más significativo se pone a cero.
Desplaza el contenido de %reg a la izquierda tantas
posiciones como indica el registro %cl. Para cada
SHL %cl, %reg desplazamiento, el bit más significativo se carga en el flag
CF y el menos significativo se pone a cero.
arquitectura IA-32
176 / 198

Instrucción Descripción
Desplaza el contenido de %reg a la derecha tantas
posiciones como indica %cl. Para cada desplazamiento el
SHR %cl, %reg bit menos significativo se carga en el flag CF y el nuevo bit
más significativo se pone a cero.
Desplaza el contenido del dato cuyo tamaño lo especifica el
sufijo s y que está almacenado a partir de la posición de
memoria mem a la izquierda tantas posiciones como indica
SHLs $inm, mem inm. Para cada desplazamiento, el bit más significativo del
segundo operando se carga en el flag CF y el menos
significativo del resultado se pone a cero.
Desplaza el contenido del dato cuyo tamaño lo especifica el
sufijo s y que está almacenado a partir de la posición de
memoria mem a la derecha tantas posiciones como indica
SHRs $inm, mem inm. Para cada desplazamiento el bit menos significativo
del segundo operando se carga en el flag CF y el nuevo bit
más significativo del resultado se pone a cero.
Desplaza el contenido del dato cuyo tamaño lo especifica el
sufijo s y que está almacenado a partir de la posición de
memoria mem a la izquierda tantas posiciones como indica
SHLs %cl, mem el registro %cl. Para cada desplazamiento, el bit más
significativo del segundo operando se carga en el flag CF y
el menos significativo del resultado se pone a cero.
Desplaza el contenido del dato cuyo tamaño lo especifica el
sufijo s y que está almacenado a partir de la posición de
memoria mem a la derecha tantas posiciones como indica el
SHRs %cl, mem registro %cl. Para cada desplazamiento el bit menos
significativo del segundo operando se carga en el flag CF y
el nuevo bit más significativo del resultado se pone a cero.

El flag CF contiene el valor del último bit que ha sido desplazado. El flag OF se modifica sólo en el caso de desplazamientos de 1
bit. Para desplazamientos a izquierda, este flag se pone a cero si los dos bits más significativos del operando antes de desplazarse
son ambos cero. En caso contrario se pone a 1. Si el desplazamiento es a la derecha, el valor es el bit más significativo del segundo
operando. Los flags SF, ZF y PF se modifican de acuerdo con el resultado obtenido.

A.5.3. RCL/RCR: Instrucción de rotación con acarreo

Instrucción Descripción
Rota el contenido de %reg concatenado con el flag CF a la
izquierda (RCL) o derecha (RCR) tantas posiciones como
indica inm. Si el desplazamiento es a la izquierda, para
RCL $inm, %reg cada desplazamiento el bit más significativo se carga en el
flag CF y éste pasa a ser el bit menos significativo. Si el
RCR $inm, %reg desplazamiento es a la derecha, para cada desplazamiento
el bit menos significativo se carga en el flag CF y éste pasa
a ser el bit más significativo.
Rota el contenido de %reg concatenado con el flag CF a la
izquierda (RCL) o derecha (RCR) tantas posiciones como
indica el registro %cl. Si el desplazamiento es a la
RCL %cl, %reg izquierda, para cada desplazamiento el bit más significativo
se carga en el flag CF y éste pasa a ser el bit menos
RCR %cl, %reg significativo. Si el desplazamiento es a la derecha, para
cada desplazamiento el bit menos significativo se carga en
el flag CF y éste pasa a ser el bit más significativo.
arquitectura IA-32
177 / 198

Instrucción Descripción
Rota el dato cuyo tamaño viene especificado por el sufijo s
y que está almacenado en memoria a partir de la posición
mem concatenado con el flag CF a la izquierda (RCL) o
derecha (RCR) tantas posiciones como indica inm. Si el
RCLs $inm, mem desplazamiento es a la izquierda, para cada desplazamiento
el bit más significativo se carga en el flag CF y éste pasa a
RCRs $inm, mem ser el bit menos significativo. Si el desplazamiento es a la
derecha, para cada desplazamiento el bit menos
significativo se carga en el flag CF y éste pasa a ser el bit
más significativo.
Rota el dato cuyo tamaño viene especificado por el sufijo s
y que está almacenado en memoria a partir de la posición
mem concatenado con el flag CF a la izquierda (RCL) o
derecha (RCR) tantas posiciones como indica el
RCLs %cl, mem registro %cl. Si el desplazamiento es a la izquierda, para
cada desplazamiento el bit más significativo se carga en el
RCRs %cl, mem flag CF y éste pasa a ser el bit menos significativo. Si el
desplazamiento es a la derecha, para cada desplazamiento
el bit menos significativo se carga en el flag CF y éste pasa
a ser el bit más significativo.

Estas instrucciones hacen que el operando destino, concatenado con el flag de acarreo CF se comporte igual que si tuviese una
estructura circular. Es decir, el bit más significativo pasa al accarreo y éste al menos significativo si se desplaza a izquierda, y al
revés si se desplaza a derecha.
Esta instrucción se utiliza para posicionar un determinado bit de un dato en el bit de acarreo y así poder consultar su valor.
Mediante la operación inversa, permite dejar tanto el operando como el flag con su valor anterior.
Aparte del flag CF que almacena uno de los bits del operando, el otro flag que se modifica es OF pero sólo en los casos en los
que el desplazamiento es de un bit. Para desplazamientos a izquierda OF contiene el resultado de la disyunción exclusiva entre
CF tras el desplazamiento y el bit más significativo del resultado. Para desplazamientos a la derecha, OF es igual a la disyunción
exclusiva de los dos bits más significativos del resultado.
El resto de flags SF, ZF y PF no se modifican.

A.5.4. ROR/ROL: Instrucción de rotación sin acarreo

Instrucción Descripción
Rota el contenido de %reg a la izquierda (ROL) o derecha
(ROR) tantas posiciones como indica inm. Si el
ROL $inm, %reg
desplazamiento es a la izquierda, para cada desplazamiento
el bit más significativo pasa a ser el menos significativo. Si
ROR $inm, %reg el desplazamiento es a la derecha, para cada
desplazamiento el bit menos significativo pasa a ser el más
significativo.
Rota el contenido de %reg a la izquierda (ROL) o derecha
(ROR) tantas posiciones como indica el registro %cl. Si el
ROL %cl, %reg
desplazamiento es a la izquierda, para cada desplazamiento
el bit más significativo pasa a ser el bit menos significativo.
ROR %cl, %reg Si el desplazamiento es a la derecha, para cada
desplazamiento el bit menos significativo pasa a ser el bit
más significativo.
arquitectura IA-32
178 / 198

Instrucción Descripción
Rota el dato cuyo tamaño viene especificado por el sufijo s
y que está almacenado en memoria a partir de la posición
mem a la izquierda (ROL) o derecha (ROR) tantas
ROLs $inm, mem posiciones como indica inm. Si el desplazamiento es a la
izquierda, para cada desplazamiento el bit más significativo
RORs $inm, mem pasa a ser el bit menos significativo. Si el desplazamiento
es a la derecha, para cada desplazamiento el bit menos
significativo pasa a ser el bit más significativo.
Rota el dato cuyo tamaño viene especificado por el sufijo s
y que está almacenado en memoria a partir de la posición
mem a la izquierda (ROL) o derecha (ROR) tantas
ROLs %cl, mem
posiciones como indica el registro %cl. Si el
desplazamiento es a la izquierda, para cada desplazamiento
RORs %cl, mem el bit más significativo pasa a ser el bit menos significativo.
Si el desplazamiento es a la derecha, para cada
desplazamiento el bit menos significativo pasa a ser el bit
más significativo.

Estas instrucciones hacen que el operando destino se comporte igual que si tuviese una estructura circular. Es decir, el bit más
significativo pasa a ser el bit de menos peso si se desplaza a izquierda, y al revés si se desplaza a derecha.
El flag CF almacena el bit más significativo si se desplaza a izquierda, y el menos significativo si se desplaza a derecha. El flag OF
se modifica sólo en los casos en los que el desplazamiento es de un bit. Para desplazamientos a izquierda OF contiene el resultado
de la disyunción exclusiva entre CF tras el desplazamiento y el bit más significativo del resultado. Para desplazamientos a la
derecha, OF es igual a la disyunción exclusiva de los dos bits más significativos del resultado.
El resto de flags SF, ZF y PF no se modifican.

A.6. Instrucciones de salto

A.6.1. JMP: Instrucción de salto incondicional

Instrucción Descripción

Pasa a ejecutar a continuación la instrucción almacenada a


JMP mem partir de la posición de memoria mem.

Pasa a ejecutar a continuación la instrucción almacenada a


JMP * %reg partir de la posición de memoria en %reg

Esta instrucción simplemente cambia la secuencia de ejecución del procesador que ejecuta la instrucción indicada en la posición
de memoria dada como operando. El uso más común es con una etiqueta como operando, que es donde pasa a ejecutar el
procesador. Si el segundo operando es un registro con el prefijo * el valor del registro se carga en el contador de programa y se
ejecuta la instrucción en la posición de memoria con ese valor.

A.6.2. Jcc: Instrucciones de salto condicional


arquitectura IA-32
179 / 198

Instrucción Condición Descripción Instrucción Condición Descripción

Salto si mayor,
Salto si menor o
JA mem salto si no menor JBE mem
CF = 0 y ZF = 0 CF = 1 ó ZF = 1 igual, salto si no
o igual (sin
JNBE mem JNA mem mayor (sin signo)
signo)

Salto si menor,
Salto si mayor o
JAE mem JB mem salto si no mayor
CF = 0 igual, salto si no CF = 1
o igual (sin
JNB mem menor (sin signo) JNAE mem signo)

JE mem Salto si igual, JNE mem Salto si diferente,


ZF = 1 ZF = 0
salto si cero. salto si no cero.
JZ mem JNZ mem

Salto si menor o
Salto si mayor, si
JG mem JLE mem ZF = 1 ó SF != igual, si no
ZF = 0 y SF = OF no menor o igual
OF mayor (con
JNLE mem (con signo) JNG mem signo)

Salto si mayor o
Salto si menor, si
JGE mem igual, si no JL mem
SF = OF SF != OF no mayor o igual
menor (con
JNL mem JNGE mem (con signo)
signo)

Salto si acarreo Salto si acarreo


JC mem CF = 1 JNC mem CF = 0
es uno es cero

Salto si Salto si
JCXZ mem %cx = 0 registro %cx es JECXZ mem %ecx = 0 registro %ecx es
cero. cero.
Salto si el bit de Salto si el bit de
JO mem OF = 1 desbordamiento JNO mem OF = 0 desbordamiento
es uno. es cero.

Salto si paridad
JPO mem JPE mem Salto si paridad
PF = 0 impar, si no PF = 1
par, si paridad.
JNP mem paridad. JP mem

JS mem SF = 1 Salto si positivo. JNS mem SF = 0 Salto si negativo.

A.6.3. CALL: Instrucción de llamada a subrutina

Instrucción Descripción
Invoca la subrutina cuya primera instrucción está en la
CALL mem posición de memoria mem. Se salva el contador de
programa en la cima de la pila.
arquitectura IA-32
180 / 198

Instrucción Descripción

Invoca la subrutina cuya primera instrucción está en la


CALLs * %reg posición de memoria contenida en el registro %reg.

El efecto relevante de esta instrucción es que deposita en la cima de la pila la dirección de retorno, o lo que es lo mismo, la
dirección de la instrucción que sigue a esta en el flujo de ejecución. Esta instrucción no modifica los flags de la palabra de estado.

A.6.4. RET: Instrucción de retorno de subrutina

Instrucción Descripción
Retorna la ejecución a la instrucción cuya dirección está
RET almacenada en la cima de la pila. Esta dirección se saca de
la pila.

El aspecto relevante de esta instrucción es que supone que la dirección a la que debe retornar está almacenada en la cima de la
pila. Esta instrucción no modifica los flags de la palabra de estado.

A.7. Instrucciones de comparación y comprobación

A.7.1. CMP: Instrucción de comparación

Instrucción Descripción

Realiza la operación %regB - %regA y modifica los flags


CMP %regA, %regB con el resultado que no se almacena en lugar alguno.

Realiza la operación %reg - inm y modifica los flags con


CMP $inm, %reg el restulado que no se almacena en lugar alguno.

Realiza la operación mem - %reg y modifica los flags con


CMP mem, %reg el restulado que no se almacena en lugar alguno.

Realiza la operación mem - %reg y modifica los flags con


CMP %reg, mem el resultado que no se almacena en lugar alguno.

Resta el dato almacenado a partir de la posición de


memoria mem y cuyo tamaño está especificado por el sufijo
CMPs $inm, mem s del valor inm codificado con tantos bits como indica el
sufijo s. Se modifican los flags con el resultado de esta
resta que no se almacena en lugar alguno.

El efecto de esta instrucción se refleja únicamente en los flags de la palabra de estado. Estos flags se pueden utilizar para, por
ejemplo, cambiar el flujo de ejecución mediante una instrucción de salto condicional.

A.7.2. TEST: Instrucción de comprobación


arquitectura IA-32
181 / 198

Instrucción Descripción
Realiza la conjunción bit a bit entre %regB y %regA y
TEST %regA, %regB modifica los flags con el resultado que no se almacena en
lugar alguno.
Realiza la conjunción bit a bit entre %reg e inm y
TEST $inm, %reg modifica los flags con el restulado que no se almacena en
lugar alguno.
Realiza la conjunción bit a bit entre mem y %reg y
TEST mem, %reg modifica los flags con el restulado que no se almacena en
lugar alguno.
Realiza la conjunción bit a bit entre mem y %reg y
TEST %reg, mem modifica los flags con el resultado que no se almacena en
lugar alguno.
Realiza la conjunción bit a bit entre el dato almacenado a
partir de la posición de memoria mem y cuyo tamaño está
especificado por el sufijo s y el valor inm codificado con
TESTs $inm, mem tantos bits como indica el sufijo s. Se modifican los flags
con el resultado de esta resta que no se almacena en lugar
alguno.

El efecto de esta instrucción se refleja únicamente en los flags de la palabra de estado. Los flags OF y CF se ponen a cero. Los
flags SF, ZF y PF se modifican de acuerdo con el resultado.
arquitectura IA-32
182 / 198

Apéndice B

El depurador

Uno de los principales problemas al escribir programas son los errores de ejecución. Compilar un programa no es garantía
suficiente de que funciona de la manera prevista. Es más, el ciclo de desarrollo de un programa está ocupado, en su mayoría
por las tareas de diagnosticar y corregir los errores de ejecución. A los errores de ejecución en programas en inglés se les suele
denominar bugs (bichos).
El origen de la utilización del término bug para describir los errores en un program es un poco confuso, pero hay una referencia
documentada a la que se le suele atribuir este mérito.
La invención del término se atribuye generalmente a la ingeniera Grace Hopper que en 1946 estaba en el laboratorio de compu-
tación de la universidad de Harvard trabajando en los ordenadores con nombre Mark II y Mark III. Los operadores descubrieron
que la causa de un error detectado en el Mark II era una polilla que se había quedado atrapada entre los contactos de un relé (por
aquel entonces el elemento básido de un ordenador) que a su vez era parte de la lógica interna del ordenador. Estos operadores
estaban familiarizados con el término bug e incluso pegaron el insecto en su libro de notas con la anotación ‘First actual case of
bug being found’ (primer caso en el que realmente se encuentra un bug) tal y como ilustra la figura B.1.
arquitectura IA-32
183 / 198

Figura B.1: Primer caso en el que realmente se encuentra un bug (Fuente: U.S. Naval Historical Center Photograph)

Hoy en día, los métodos que se utilizan para depurar los errores de un programa son múltiples y con diferentes niveles de eficacia.
El método consistente en insertar líneas de código que escriben en pantalla mensajes es quizás el más ineficiente de todos ellos.
En realidad lo que se precisa es una herramienta que permita ejecutar de forma controlada un programa, que permita suspender
la ejecución en cualquier punto para poder realizar comprobaciones, ver el contenido de las variables, etc.
Esta herramienta se conoce con el nombre de depurador o, su término inglés, debugger. El depurador es un ejecutable cuya misión
es permitir la ejecución controlada de un segundo ejecutable. Se comporta como un envoltorio dentro del cual se desarrolla una
ejecución normal de un programa, pero a la vez permite realizar una serie de operaciones específicas para visualizar el entorno
de ejecución en cualquier instante.
Más concretamente, el depurador permite:

ejecutar un programa línea a línea


detener la ejecución temporalmente en una línea de código concreta
detener temporalmente la ejecución bajo determinadas condiciones
arquitectura IA-32
184 / 198

visualizar el contenido de los datos en un determinado momento de la ejecución

cambiar el valor del entorno de ejecución para poder ver su efecto de una corrección en el programa

Uno de los depuradores más utilizados en entornos Linux es gdb (Debugger de GNU). En este documento se describen los
comandos más relevantes de este depurador para ser utilizados con un programa escrito en C. Todos los ejemplos utilizados en
el resto de esta sección se basan en el programa cuyo código fuente se muestra en la Tabla B.1 y que se incluye en el fichero
gdbuse.s

.data # Comienza -
sección de datos
nums: .int 2, 3, 2, 7, 5, 4, 9
# Secuencia -
de números a imprimir
tamano: .int 7 # Tamaño de -
la secuencia
1 formato:.string " %d\n" # String -
2 para imprimir un número
.text # Comienza -
3
la sección de código
4
.globl main # main es un -
5
símbolo global
6 main: push %ebp # Bloque de -
7 activación
8 mov %esp, %ebp
9 push %eax # Guardar -
10 copia de los registros en la pila
11 push %ebx
12 push %ecx
13 push %edx
14 mov $0, %ebx
15 bucle: cmp %ebx, tamano
16 je termina
17 push nums(, %ebx,4) # pone el -
18 número en la pila
19 push $formato # pone el -
20 formato en la pila
21 call printf # imprime -
22 los datos que recibe
23 add $8, %esp # borra los -
24 datos de la cima de la pila
25 inc %ebx
26 jmp bucle
27 termina:pop %edx # restaurar -
28 el valor de los registros
29 pop %ecx
pop %ebx
pop %eax
mov %ebp, %esp # Deshacer -
bloque de activación
pop %ebp
ret # termina el -
programa

Tabla B.1: Programa en ensamblador utilizado como ejemplo


arquitectura IA-32
185 / 198

B.1. Arranque y parada del depurador

Para que un programa escrito en ensamblador pueda ser manipulado por gdb es preciso realizar una compilación que incluya
como parte del ejecutable, un conjunto de datos adicionales. Esto se consigue incluyendo la opción -gstabs+ al invocar el
compilador:
shell$ gcc -gstabs+ -o gdbuse gdbuse.s

Si el programa se ha escrito correctamente este comando ha generado el fichero ejecutable con nombre gdbuse. Una vez este
fichero se invoca el depurador con el comando:
shell$ gdb gdbuse

Tras arrancar el depurador se muestra por pantalla un mensaje seguido del prompt (gdb):

shell$ gdb gdbuse


GNU gdb Red Hat Linux (6.0post-0.20040223.19rh)
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-redhat-linux-gnu"...Using host libthread_db library "/ -
lib/tls/libthread_db.so.1".
(gdb)

En este instante, el programa depurador ha arrancado, pero la ejecución del programa gdbuse (que se ha pasado como primer
argumento) todavía no. La interacción con el depurador se realiza a través de comandos introducidos a continuación del prompt,
de forma similar a como se proporcionan comandos a un shell o intérprete de comandos en Linux.
Para arrancar la ejecución del programa se utiliza el comando run (o su abreviatura r). Tras introducir este comando, el programa
se ejecuta de forma normal y se muestra por pantalla de nuevo el prompt (gdb). Por ejemplo:

(gdb) r
Starting program: /home/test/gdbuse
2
3
2
7
5
4
9

Program exited normally.


(gdb)

En el ejemplo, se puede comprobar como el programa termina correctamenta (tal y como denota el mensaje que aparece por
pantalla). Cuando se produce un error en la ejecución, el depurador se detiene y muestra de nuevo el prompt.
Si se desea detener un programa mientras se está ejecutando se debe pulsar Crtl-C (la tecla control, y mientras se mantiene
pulsada, se pulsa C). La interrupción del programa es capturada por el depurador, y el control lo retoma el intérprete de comandos
de gdb. En este instante, la ejecución del programa ha sido detenida pero no terminada. Prueba de ello, es que la ejecución puede
continuarse mediante el comando continue (que se puede abreviar simplemente con la letra c).
arquitectura IA-32
186 / 198

Para salir del depurador se utiliza el comando quit (abreviado por la letra q). Si se pretende terminar la sesión del depurador
mientras el programa está en ejecución se pide confirmación para terminar dicha ejecución.

(gdb) q
The program is running. Exit anyway? (y or n) y
shell$

El comando help muestra la información referente a todos los comandos y sus opciones. Si se invoca sin parámetros, se mues-
tran las categorías en las que se clasifican los comandos. El comando help seguido del nombre de una categoría, proporciona
información detallada sobre sus comandos. Si se invoca seguido de un comando, describe su utilización.

B.2. Visualización de código

El código fuente del programa en ejecución se puede mostrar por pantalla mediante el comando list (abreviado l). Sin opciones,
este comando muestra la porción de código alrededor de la línea que está siendo ejecutada en el instante en el que está detenido el
programa. Si el programa no está en ejecución, se muestra el código a partir de la etiqueta main. El comando list acepta opciones
para mostrar una línea en concreto, una línea en un fichero, una etiqueta en un fichero, e incluso el código almacenado en una
dirección de memoria completa. El comando help list muestra todas las opciones posibles.

(gdb) l main
3 # Secuencia de números a imprimir
4 tamano: .int 7 # Tamaño de la secuencia
5 formato:.string " %d\n" # String para imprimir un número
6 .text # Comienza la sección de código
7 .globl main # main es un símbolo global
8 main: push %ebp # Bloque de activación
9 mov %esp, %ebp
10 push %eax # Guardar copia de los registros en la pila
11 push %ebx
12 push %ecx
(gdb)

B.3. Ejecución controlada de un programa

Aparte de detener la ejecución de un programa con Crtl-C, lo más útil es detener la ejecución en una línea concreta del código.
Para ello es preciso insertar un punto de parada (en inglés breakpoint). Dicho punto es una marca que almacena el depurador, y
cada vez que la ejecución del programa pasa por dicho punto, suspende la ejecución y devuelve el control al usuario. Para insertar
un punto de parada se utiliza el comando break (abreviado b) seguido de la línea en la que se desea introducir.
arquitectura IA-32
187 / 198

(gdb) l 14
9 mov %esp, %ebp
10 push %eax # Guardar copia de los registros en la pila
11 push %ebx
12 push %ecx
13 push %edx
14 mov $0, %ebx
15 bucle: cmp %ebx, tamano
16 je termina
17 push nums(, %ebx,4) # pone el número en la pila
18 push $formato # pone el formato en la pila
(gdb) b 14
Breakpoint 1 at 0x8048377: file gdbuse.s, line 14.
(gdb)

Se pueden introducir tantos puntos de parada como sean necesarios en diferentes lugares del código. El depurador asigna un
número a cada uno de ellos comenzando por el 1. En la última línea del mensaje anterior se puede ver como al punto introducido
en la línea 14 del fichero gdbuse.s se le ha asignado el número 1.
El comando info breakpoints (o su abreviatura info b) muestra por pantalla la lista de puntos de parada que contiene el depurador.

(gdb) l 21
16 je termina
17 push nums(, %ebx,4) # pone el número en la pila
18 push $formato # pone el formato en la pila
19 call printf # imprime los datos que recibe
20 add $8, %esp # borra los datos de la cima de la pila
21 inc %ebx
22 jmp bucle
23 termina:pop %edx # restaurar el valor de los registros
24 pop %ecx
25 pop %ebx
(gdb) b 21
Breakpoint 2 at 0x8048398: file gdbuse.s, line 21.
(gdb) info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x08048377 gdbuse.s:14
2 breakpoint keep y 0x08048398 gdbuse.s:21
(gdb)

Los puntos de parada se pueden introducir en cualquier momento de la ejecución de un proceso. Una vez introducidos, si se
comienza la ejecución del programa mediante el comando run (o su abreviatura r), ésta se detiene en cuanto se ejecuta una línea
con un punto de parada.
arquitectura IA-32
188 / 198

(gdb) r
Starting program: /home/test/gdbuse

Breakpoint 1, main () at gdbuse.s:14


14 mov $0, %ebx
(gdb) c
Continuing.
2

Breakpoint 2, bucle () at gdbuse.s:21


21 inc %ebx
(gdb)

Nótese que el depurador primero se ha detenido en el punto de parada 1, tras introducir el comando continue se ha detenido en
el punto de parada 2.
Cada punto de parada puede ser temporalmente desactivado/activado de manera independiente. Los comandos enable y disable
seguido de un número de punto de parada activan y desactivan respectivamente dichos puntos.
Para reanudar la ejecución del programa previamente suspendida hay tres comandos posibles. El primero que ya se ha visto es
continue (o c). Este comando continua la ejecución del programa y no se detendrá hasta que se encuentre otro punto de parada,
se termine la ejecución, o se produzca un error. El segundo comando para continuar la ejecución es stepi (o su abreviatura si).
Este comando ejecuta únicamente la instrucción en la que está detenido el programa y vuelve de nuevo a suspender la ejecución.

(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y

Starting program: /home/test/gdbuse

Breakpoint 1, main () at gdbuse.s:14


14 mov $0, %ebx
(gdb) si
bucle () at gdbuse.s:15
15 bucle: cmp %ebx, tamano
(gdb) si
16 je termina
(gdb)

Con la utilización de este comando se puede conseguir ejecutar un programa ensamblador instrucción a instrucción de forma
que se pueda ver qué está sucediendo en los registros del procesador y en los datos en memoria. Mediante la combinación del
mecanismo de puntos de parada y el comando stepi se puede ejecutar un programa hasta un cierto punto, y a partir de él ir
instrucción a instrucción. Este proceso es fundamental para detectar los errores en los programas.
El comando stepi tiene un inconveniente. Cuando la instrucción a ejecutar es una llamada a subrutina (por ejemplo la instrucción
call printf), el depurador ejecuta la instrucción call y se detiene en la primera instrucción de la subrutina. Este compor-
tamiento es deseable siempre y cuando se quiera ver el código de la subrutina, pero si dicho código pertenece a una librería del
sistema, lo que se necesita es un comando que permita ejecutar la llamada a la subrutina entera y detenerse en la instrucción que
le sigue. Esto se puede conseguir si, al estar a punto de ejecutar una instrucción call se utiliza el comando nexti en lugar de
stepi.
arquitectura IA-32
189 / 198

(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y

Starting program: /home/bin/gdbuse

Breakpoint 1, main () at gdbuse.s:14


14 mov $0, %ebx
(gdb) si
bucle () at gdbuse.s:15
15 bucle: cmp %ebx, tamano
(gdb) si
16 je termina
(gdb) si
17 push nums(, %ebx,4) # pone el número en la pila
(gdb) si
bucle () at gdbuse.s:18
18 push $formato # pone el formato en la pila
(gdb) si
bucle () at gdbuse.s:19
19 call printf # imprime los datos que recibe
(gdb) ni
2
20 add $8, %esp # borra los datos de la cima de la pila
(gdb)

En general, cuando se produce un error en un programa ensamblador, mediante la utilización de los puntos de parada se permite
llegar al programa al lugar aproximado del código en el que se supone que está el error, y luego mediante la utilización de stepi se
ejecuta instrucción a instrucción teniendo cuidado de utilizar nexti cuando se quiera ejecutar una instrucción call que incluya
la llamada entera.

B.4. Visualización de datos

Los comandos descritos hasta ahora permiten una ejecución controlada de un programa, pero cuando el depurador es realmente
eficiente es cuando hay que localizar un error de ejecución. Generalmente, ese error se manifiesta como una terminación abrup-
ta (por ejemplo segmentation fault). Cuando el programa se ejecuta desde el depurador, esa terminación retorna el control al
depurador con lo que es posible utilizar comandos para inspeccionar el estado en el que ha quedado el programa.
Uno de los comandos más útiles del depurador es print (o su abreviatura p). Como argumento recibe una expresión, y su efecto
es imprimir el valor resultante de evaluar dicha expresión. Este comando puede recibir el nombre de cualquier símbolo que esté
visible en ese instante en la ejecución del programa. El contenido de uno de estos símbolos se muestra por pantalla simplemente
escribiendo el comando print seguido del nombre.
arquitectura IA-32
190 / 198

(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y

Starting program: /home/bin/gdbuse

Breakpoint 1, main () at gdbuse.s:14


14 mov $0, %ebx
(gdb) p tamano
$7 = 7
(gdb)

Aparte de nombres de etiquetas que apuntan a datos, print acepta expresiones que se refieren a los registros del procesador:
$eax, $ebx, etc.

Breakpoint 1, main () at gdbuse.s:14


14 mov $0, %ebx
(gdb) p tamano
$7 = 7
(gdb) p $eax
$8 = 0
(gdb) p $ebx
$9 = 9105372
(gdb) p/x $ebx
$10 = 0x8aefdc
(gdb)

Nótese que el último comando print tiene el sufijo /x que hace que el resultado se muestre en hexadecimal, en lugar de decimal.
Si se quiere ver el contenido de todos los registros del procesador se puede utilizar el comando info registers.

(gdb) info registers


eax 0x0 0
ecx 0xfefff5ec -16779796
edx 0xfefff5e4 -16779804
ebx 0x8aefdc 9105372
esp 0xfefff548 0xfefff548
ebp 0xfefff558 0xfefff558
esi 0x1 1
edi 0x8b10dc 9113820
eip 0x8048377 0x8048377
eflags 0x200246 2097734
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb)

Nótese que para cada registro se muestra su valor en hexadecimal seguido por su representación en decimal. No todos los registros
arquitectura IA-32
191 / 198

que muestra este comando son manipulables desde un programa ensamblador, tan sólo los ocho primeros.
El comando print permite igualmente visualizar arrays de valores consecutivos en memoria. Para ello es preciso especificar en
el comando el tipo de datos que contiene el array y su longitud. El formato utilizado es incluir entre paréntesis el tipo seguido
por el tamaño entre corchetes. En el programa dado como ejemplo el comando para imprimir los siete números enteros que se
definen en la etiqueta nums es:

(gdb) p/x (int[7])nums


$13 = {0x2, 0x3, 0x2, 0x7, 0x5, 0x4, 0x9}
(gdb)

Si lo que se necesita es visualizar los bytes almacenados en un lugar concreto de memoria, el comando examine (o su abreviatura
‘x’) imprime una determinada porción de memoria por pantalla. La sintaxis de este comando es ‘x/NFU dirección’. Las letras
NFU representan opciones del comando. La N representa un entero que codifica el número de unidades de información en
memoria a mostrar. La F representa el formato en el que se muestran los datos (al igual que el comando print, la ‘x’ quiere decir
hexadecimal). La letra U representa el tamaño de las unidades a mostrar. Sus posibles valores son ‘b’ para bytes, ‘h’ para palabras
de 2 bytes, ‘w’ para palabras de 4 bytes y ‘g’ para palabras de ocho bytes.
La dirección a partir de la cual se muestra el contenido se puede dar como una constante en hexadecimal, o como el nombre de
una etiqueta precedido del carácter ‘&’. Por ejemplo, para mostrar el contenido de las 7 palabras de 4 bytes almacenadas a partir
de la etiqueta nums el comando es:

(gdb) x/7xw &nums


0x804957c >nums<: 0x00000002 0x00000003 0x00000002 0x00000007
0x804958c >nums+16<: 0x00000005 0x00000004 0x00000009
(gdb)

Este comando se puede utilizar para mostrar el contenido de una porción de memoria a la que apunta un determinado registro. Por
ejemplo, para mostrar las cuatro palabras de memoria almacenadas en la cima de la pila se puede utilizar el siguiente comando:

(gdb) x/16xb $esp


0xfefff548: 0xe4 0xf5 0xff 0xfe 0xec 0xf5 0xff 0xfe
0xfefff550: 0xdc 0xef 0x8a 0x00 0x00 0x00 0x00 0x00
(gdb)

Además de visualizar datos en registros o memoria, el depurador permite también manipular estos datos mientras el programa
está detenido. El comando set permite la asignación de un valor numérico tanto a porciones de memoria como a registros. Para
asignar el valor 10 al registro %eax se utiliza el comando:

(gdb) set $eax=10


(gdb) p $eax
$14 = 10
(gdb)

Este comando es útil cuando se detecta un valor erróneo en un registro y se puede corregir para mostrar si el programa puede
continuar normalmente.
arquitectura IA-32
192 / 198

Al igual que se permite modificar datos en registros, también se pueden modificar datos en memoria. Para ello es necesario
especificar el tipo de dato que se está almacenando entre llaves seguido de la dirección de memoria. De esta forma se especifica
dónde almacenar el valor que se proporciona a continuación tras el símbolo de igual. Por ejemplo:

(gdb) set {int}0x83040 = 4


(gdb)

El comando anterior almacena el valor 4 en la posición de memoria cuya dirección es 0x83040 y almacena 4 bytes porque se
refiere a ella como un entero.

B.5. Ejercicios

Para la realización de los siguientes ejercicios se utiliza el código fuente utilizado como ejemplo, mostrado en la Tabla B.1 y
contenido en el fichero gdbuse.s. Se supone que el programa ha sido compilado, el ejecutable producido y el depurador arrancado.

1. ¿Qué comando hay que utilizar para mostrar por pantalla todos los bytes que codifican el string con nombre formato?
¿Cuál es el valor del último byte?
2. Se sabe que las instrucciones del tipo push :registro: se codifican mediante un único byte y mov :registro-
:, :registro: mediante dos bytes. Utilizando únicamente el depurador, decir cuál es el código hexadecimal de las
siguientes instrucciones:

push %ebp
push %eax
push %ebx
push %ecx
push %edx

3. Situar un punto de parada en la instrucción call printf. ¿Qué comando es necesario para mostrar por pantalla el valor
que deposita en la cima de la pila la instrucción push $formato?
4. Introducir un punto de parada en la línea 10 del código (en la instrucción push %eax). Mostrar por pantalla mediante el
comando print el valor de los registros %eax, %ebx, %ecx y %edx. Apuntar estos valores.
A continuación introducir un segundo punto de parada en la línea 14 (en la instrucción mov $0, %ebx). Mediante el
comando continue continuar la ejecución hasta ese punto.
¿Qué comando hay que utilizar para mostrar por pantalla el contenido de las cuatro palabras de memoria que se encuentran
en la cima de la pila? Comprobar que estos valores son idénticos a los mostrados en el primer punto de parada.

5. La instrucción inc %ebx aumenta el valor de dicho registro en una unidad. Este registro contiene el índice del siguiente
elemento a imprimir. Poner un punto de parada en la instrucción siguiente a esta y con el programa detenido modificar el
valor de este registro con un número entre cero y seis (ambos inclusive). Explica qué es lo que sucede y por qué.
6. Utilizando la ejecución instrucción a instrucción que permite el depurador, ¿qué instrucción se ejecuta justo antes de la
instrucción pop %edx?

7. La instrucción push nums(, %ebx,4) deposita un cierto valor en la pila. Introducir un punto de parada en la siguiente
instrucción, y una vez detenido el programa, poner en la cima de la pila otro número arbitrario mediante el comando set.
Explica qué efecto tiene esto y por qué.
arquitectura IA-32
193 / 198

B.6.

Introducir todos los valores en hexadecimal y con el prefijo 0x. Responder a todas las preguntas antes de pulsar el botón de envío
al final del formulario.

0. ¿Qué comando hay que utilizar para mostrar por pantalla todos los bytes que codifican el string con nombre
formato? text IntroToGdb:A1_stringCod 16 16
¿Cuál es el valor del último byte? text IntroToGdb:A2_stringLastByte 16 16

0. Se sabe que las instrucciones del tipo push :registro: se codifican mediante un único byte y mov
:registro:, :registro: mediante dos bytes. Utilizando únicamente el depurador, decir cuál es el código
hexadecimal de las siguientes instrucciones:
push %ebp text IntroToGdb:B1_push 16 16
push %eax text IntroToGdb:B2_push 16 16
push %ebx text IntroToGdb:B3_push 16 16
push %ecx text IntroToGdb:B4_push 16 16
push %edx text IntroToGdb:B5_push 16 16
0. Situar un punto de parada en la instrucción call printf. ¿Qué comando es necesario para mostrar por pantalla
el valor que deposita en la cima de la pila la instrucción push $formato? text IntroToGdb:C_pushformat 16 16

0. ¿Qué comando hay que utilizar para mostrar por pantalla el contenido de las cuatro palabras de memoria que se
encuentran en la cima de la pila? Comprobar que estos valores son idénticos a los mostrados en el primer punto de
parada. text IntroToGdb:D_stackTop 16 16
0. La instrucción inc %ebx aumenta el valor de dicho registro en una unidad. Este registro contiene el índice del
siguiente elemento a imprimir. Poner un punto de parada en la instrucción siguiente a esta y con el programa
detenido modificar el valor de este registro con un número entre cero y seis (ambos inclusive). Explica qué es lo que
sucede y por qué.
IntroToGdb:E_arrayindex 80 10
0. Utilizando la ejecución instrucción a instrucción que permite el depurador, ¿qué instrucción se ejecuta justo antes
de la instrucción pop %edx? text IntroToGdb:F_beforepop 16 16

0. La instrucción push nums(, %ebx,4) deposita un cierto valor en la pila. Introducir un punto de parada en la
siguiente instrucción, y una vez detenido el programa, poner en la cima de la pila otro número arbitrario mediante el
comando set. Explica qué efecto tiene esto y por qué.
IntroToGdb:G_modifyvalue 80 10
arquitectura IA-32
194 / 198

Apéndice C

Licencia Creative Commons

Reconocimiento-NoComercial-CompartirIgual 2.5 España


CREATIVE COMMONS CORPORATION NO ES UN DESPACHO DE ABOGADOS Y NO PROPORCIONA SERVICIOS
JURÍDICOS. LA DISTRIBUCIÓN DE ESTA LICENCIA NO CREA UNA RELACIÓN ABOGADO-CLIENTE. CREATIVE
COMMONS PROPORCIONA ESTA INFORMACIÓN TAL CUAL (ON AN "AS-IS" BASIS). CREATIVE COMMONS NO
OFRECE GARANTÍA ALGUNA RESPECTO DE LA INFORMACIÓN PROPORCIONADA, NI ASUME RESPONSABILI-
DAD ALGUNA POR DAÑOS PRODUCIDOS A CONSECUENCIA DE SU USO.
Licencia
LA OBRA (SEGÚN SE DEFINE MÁS ADELANTE) SE PROPORCIONA BAJO LOS TÉRMINOS DE ESTA LICENCIA
PÚBLICA DE CREATIVE COMMONS ("CCPL" O "LICENCIA"). LA OBRA SE ENCUENTRA PROTEGIDA POR LA LEY
ESPAÑOLA DE PROPIEDAD INTELECTUAL Y/O CUALESQUIERA OTRAS NORMAS RESULTEN DE APLICACIÓN.
QUEDA PROHIBIDO CUALQUIER USO DE LA OBRA DIFERENTE A LO AUTORIZADO BAJO ESTA LICENCIA O LO
DISPUESTO EN LAS LEYES DE PROPIEDAD INTELECTUAL.
MEDIANTE EL EJERCICIO DE CUALQUIER DERECHO SOBRE LA OBRA, USTED ACEPTA Y CONSIENTE LAS LI-
MITACIONES Y OBLIGACIONES DE ESTA LICENCIA. EL LICENCIADOR LE CEDE LOS DERECHOS CONTENIDOS
EN ESTA LICENCIA, SIEMPRE QUE USTED ACEPTE LOS PRESENTES TÉRMINOS Y CONDICIONES.
1. Definiciones

a. La "obra" es la creación literaria, artística o científica ofrecida bajo los términos de esta licencia.

b. El "autor" es la persona o la entidad que creó la obra.


c. Se considerará "obra conjunta" aquella susceptible de ser incluida en alguna de las siguientes categorías:
1. "Obra en colaboración", entendiendo por tal aquella que sea resultado unitario de la colaboración de varios autores.
2. "Obra colectiva", entendiendo por tal la creada por la iniciativa y bajo la coordinación de una persona natural o
jurídica que la edite y divulgue bajo su nombre y que esté constituida por la reunión de aportaciones de diferentes
autores cuya contribución personal se funde en una creación única y autónoma, para la cual haya sido concebida sin
que sea posible atribuir separadamente a cualquiera de ellos un derecho sobre el conjunto de la obra realizada.
3. "Obra compuesta e independiente", entendiendo por tal la obra nueva que incorpore una obra preexistente sin la
colaboración del autor de esta última.

d. Se considerarán "obras derivadas" aquellas que se encuentren basadas en una obra o en una obra y otras preexistentes,
tales como: las traducciones y adaptaciones; las revisiones, actualizaciones y anotaciones; los compendios, resúmenes y
extractos; los arreglos musicales y, en general, cualesquiera transformaciones de una obra literaria, artística o científica,
salvo que la obra resultante tenga el carácter de obra conjunta en cuyo caso no será considerada como una obra derivada a
los efectos de esta licencia. Para evitar la duda, si la obra consiste en una composición musical o grabación de sonidos, la
sincronización temporal de la obra con una imagen en movimiento ("synching") será considerada como una obra derivada
a los efectos de esta licencia.
arquitectura IA-32
195 / 198

e. Tendrán la consideración de "obras audiovisuales" las creaciones expresadas mediante una serie de imágenes asociadas,
con o sin sonorización incorporada, así como las composiciones musicales, que estén destinadas esencialmente a ser
mostradas a través de aparatos de proyección o por cualquier otro medio de comunicación pública de la imagen y del
sonido, con independencia de la naturaleza de los soportes materiales de dichas obras.
f. El "licenciador" es la persona o la entidad que ofrece la obra bajo los términos de esta licencia y le cede los derechos de
explotación de la misma conforme a lo dispuesto en ella.

g. "Usted" es la persona o la entidad que ejercita los derechos cedidos mediante esta licencia y que no ha violado previamente
los términos de la misma con respecto a la obra, o que ha recibido el permiso expreso del licenciador de ejercitar los
derechos cedidos mediante esta licencia a pesar de una violación anterior.
h. La "transformación" de una obra comprende su traducción, adaptación y cualquier otra modificación en su forma de
la que se derive una obra diferente. Cuando se trate de una base de datos según se define más adelante, se considerará
también transformación la reordenación de la misma. La creación resultante de la transformación de una obra tendrá la
consideración de obra derivada.
i. Se entiende por "reproducción" la fijación de la obra en un medio que permita su comunicación y la obtención de copias
de toda o parte de ella.
j. Se entiende por "distribución" la puesta a disposición del público del original o copias de la obra mediante su venta,
alquiler, préstamo o de cualquier otra forma.
k. Se entenderá por "comunicación pública" todo acto por el cual una pluralidad de personas pueda tener acceso a la obra sin
previa distribución de ejemplares a cada una de ellas. No se considerará pública la comunicación cuando se celebre dentro
de un ámbito estrictamente doméstico que no esté integrado o conectado a una red de difusión de cualquier tipo. A efectos
de esta licencia se considerará comunicación pública la puesta a disposición del público de la obra por procedimientos
alámbricos o inalámbricos, incluida la puesta a disposición del público de la obra de tal forma que cualquier persona pueda
acceder a ella desde el lugar y en el momento que elija.
l. La "explotación" de la obra comprende su reproducción, distribución, comunicación pública y transformación.
m. Tendrán la consideración de "bases de datos" las colecciones de obras ajenas, de datos o de otros elementos independientes
como las antologías y las bases de datos propiamente dichas que por la selección o disposición de sus contenidos constitu-
yan creaciones intelectuales, sin perjuicio, en su caso, de los derechos que pudieran subsistir sobre dichos contenidos.
n. Los "elementos de la licencia" son las características principales de la licencia según la selección efectuada por el licen-
ciador e indicadas en el título de esta licencia: Reconocimiento de autoría (Reconocimiento), Sin uso comercial (NoCo-
mercial), Compartir de manera igual (CompartirIgual).

2. Límites y uso legítimo de los derechos. Nada en esta licencia pretende reducir o restringir cualesquiera límites legales de
los derechos exclusivos del titular de los derechos de propiedad intelectual de acuerdo con la Ley de Propiedad Intelectual o
cualesquiera otras leyes aplicables, ya sean derivados de usos legítimos, tales como el derecho de copia privada o el derecho a
cita, u otras limitaciones como la derivada de la primera venta de ejemplares.
3. Concesión de licencia. Conforme a los términos y a las condiciones de esta licencia, el licenciador concede (durante toda la
vigencia de los derechos de propiedad intelectual) una licencia de ámbito mundial, sin derecho de remuneración, no exclusiva e
indefinida que incluye la cesión de los siguientes derechos:

a. Derecho de reproducción, distribución y comunicación pública sobre la obra.


b. Derecho a incorporarla en una o más obras conjuntas o bases de datos y para su reproducción en tanto que incorporada a
dichas obras conjuntas o bases de datos.
c. Derecho para efectuar cualquier transformación sobre la obra y crear y reproducir obras derivadas.
d. Derecho de distribución y comunicación pública de copias o grabaciones de la obra, como incorporada a obras conjuntas
o bases de datos.

e. Derecho de distribución y comunicación pública de copias o grabaciones de la obra, por medio de una obra derivada.
arquitectura IA-32
196 / 198

Los anteriores derechos se pueden ejercitar en todos los medios y formatos, tangibles o intangibles, conocidos o por conocer.
Los derechos mencionados incluyen el derecho a efectuar las modificaciones que sean precisas técnicamente para el ejercicio
de los derechos en otros medios y formatos. Todos los derechos no cedidos expresamente por el licenciador quedan reservados,
incluyendo, a título enunciativo pero no limitativo, los establecidos en la sección 4e.
4. Restricciones. La cesión de derechos que supone esta licencia se encuentra sujeta y limitada a las restricciones siguientes:

a. Usted puede reproducir, distribuir o comunicar públicamente la obra solamente bajo los términos de esta licencia y debe
incluir una copia de la misma, o su Identificador Uniforme de Recurso (URI), con cada copia o grabación de la obra que
usted reproduzca, distribuya o comunique públicamente. Usted no puede ofrecer o imponer ningún término sobre la obra
que altere o restrinja los términos de esta licencia o el ejercicio de sus derechos por parte de los cesionarios de la misma.
Usted no puede sublicenciar la obra. Usted debe mantener intactos todos los avisos que se refieran a esta licencia y a la
ausencia de garantías. Usted no puede reproducir, distribuir o comunicar públicamente la obra con medidas tecnológicas
que controlen el acceso o uso de la obra de una manera contraria a los términos de esta licencia. Lo anterior se aplica a
una obra en tanto que incorporada a una obra conjunta o base de datos, pero no implica que éstas, al margen de la obra
objeto de esta licencia, tengan que estar sujetas a los términos de la misma. Si usted crea una obra conjunta o base de datos,
previa comunicación del licenciador, usted deberá quitar de la obra conjunta o base de datos cualquier referencia crédito
requerido en el apartado 4d, según lo que se le requiera y en la medida de lo posible. Si usted crea una obra derivada, previa
comunicación del licenciador, usted deberá quitar de la obra derivada cualquier crédito requerido en el apartado 4d, según
lo que se le requiera y en la medida de lo posible.
b. Usted puede reproducir, distribuir o comunicar públicamente una obra derivada solamente bajo los términos de esta licen-
cia, o de una versión posterior de esta licencia con sus mismos elementos principales, o de una licencia iCommons de Crea-
tive Commons que contenga los mismos elementos principales que esta licencia (ejemplo: Reconocimiento-NoComercial-
Compartir 2.5 Japón). Usted debe incluir una copia de la esta licencia o de la mencionada anteriormente, o bien su Identi-
ficador Uniforme de Recurso (URI), con cada copia o grabación de la obra que usted reproduzca, distribuya o comunique
públicamente. Usted no puede ofrecer o imponer ningún término respecto de las obras derivadas o sus transformaciones
que alteren o restrinjan los términos de esta licencia o el ejercicio de sus derechos por parte de los cesionarios de la misma.
Usted debe mantener intactos todos los avisos que se refieran a esta licencia y a la ausencia de garantías. Usted no puede
reproducir, distribuir o comunicar públicamente la obra derivada con medidas tecnológicas que controlen el acceso o uso
de la obra de una manera contraria a los términos de esta licencia. Lo anterior se aplica a una obra derivada en tanto que
incorporada a una obra conjunta o base de datos, pero no implica que éstas, al margen de la obra objeto de esta licencia,
tengan que estar sujetas a los términos de esta licencia.
c. Usted no puede ejercitar ninguno de los derechos cedidos en la sección 3 anterior de manera que pretenda principalmente o
se encuentre dirigida hacia la obtención de un beneficio mercantil o la remuneración monetaria privada. El intercambio de
la obra por otras obras protegidas por la propiedad intelectual mediante sistemas de compartir archivos no se considerará
como una manera que pretenda principalmente o se encuentre dirigida hacia la obtención de un beneficio mercantil o la
remuneración monetaria privada, siempre que no haya ningún pago de cualquier remuneración monetaria en relación con
el intercambio de las obras protegidas.
d. Si usted reproduce, distribuye o comunica públicamente la obra o cualquier obra derivada, conjunta o base datos que la
incorpore, usted debe mantener intactos todos los avisos sobre la propiedad intelectual de la obra y reconocer al autor
original, de manera razonable conforme al medio o a los medios que usted esté utilizando, indicando el nombre (o el seu-
dónimo, en su caso) del autor original si es facilitado, y/o reconocer a aquellas partes (por ejemplo: institución, publicación,
revista) que el autor original y/o el licenciador designen para ser reconocidos en el aviso legal, las condiciones de uso, o
de cualquier otra manera razonable; el título de la obra si es facilitado; de manera razonable, el Identificador Uniforme
de Recurso (URI), si existe, que el licenciador especifica para ser vinculado a la obra, a menos que tal URI no se refiera
al aviso sobre propiedad intelectual o a la información sobre la licencia de la obra; y en el caso de una obra derivada, un
aviso que identifique el uso de la obra en la obra derivada (e.g., "traducción castellana de la obra de Autor Original," o
"guión basado en obra original de Autor Original"). Tal aviso se puede desarrollar de cualquier manera razonable; con tal
de que, sin embargo, en el caso de una obra derivada, conjunta o base datos, aparezca como mínimo este aviso allá donde
aparezcan los avisos correspondientes a otros autores y de forma comparable a los mismos.
e. Para evitar la duda, sin perjuicio de la preceptiva autorización del licenciador, y especialmente cuando la obra se trate de
una obra audiovisual, el licenciador se reserva el derecho exclusivo a percibir, tanto individualmente como mediante una
entidad de gestión de derechos, o varias, (por ejemplo: SGAE, Dama, VEGAP), los derechos de explotación de la obra,
así como los derivados de obras derivadas, conjuntas o bases de datos, si dicha explotación pretende principalmente o se
encuentra dirigida hacia la obtención de un beneficio mercantil o la remuneración monetaria privada.
arquitectura IA-32
197 / 198

f. En el caso de la inclusión de la obra en alguna base de datos o recopilación, el propietario o el gestor de la base de datos
deberá renunciar a cualquier derecho relacionado con esta inclusión y concerniente a los usos de la obra una vez extraída
de las bases de datos, ya sea de manera individual o conjuntamente con otros materiales.

5. Exoneración de responsabilidad
A MENOS QUE SE ACUERDE MUTUAMENTE ENTRE LAS PARTES, EL LICENCIADOR OFRECE LA OBRA TAL
CUAL (ON AN "AS-IS" BASIS) Y NO CONFIERE NINGUNA GARANTÍA DE CUALQUIER TIPO RESPECTO DE LA
OBRA O DE LA PRESENCIA O AUSENCIA DE ERRORES QUE PUEDAN O NO SER DESCUBIERTOS. ALGUNAS
JURISDICCIONES NO PERMITEN LA EXCLUSIÓN DE TALES GARANTÍAS, POR LO QUE TAL EXCLUSIÓN PUEDE
NO SER DE APLICACIÓN A USTED.
6. Limitación de responsabilidad.
SALVO QUE LO DISPONGA EXPRESA E IMPERATIVAMENTE LA LEY APLICABLE, EN NINGÚN CASO EL LICEN-
CIADOR SERÁ RESPONSABLE ANTE USTED POR CUALQUIER TEORÍA LEGAL DE CUALESQUIERA DAÑOS RE-
SULTANTES, GENERALES O ESPECIALES (INCLUIDO EL DAÑO EMERGENTE Y EL LUCRO CESANTE), FORTUI-
TOS O CAUSALES, DIRECTOS O INDIRECTOS, PRODUCIDOS EN CONEXIÓN CON ESTA LICENCIA O EL USO DE
LA OBRA, INCLUSO SI EL LICENCIADOR HUBIERA SIDO INFORMADO DE LA POSIBILIDAD DE TALES DAÑOS.
7. Finalización de la licencia

a. Esta licencia y la cesión de los derechos que contiene terminarán automáticamente en caso de cualquier incumplimiento de
los términos de la misma. Las personas o entidades que hayan recibido obras derivadas, conjuntas o bases de datos de usted
bajo esta licencia, sin embargo, no verán sus licencias finalizadas, siempre que tales personas o entidades se mantengan en
el cumplimiento íntegro de esta licencia. Las secciones 1, 2, 5, 6, 7 y 8 permanecerán vigentes pese a cualquier finalización
de esta licencia.
b. Conforme a las condiciones y términos anteriores, la cesión de derechos de esta licencia es perpetua (durante toda la
vigencia de los derechos de propiedad intelectual aplicables a la obra). A pesar de lo anterior, el licenciador se reserva el
derecho a divulgar o publicar la obra en condiciones distintas a las presentes, o de retirar la obra en cualquier momento.
No obstante, ello no supondrá dar por concluida esta licencia (o cualquier otra licencia que haya sido concedida, o sea
necesario ser concedida, bajo los términos de esta licencia), que continuará vigente y con efectos completos a no ser que
haya finalizado conforme a lo establecido anteriormente.

8. Miscelánea

a. Cada vez que usted explote de alguna forma la obra, o una obra conjunta o una base datos que la incorpore, el licenciador
original ofrece a los terceros y sucesivos licenciatarios la cesión de derechos sobre la obra en las mismas condiciones y
términos que la licencia concedida a usted.
b. Cada vez que usted explote de alguna forma una obra derivada, el licenciador original ofrece a los terceros y sucesivos
licenciatarios la cesión de derechos sobre la obra original en las mismas condiciones y términos que la licencia concedida
a usted.
c. Si alguna disposición de esta licencia resulta inválida o inaplicable según la Ley vigente, ello no afectará la validez o
aplicabilidad del resto de los términos de esta licencia y, sin ninguna acción adicional por cualquiera las partes de este
acuerdo, tal disposición se entenderá reformada en lo estrictamente necesario para hacer que tal disposición sea válida y
ejecutiva.
d. No se entenderá que existe renuncia respecto de algún término o disposición de esta licencia, ni que se consiente violación
alguna de la misma, a menos que tal renuncia o consentimiento figure por escrito y lleve la firma de la parte que renuncie
o consienta.

e. Esta licencia constituye el acuerdo pleno entre las partes con respecto a la obra objeto de la licencia. No caben interpreta-
ciones, acuerdos o términos con respecto a la obra que no se encuentren expresamente especificados en la presente licencia.
El licenciador no estará obligado por ninguna disposición complementaria que pueda aparecer en cualquier comunicación
de usted. Esta licencia no se puede modificar sin el mutuo acuerdo por escrito entre el licenciador y usted.
arquitectura IA-32
198 / 198

Creative Commons no es parte de esta licencia, y no ofrece ninguna garantía en relación con la obra. Creative Commons no será
responsable frente a usted o a cualquier parte, por cualquier teoría legal de cualesquiera daños resultantes, incluyendo, pero no
limitado, daños generales o especiales (incluido el daño emergente y el lucro cesante), fortuitos o causales, en conexión con esta
licencia. A pesar de las dos (2) oraciones anteriores, si Creative Commons se ha identificado expresamente como el licenciador,
tendrá todos los derechos y obligaciones del licenciador.
Salvo para el propósito limitado de indicar al público que la obra está licenciada bajo la CCPL, ninguna parte utilizará la
marca registrada "Creative Commons" o cualquier marca registrada o insignia relacionada con "Creative Commons" sin su
consentimiento por escrito. Cualquier uso permitido se hará de conformidad con las pautas vigentes en cada momento sobre el
uso de la marca registrada por "Creative Commons", en tanto que sean publicadas su sitio web (website) o sean proporcionadas
a petición previa.
Puede contactar con Creative Commons en: http://creativecommons.org/.

También podría gustarte