Está en la página 1de 126

CAPITULO I INTRODUCION AL LENGUAJE ENSAMBLADOR

En este capítulo encontraras el fundamento para aprender a programar en lenguaje ensamblador, programación,
procesadores, lenguajes de computación, sistemas de números y software para desarrollo de herramientas.

Nuevos términos

Procesador. El circuito, en la tarjeta principal de la máquina, que ejecuta las operaciones básicas de la
computadora.

Conjunto de instrucción. El conjunto de todas las posibles instrucciones que el procesador puede
ejecutar.

Lenguaje Máquina. El conjunto de operaciones básicas, ejecutadas por el procesador, además de las
reglas para usarlas.

Lenguaje de alto nivel. Un lenguaje cuyas instrucciones están muy lejanas del lenguaje máquina.

Lenguaje de bajo nivel. Un lenguaje cuyas instrucciones están más cercanas al lenguaje máquina.

Lenguaje Ensamblador. Es un tipo de lenguaje de bajo nivel en el cual abreviaciones mnemotécnicas


representan operaciones máquina y almacenan localidades. El lenguaje ensamblador hace posible
programar al procesador directamente sin usar lenguaje máquina.

Compilador. Un programa que traduce programas de alto nivel a lenguaje máquina para que estos
puedan ser ejecutados.

Interprete. Un programa que traduce programas de alto nivel a lenguaje máquina, una línea a la vez,
conforme se van ejecutando.

Compilador Incremental. Un programa que combina compilación e interpretación para traducir un


programa de alto nivel a una forma intermedia (pseudo-código) que es después interpretado.

Pseudo-Código. La forma en la cual un programa de alto nivel es parcialmente compilado por un


compilador incremental.

Ensamblador. (1) Un programa que traduce programas de bajo nivel a lenguaje máquina. (2) Un término
usado para nombrar al Lenguaje Ensamblador.

Coprocesador. Un procesador opcional usado para ejecutar operaciones matemáticas, especialmente


cálculos de punto flotante.

Modo Real. Un estado de operación en el cual un 286, 386, o 486 se comportan como un 8088 rápido; en
el modo real, la computadora puede ejecutar muchos programas escritos por los miembros viejos de la
familia PC.

Modo Protegido. Un estado de operación en el cual un 286, 386, o 486 pueden tener acceso a una gran
cantidad de memoria; este estado también permite las multitareas, particularmente para trabajos en
sistemas operativos y otros software’s sofisticados.

1
Memoria Virtual. Un sistema de administración de la memoria en el cual la memoria del disco es usada
para simular grandes cantidades de memoria regular interna.

Multitareas. Ejecutar más de un programa al mismo tiempo por medio de rápidos cambios entre los
programas.

Modo Virtual 86. Un estado de operación en el cual un procesador 386, o 486 pueden correr múltiples
programas, donde cada uno tiene su propio procesador 8086.

Código. (1) Usado como verbo el término se refiere a un programa escrito. (2) Usado como sustantivo el
término se refiere a las instrucciones de un programa.

Editor. Un programa que permite teclear un programa y hacerle cambios cuando sea necesario.

Enlazador (Linker). Un programa que procesa un programa traducido de ensamblador a una forma en
que puede ser ejecutado.

Programa Fuente ó Código Fuente. Un programa en lenguaje ensamblador que va a ser traducido a
Ensamblador. Los programas fuente son almacenados en archivos con la extensión ASM.

Modulo Objeto. La traducción a lenguaje máquina del programa fuente; los módulos objeto son
almacenados en archivos con extensión OBJ.

Modulo Cargable (Load Module ó Run Module). Una versión ejecutable de un modulo objeto, también
llamado un run module, producido por el Enlazador. Los módulos cargables son almacenados en archivos
con la extensión EXE (o algunas veces COM).

Depurador (Debugger). Un programa que provee un ambiente para probar load modules.

1.1 Orientación.

En términos informales el cerebro de la computadora es el procesador, un chip electrónico que, en


muchas computadoras, se encuentra en la tarjeta del sistema principal, las computadoras personales IBM
usan procesadores de la familia Intel 86. Estos procesadores son conocidos por un número de modelo,
desde los menos poderosos hasta los actuales, estos son: 8086, 8088, 186, 286, 386, 486, Pentium, etc.

Dentro de cada número de modelo hay variaciones, por ejemplo existen procesadores 386 SX y 386 DX.
La más importante idea de IBM es que todos sus procesadores, menos alguno, corran los mismos
programas, asegurándose que los miembros de la familia Intel 86 sean mutuamente compatibles. Las
habilidades que aprendas del lenguaje ensamblador te permitirán programar cualquier PC, sin importar
que procesador uses.

1.2 Lenguaje Máquina.

La lista de todas las posibles instrucciones que cada procesador puede ejecutar es denominada
conjunto de instrucciones. La tabla 1-1 muestra el tamaño del conjunto de instrucciones para varios
miembros de la familia Intel 86.

2
Tabla 1-1 Procesador Tamaño del conjunto de instrucciones
8086/8088 115
186 126
286 142
386 200
486 206
Pentium 216

El conjunto de instrucciones que el procesador puede ejecutar directamente, y las reglas para usar esas
instrucciones se llama lenguaje máquina. En lenguaje máquina cada instrucción esta codificada con un
número. Si tú ves un programa en lenguaje máquina veras una gran cadena de números.

Los seres humanos no pueden programar en lenguaje máquina, debido a que un programa consiste de
cientos de miles de números. De una forma más simple nosotros podemos escribir programas usando
lenguajes de computación. Nosotros podemos dividir los lenguajes de computación en dos grupos: alto
nivel y bajo nivel. Los lenguajes de alto nivel tales como C, C++, Pascal o Basic, están muy lejanos del
lenguaje máquina. Cuando tú usas un lenguaje de alto nivel, te concentras en el problema a resolver, y no
necesitas saber nada acerca de como el procesador ejecutará el programa. Por supuesto antes de ejecutarlo
este tiene que ser convertido a lenguaje máquina.

Sin importar el lenguaje que uses, este debe ser traducido a instrucciones en lenguaje máquina, el cual es
el que comprende el procesador. Existen tres tipos de sistemas para traducir lenguajes de alto nivel:
Compiladores, Intérpretes y Compiladores Incrementales.

Los lenguajes de bajo nivel son una representación del lenguaje máquina de forma que las personas
puedan trabajar con el. El Lenguaje Ensamblador es un lenguaje de bajo nivel, cuando usas un lenguaje
de bajo nivel, tú tienes control directo sobre el procesador, otra ventaja es que los programas son más
pequeños y más rápidos.

Cuando trabajas con lenguajes de bajo nivel, el programa que traduce tu código a lenguaje máquina es
llamado Ensamblador.

1.3 Lenguaje Ensamblador.

Un Ensamblador lee un programa en lenguaje ensamblador de bajo nivel y genera un programa


equivalente en lenguaje máquina.

Para programar en ensamblador debes conocer algunos aspectos extra: debes comprender no sólo las
instrucciones que vas a usar sino también la arquitectura de la máquina, necesitas saber como se
almacenan los datos, como el procesador manipula los datos, etc.

Cuando programas en lenguajes de bajo nivel debes hacer más que solo describir la solución formal del
programa, debes dividir el programa en pasos pequeños y básicos. Estos pasos le dirán al procesador que
hacer, una instrucción a la vez.

3
1.4 El Programador de lenguaje Ensamblador.

Para programar en cualquier lenguaje de computación, necesitas buenas habilidades


organizacionales.
Fundamentalmente necesitas ser capaz de conceptuar un plan de acción basado en tiempo. Esto es, la
habilidad de tomar un problema y diseñar una solución que involucre ejecutar ciertos pasos en cierto
orden.
Para programar en lenguaje ensamblador necesitas más, debes tener una personalidad que disfrute atender
los detalles, y tener buenas habilidades aritméticas, en algunos casos debe tenerse mucha calma para
rastrear los problemas del código (conocidos como bugs)
La recompensa de aprender y practicar con el lenguaje ensamblador es desarrollar una idea sólida de
como opera una computadora, además de escribir programas rápidos y pequeños para ejecutar tareas que
no son practicas (demasiado lentas) o no son posibles o demasiado complejas en lenguajes de alto nivel.

Los mejores programadores de ensamblador son obsesivos, esto no es necesario pero ayuda.

1.5 Procesadores y Coprocesadores.

Ya mencione anteriormente que el procesador es la parte de la computadora que hace casi todo el
trabajo. Las computadoras personales IBM y compatibles usan el procesador de la familia Intel 86 o un
procesador compatible de otra compañía, (ver tabla 1-1).

Los procesadores de la tabla 1-1 son de propósito general. Existen también coprocesadores matemáticos
especiales que pueden ser usados para aumentar al procesador principal. Estos Coprocesadores ejecutan
operaciones matemáticas, incluyendo aritmética de punto flotante, y son extremadamente rápidos.

Los procesadores 486 y Pentium tiene un coprocesador matemático ínter construido, el cual es
funcionalmente equivalente a un 387. Así puedes considerar un 486 como un mejorado 386 + 387 (en la
tabla 1-1, el tamaño de las instrucciones no incluye las instrucciones matemáticas).

Los Coprocesadores matemáticos tienen su propio conjunto de instrucciones y requieren de una


programación especial, para referencia la tabla 1-3 muestra el tamaño de los varios conjuntos de
instrucciones matemáticas.

Tabla 1-2 Procesador Coprocesador Math


8086/8088 8087
186 8087
286 287
386 387
486 ---
Pentium ---

Tabla 1-3 Coprocesador Tamaño del conjunto de instrucciones


8087 77
287 74
387 80
486/487 80
Pentium 80

4
1.6 Programando para los Procesadores Intel 86

La Tabla 1-4 muestra el número de transistores en varios modelos del chip Intel 86. El número de
transistores de cada tipo de procesador se ha incrementado cuantitativamente, aunque es difícil definir lo
que significa un transistor, sin embargo, el número total de transistores es un buena indicación de la
complejidad de un chip.

Programar en lenguaje Ensamblador en 386, 486 o Pentium es casi lo mismo que programar en el 8088.
Con cada nuevo procesador que Intel hace, se asegura que los programas hechos para los procesadores
anteriores funcionen. Esto significa que la computadora más rápida y moderna puede correr los
programas escritos para la IBM PC original (la cual usaba el procesador 8086).

Tabla 1-4 Procesador Número aproximado Año de Introducción


de transistores
8086 29,000 1978
8088 29,000 1979
186 100,000 1982
286 134,000 1982
386 375,000 1985
486 1,200,000 1989
Pentium 3,100,000 1993

Una de las más grandes limitantes de los primeros procesadores 8086, 8088, 186, era su incapacidad de
usar más de 1 megabyte de memoria. El 286 resolvió ese problema operando en dos modos diferentes. En
modo real, una 286 actúa como una 8086, 8088, o 186. El modo Real da la compatibilidad para todos los
programas de DOS que dependen de la arquitectura original de la 8088.

En modo protegido, la 286 ofrece tres funciones importantes: Primero, la 286 pude utilizar 16 megabytes
de memoria. Segundo, la 286 puede usar almacenamiento externo de disco para proveer 1 gigabyte de
memoria virtual, la cual es memoria simulada. Finalmente, el 286 ofrece hardware multitareas, la
habilidad de cambiar rápidamente de un programa a otro.

Infortunadamente, pocas personas podían tomar ventaja de las avanzadas facilidades del 286 debido a que
casi todos usaban DOS y DOS está basado en el viejo 8088. Esto significa que para propósitos prácticos,
se usaba el modo real y teníamos efectivamente rápidos 8088´s.

El procesador 386 fue un importante cambio debido a su nueva facilidad añadida llamada modo virtual
86. Esto permitió al procesador correr más de un programa DOS a la vez, cada uno de los cuales pensaba
que corría en sus propia máquina 8086. Además, un 386 en modo protegido puede usar más de 4
gigabytes de memoria real y más de 64 terabytes de memoria virtual.

El 486 y Pentium combinan la funcionalidad de un 386 con un procesador matemático ínter construido
como también un controlador de cache (el cual controla la memoria especial de alta velocidad del
procesador). Así desde el punto de vista de programación, podemos considerar al 486 y al Pentium casi
iguales al 386.

5
Sin embargo todas las instrucciones extra que fueron adicionadas después del 8088 son para programar en
modo protegido. A menos que tú hagas software avanzado tal como un sistema operativo, un dispositivo
de driver o un compilador, no es factible que necesites esas instrucciones extra.
Así casi todos los programadores en lenguaje ensamblador, programan para cualquier procesador de Intel
igual que como programaban para el 8086 original. Esto es especialmente cierto si tú haces programas
para DOS.

1.7 Cuando usar el Lenguaje Ensamblador.

Te puedes preguntar: ¿Cuando debo usar el Lenguaje Ensamblador y cuando puedo usar el Lenguaje
de alto nivel? Los lenguajes de alto nivel son menos detallados y más conceptuales, y ellos deben ser tu
primera opción la mayoría de las veces. Comparados con los programas en lenguaje Ensamblador, los
programas de alto nivel son más fáciles de comprender y modificar. Tú sólo debes usar ensamblador
cuando sea necesario.

La razón principal del porque usar ensamblador es si quieres hacer algo que es imposible o muy poco
práctico con un lenguaje de alto nivel. Debido a que el ensamblador te da control completo sobre el
procesador, puedes hacer cualquier cosa que la máquina sea capaz de hacer. Los lenguajes de alto nivel
no te dan este tipo de control.

La segunda razón del porque usar ensamblador es acelerar un programa lento. Los programas hechos con
lenguajes de alto nivel se ejecutan más lentamente que sus equivalentes en ensamblador. El que tanto más
rápido se ejecutan los programas depende de que tan bueno sea el compilador para generar código en
lenguaje máquina eficiente.

En ciertas situaciones, necesitas acelerar cierta parte de un programa de alto nivel que es demasiado lento,
en este caso puedes analizar el programa para encontrar los puntos lentos. Como regla general, los
programas gastan el 90 % de su tiempo ejecutando el 10 % de las instrucciones. Usualmente estas
instrucciones son las que necesitan ser mejoradas. Si es posible manteniendo la mayoría del programa
como alto nivel, rescribiendo partes seleccionadas en Lenguaje Ensamblador, y uniendo todas las piezas
juntas.

La tercera razón para usar Ensamblador es para diseñar programas tan pequeños como sea posible. Ahora
la memoria de la computadora es más barata, y ahorrar espacio de memoria no es muy importante. Sin
embargo, cuando tienes que “meter” un programa en espacios de memoria pequeños, usar el ensamblador
es la mejor salida, puedes diseñar un programa que use menos espacio que uno generado por un
compilador.

La última razón para escoger Lenguaje Ensamblador es la más importante: por diversión. Mucha gente
prefiere lenguaje Ensamblador a los lenguajes de alto nivel debido a que les divierte trabajar a nivel del
procesador.

1.8 El Sistema Operativo.

El sistema operativo es el control maestro de programas que corre la computadora. Como


programador en Lenguaje Ensamblador, puedes usar el sistema operativo de dos maneras:

☯ Primero, sin importar que computadora uses, usas comandos que ejecuta el sistema
operativo: comandos para copiar archivos, iniciar un programa, listar un directorio,
etc.
6
☯ Segundo, tú llamas al sistema operativo dentro de tus programas para hacer ciertas
tareas; por ejemplo: entrada/salida, acceso al reloj, trabajar con archivos y directorios,
etc. Estas tareas pueden ser muy complicadas para hacerlas por ti mismo. De hecho,
puedes llamar al sistema operativo para que haga el trabajo por ti.

La mayoría de nosotros usamos el sistema operativo DOS (Disk Operating System), si tú estas
programando para Microsoft Windows, también puedes trabajar bajo DOS.

1.10 Desarrollo de un Programa en Ensamblador

Una vez que diseñas un programa en Lenguaje Ensamblador, hay varios pasos que debes hacer antes
de poderlo ejecutar.
Primero debes meter el programa a la computadora. Para hacer esto, puedes usar un programa llamado
editor y salvar el programa a disco. Una vez que el programa esta almacenado en un archivo, usa un
Ensamblador para ejecutar la traducción a lenguaje máquina. Sin embargo un programa en lenguaje
máquina no puede ser ejecutado directamente; este debe ser procesado por un enlazador. El enlazador
crea una forma de programa que está lista para ejecutarse. También permite que hagas programas
separados que serán unidos juntos para ejecutarse como un todo (de ahí el nombre “enlazador”).

Bajo DOS existen ciertas convenciones para nombrar a los archivos:

ASM Archivos de código fuente.


OBJ Archivos producidos por la conversión de código fuente a código máquina.
EXE Archivos generados por el enlazamiento de archivos OBJ.

Por ejemplo si creas un programa fuente llamado TEST.ASM, el ensamblador lee el archivo, generando
un modulo objeto llamado TEST.OBJ y este a su vez puede ser leído por el enlazador para generar el
programa ejecutable o el load module llamado TEST.EXE.

Cuando un programa esta totalmente terminado, puede ser probado bajo los auspicios de un debugger. Un
debugger es un programa que provee un ambiente para probar load modules. Usando un debugger, puedes
desplegar un programa, ejecutarlo una instrucción a la vez, ver los resultados de cada instrucción,
desplegar áreas de la memoria, etc.

1.11 Que Software necesitas.

Sistema DOS en cualquier versión o Windows versión 3.x o 95.


Programa editor de textos. De preferencia el EDIT de DOS.
Programa MASM ó TASM (MacroAssembler ó TurboAssembler).
Programa LINK ó TLINK (enlazadores de MicroSoft ó Turbo).
Programa DEBUG.

Nota: No utilices editores de textos sofisticados (a menos que sea en modo texto), para teclear tus
programas ya que estos adicionan información extra a los archivos que ocasionan problemas al ensamblar
el código fuente.

7
1.12 Que Conocimientos necesitas antes de empezar.

Hay ciertos prerrequisitos para programar en lenguaje Ensamblador:

Primero, debes conocer como utilizar el sistema operativo, no necesitas ser experto pero debes estar
familiarizado con las funciones básicas, tales como manipulación de archivos y uso de discos.

Segundo, debes conocer como usar tú editor.

Tercero, debes tener alguna experiencia de programación, no debes ser experto, pero debes tener alguna
idea de como usar la computadora para resolver problemas. Si no has programado antes mejor empieza
con un programa de alto nivel como Pascal ó C.

Finalmente, no debes escribir programas en Ensamblador a menos que comprendas la arquitectura y la


memoria de la computadora y la aritmética hexadecimal, que se verán en los siguientes capítulos.

{_____________o___________}

8
CAPITULO 2 SISTEMAS NUMERICOS

Nuevos Términos.

Bit. Abreviación de “dígito binario”. La unidad fundamental de almacenamiento de la memoria de la


computadora, un bit tiene uno de dos valores 1 ó 0.

Byte. Una unidad de memoria que contiene 8 bits.

Word. Una unidad de memoria de contiene 16 bits (2 bytes).

Doubleword. Una unidad de memoria que contiene 32 bits (4 bytes, 2 words).

Quadword. Una unidad de memoria que contiene 64 bits (8 bytes, 4 words).

Paragraph (Párrafo). Una unidad de memoria que contiene 128 bits (16 bytes, 8 words).

ASCII. Abreviación de “Código Estándar Americano para Intercambio de Información”. Un código


estándar, usado para almacenar caracteres de texto en la memoria, en la cual cada carácter está
representado por un patrón único de 8 bits.

Sistema Decimal ó Base 10. Nuestro sistema aritmético de todos los días, basado en 10 dígitos del 0 al 9.

Sistema Binario ó Base 2. Un sistema aritmético usado por las computadoras, cuya base son los dígitos
0, 1.

Sistema Hexadecimal (Hex) ó Base 16. Un sistema aritmético usado con las computadoras, que está
basado en 16 dígitos. Para representar 16 dígitos, el sistema hexadecimal usa 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A,
B, C, D, E, F.

Kilobyte ó K byte ó Kb. Una unidad de memoria que contiene 1024 bytes (210).

Megabyte ó M byte ó MB. Una unidad de memoria que contiene 1,048,576 bytes (220). 1 Mb contiene
1024 Kilobytes.

2.1 Bits y Bytes.

Para propósitos de programar en lenguaje ensamblador, piense en la memoria de la computadora


como en una secuencia larga de números, 0´s y 1´s. Por ejemplo:

100100110110010111001001000100010011101011...
9
Cada 0 y 1 es llamado un bit, las computadoras almacenan información como una secuencia larga de bits,
cada grupo de 8 bits es llamado un byte. Existen otras agrupaciones, como se puede ver en la tabla 2-1.

Tabla 2-1 Número de bits Nombre


8 Byte
16 Word (2 bytes)
32 Doubleword (4 Bytes)
64 Quadword (8 bytes)
128 Paragraph (16 bytes)

2.2 Como son almacenados los caracteres.

Si una computadora sólo puede almacenar secuencias largas de 0´s y 1´s ¿Como puede almacenar
información diferente a esa? Existen varias maneras. El texto, el cual es información consistente de
caracteres, es representado por un código.
Como sabemos, cada byte contiene 8 bits y cada uno de estos bits puede ser un 0 ó un 1; entonces hay 28
ó 256 posibilidades diferentes. Esto es, existen sólo 256 patrones diferentes.
Estos 256 patrones son usados para formar un código. Un patrón es un símbolo. Por ejemplo la letra “A”
es 01000001; el patrón para la letra “Q” es 01010001.
Este conjunto de patrones, con todos los caracteres que lo forman es llamado “American Standard Code
for Information Interchange, o ASCII.
Así, si quieres decodificar una cadena larga de bits, hay que dividirlos en grupos de 8 para ver que
patrones forman.

2.3 El Sistema Binario.

Si la computadora sólo almacena 0´s y 1´s, tiene sentido que la aritmética que la computadora usa
este basado en estos números. Nosotros utilizamos normalmente el sistema decimal sin embargo la
computadora utiliza el sistema binario o base 2.
Como en el sistema decimal nosotros podemos representar un cadena de números posicionales donde
cada número representa la multiplicación de ese dígito con una potencia de 10 de derecha a izquierda. En
el sistema binario es exactamente lo mismo sólo que se utiliza una potencia de 2.
Cuando usamos más de un sistema numérico debemos indicar que sistema estamos usando cada vez que
escribimos un número, por convención dentro del ensamblador se utiliza una “B” al final de cada número
binario, ejemplo:

1000 (mil en decimal)


1000B (8 en binario)

La Tabla 2-2 muestra los números decimales del 0 al 15. A un lado de cada número decimal esta su
equivalente en binario:

Tabla 2-2 Decimal Binario


0 0
10
1 1
2 10
3 11
4 100
5 101
6 110
7 111
8 1000
9 1001
10 1010
11 1011
12 1100
13 1101
14 1110
15 1111

2.4 El Sistema Hexadecimal.

La principal ventaja del sistema binario es que nos permite trabajar con números exactamente como
son almacenados, es decir, como 0´s y 1´s. Sin embargo hay un inconveniente con el binario, que lo hace
muy difícil de utilizar. La razón es que es mucho más largo que sus números decimales equivalentes; y
además, las cadenas de 0´s y 1´s son muy confusas.
Por ejemplo, puedes comprender inmediatamente la ecuación:

125 + 75 = 200

Sin embargo en binario, quedaría escrito como sigue:

1111101B + 1001011B = 11001000B

Obviamente necesitamos un sistema que sea compacto (como el decimal) y que pueda ser usado para
trabajar con los valores de la memoria (como el binario). El sistema hexadecimal, o base 16 tiene esas
características.
Hexadecimal, o Hex, es un sistema que usa 16 dígitos. Debido a que sólo tenemos diez símbolos para
dígitos (0 al 9), usaremos las letras A, B, C, D, E, y F como los otros seis dígitos. En hex, la “A” es el
diez, “B” el once, etc. La tabla 2-4 muestra los equivalentes en decimal, binario y hexadecimal.
Cuando escribimos números en hexadecimal, ponemos una “H” al final para reconocerlos, por ejemplo
“12H”.

La tabla 2-3 muestra algunas equivalencias entre decimales y hexadecimales.

Tabla 2-3 Decimal Hex


10 A
15 F
100 64
500 1F4
2730 AAA
65536 10000

11
Tabla 2-4 Decimal Binario Hex
0 0 0
1 1 1
2 10 2
3 11 3
4 100 4
5 101 5
6 110 6
7 111 7
8 1000 8
9 1001 9
10 1010 A
11 1011 B
12 1100 C
13 1101 D
14 1110 E
15 1111 F

2.5 Medidas del tamaño de la Memoria.

Las computadoras manejan datos en bytes, no en bits. Por eso, cuando describes el tamaño de la
memoria, dices cuantos bytes contiene. Debido a que la computadora tiene miles o millones de bytes, se
usan términos como kilobytes ó megabytes. El prefijo kilo y mega esta tomado del sistema métrico que
significa mil o un millón. Sin embargo mil o un millón no son números redondos en el sistema binario.
De hecho, usamos 1024 (210) y 1048576 (220).

De esta manera 64 kilobytes no significa 64000 bytes; sino 64x1024 (65536) bytes. El prefijo kilo se
abrevia K y el prefijo Mega se abrevia M. Una relación que debes recordar siempre es que 1024 K bytes
son 1 Mega.

2.6 Conversión entre Hexadecimal y Binario.

El sistema hexadecimal es importante debido a que proporciona un modo compacto de representar


largas cadenas de bits. En la tabla 2-5 se encuentran los primeros 16 números hexadecimales y sus
equivalentes en binario, note que cada número binario esta formado por sólo 4 dígitos, adicionando ceros
cuando sea necesario (Por supuesto adicionar ceros a la izquierda de un número no altera su valor).

Podemos representar cualquier secuencia de dígitos binarios por una secuencia más pequeña de dígitos
hex, por ejemplo 00001111B es igual a 0FH.

El siguiente es un ejemplo que convierte el número binario 10011010110101101B a hex. Primero


dividimos la cadena de derecha a izquierda en grupos de 4 bits:

1 0011 0101 1010 1101

Después adicionamos 3 ceros al grupo de más a la izquierda para tener 4 bits.

12
0001 0011 0101 1010 1101

Tabla 2-5 Hex Binario


0 0000
1 0001
2 0010
3 0011
4 0100
5 0101
6 0110
7 0111
8 1000
9 1001
A 1010
B 1011
C 1100
D 1101
E 1110
F 1111

Ahora usemos la tabla 2-5 para encontrar el dígito equivalente en hex para cada grupo:

0001 0011 0101 1010 1101


1 3 5 A D

Así vemos que 10011010110101101B es 135AD en hex.

Similarmente si queremos convertir un número hexadecimal a binario todo lo que necesitamos es


reemplazar cada dígito hexadecimal por su equivalente de 4 bits. Por ejemplo para convertir 72AD2EH a
binario. Primero escribimos cada dígito separado.

7 2 A D 2 E

Luego usamos la tabla y escribimos el equivalente número de 4 bits debajo del dígito en hex.

7 2 A D 2 E
0111 0010 1010 1101 0010 1110

Ahora, ponemos los bits juntos para formar el número binario, entonces 72AD2EH es
11100101010110100101110B en binario.

Mencione antes que puedes considerar a la memoria de la computadora como una larga secuencia de bits.
Sin embargo como puedes ver, las cadenas de bits se pueden convertir fácilmente a dígitos en
hexadecimal (y viceversa). Entonces es más correcto decir que la memoria de la computadora puede ser
considerada como una secuencia de dígitos hex.

2.7 Conversión de Hexadecimal a Decimal.

Debes aprender a convertir entre decimal y hex y de hex a decimal, porque la gente maneja decimal
pero la computadora almacena la información en hex.
13
Para convertir de hex a decimal, necesitas recordar la posición de cada dígito. Por ejemplo, considera el
número decimal 50786. Este tiene el valor:

5x10000 + 0x1000 + 7x100 + 8x10 + 6 ó

5x104 + 0x103 + 7x102 + 8x101 + 6x100

Si quieres encontrar el valor decimal de un número en hexadecimal, puedes hacer lo mismo que acabas de
ver pero usando potencias de 16, por ejemplo el número AD65H expresando en potencias de 16 es:

Ax163+ Dx162 + 6x161 + 5x160

Recordando los valores de los dígitos en hexadecimal expresados con letras podemos sustituir lo anterior
por:

10x163 + 13x162 + 6x16 + 5 = 10x4096 + 13x256 + 6x16 + 5 = 44389

En general, el procedimiento para convertir de hex a decimal es como sigue:

1. Separe cada dígito hexadecimal y coloque de derecha a izquierda la potencia de 16 adecuada


empezando desde cero.

2. Sustituya los dígitos A, B, C, D, E, F por los valores en decimal apropiados.

3. Haga las operaciones correspondientes.

2.8 Conversión de Decimal a Hex.

Para convertir de decimal a hexadecimal, debes dividir el número decimal entre 16 repetidamente.
Los residuos te darán los dígitos hexadecimales, leyéndolos de derecha a izquierda. Por ejemplo convierta
14855 de decimal a hex:

Cociente Residuo
14855 / 16 928 7
928 / 16 58 0
58 / 16 3 10
3 / 16 0 3

Ahora se escriben todos los residuos de abajo hacia arriba de izquierda a derecha, sustituyendo los
valores de 10, 11, 12..etc., por A, B, C..etc.

14855 = 3A07H

Este es el procedimiento general para convertir de decimal a hex:

1. Divida el número decimal entre 16 repetidamente hasta que el cociente sea 0.

2. Para obtener los dígitos en hexadecimal, escriba abajo los residuos, de derecha a izquierda.

3. Si cualquiera de los residuos es 10, 11, 12, 13, 14 ó15, cámbielos por A, B, C, D, E ó F
14
respectivamente.

2.9 Conversión de Binario a Decimal.

Existen dos maneras de convertir un número binario en decimal. La primera es convertir el número
binario a hex y luego el número en hex a decimal. Como ejemplo, convirtamos 10110011B a decimal.
Primero convertiremos a hex separando los bits en grupos de 4:

1011 0011

Bajo cada grupo, escribimos el equivalente número hexadecimal:

1011 0011
B 3

Entonces, 10110011B es igual a B3H en hex. Ahora expresemos B3H como la suma de los dígitos
multiplicándolo por la potencia de 16 apropiada (recuerde cambiar la B por 11):

11x16 + 3

Evaluando lo anterior obtenemos el resultado en decimal, 179.

El segundo método para convertir de binario a decimal es hacer lo anterior directamente como lo hicimos
para el hexadecimal. Expresemos el número binario como la suma de los dígitos multiplicando por las
potencias de 2 apropiadas. Así para evaluar la expresión 10110011B tenemos:

1x27 + 0x26 + 1x25 + 1x24 + 0x23 + 0x22 + 1x21 + 1x20

o lo que es lo mismo:

1x128 +0x64 + 1x32 + 1x16 + 0x8 + 0x4 + 1x2 + 1 = 179.

2.10 Conversión de Decimal a Binario.

Existen dos formas de convertir de decimal a binario. Primero, conviertes el número decimal a hex y
después a binario. Por ejemplo suponga que quiere convertir 23406 a binario. Para convertirlo a hex,
dividimos repetidamente entre 16:

Cociente Residuo
23406 / 16 1462 14
1462 / 16 91 6
91 / 16 5 11
5 / 16 0 5 = 5B6EH

15
Para convertir el número hexadecimal 5B6EH separamos cada dígito hex y encontramos su equivalente
en binario:

5 B 6 E
0101 1011 0110 1110 = 101101101101110B

La segunda forma para convertir de decimal a binario es usar el mismo método de división para convertir
a hexadecimal, sólo debemos dividir repetidamente entre 2 en lugar de entre 16. Con este método
los residuos son escritos de abajo hacia arriba y de izquierda a derecha, dándonos el número binario
directo:

Cociente Residuo
23406 / 2 11703 0
11703 / 2 5851 1
5851 / 2 2925 1
2925 / 2 1462 1
1462 / 2 731 0
731 / 2 365 1
365 / 2 182 1
182 / 2 91 0
91 / 2 45 1
45 / 2 22 1
22 / 2 11 0
11 / 2 5 1
5/2 2 1
2/2 1 0
1/2 0 1 = 101101101101110B

2.11 Sumando en Hexadecimal.

Algunas veces necesitaras sumar o restar dos números hexadecimales. Por supuesto puedes convertir
ambos números de hex a decimal calcular el resultado y regresar la respuesta a hex. La figura 2-6
contiene la tabla de adición hex.

Tratemos de sumar 28A70H más 90B6H como ejemplo. Escribimos los números como en una suma
normal:

28A70
+ 90B6

Sumamos cada columna de derecha a izquierda. Primero 0H + 6H = 6H

28A70
+ 90B6
6

A continuación, 7H + BH es igual a 12H. Escribimos 2 bajo la segunda columna y tenemos un acarreo de


1.

16
1
28A70
+ 90B6
26

Ahora, AH + 0H + 1H es igual a BH. Escribimos B bajo la tercera columna.

28A70
+ 90B6
B26

Después, 8H + 9H igual a 11H. Escribimos un 1 y acarreamos un 1.

1
28A70
+ 90B6
1B26

Finalmente, 2H + 1H es igual a 3.

28A70
+ 90B6
31B26

Tabla 2-6 Adición en Hex

0 1 2 3 4 5 6 7 8 9 A B C D E F
0 0 1 2 3 4 5 6 7 8 9 A B C D E F
1 1 2 3 4 5 6 7 8 9 A B C D E F 10
2 2 3 4 5 6 7 8 9 A B C D E F 10 11
3 3 4 5 6 7 8 9 A B C D E F 10 11 12
4 4 5 6 7 8 9 A B C D E F 10 11 12 13
5 5 6 7 8 9 A B C D E F 10 11 12 13 14
6 6 7 8 9 A B C D E F 10 11 12 13 14 15
7 7 8 9 A B C D E F 10 11 12 13 14 15 16
8 8 9 A B C D E F 10 11 12 13 14 15 16 17
9 9 A B C D E F 10 11 12 13 14 15 16 17 18
A A B C D E F 10 11 12 13 14 15 16 17 18 19
B B C D E F 10 11 12 13 14 15 16 17 18 19 1A
C C D E F 10 11 12 13 14 15 16 17 18 19 1A 1B
D D E F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C
E E F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D
F F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E

2.12 Substracción en Hex.

Para restar en hexadecimal necesitas la habilidad de encontrar la diferencia entre dos números. La
figura 2-7 muestra la tabla de substracción en hex.

17
Los números a la izquierda representan el número más grande. Los números de arriba representan los
números más pequeños. Para hacer una resta, primero busque el renglón y luego la columna.
Por ejemplo cuanto será FH - 8H = 7H

Para restar números grandes, use la misma metodología que para restar decimales. Por ejemplo reste
DB4H - 2A1H:

DB4
- 2A1
B13

Aquí hay un ejemplo más complicado; suponga que quiere restar 74AH - 2EFH:

74A
- 2EF

Para empezar debe restar FH de AH -pero FH es más grande que AH. Proceda justo como lo haría
normalmente. Adicione 1 a AH (borrow) para convertirlo en 1AH. Ahora calcule 1AH - FH lo cual es
igual a BH

74A
- 2EF
B

Ahora, necesita calcular 4H - EH - 1H (el borrow). De nuevo existe un préstamo; esta vez necesita
cambiar 4H a 14H. Calcule 14H - EH - 1H. El resultado es 5H bajo la segunda columna.

74A
- 2EF
5B

Finalmente, calcule 7H - 2H - 1H (el borrow). La respuesta es 4H.

74A
- 2EF
45B

18
Tabla 2-6 Tabla de substracción en hex

0 1 2 3 4 5 6 7 8 9 A B C D E F
0 0
1 1 0
2 2 1 0
3 3 2 1 0
4 4 3 2 1 0
5 5 4 3 2 1 0
6 6 5 4 3 2 1 0
7 7 6 5 4 3 2 1 0
8 8 7 6 5 4 3 2 1 0
9 9 8 7 6 5 4 3 2 1 0
A A 9 8 7 6 5 4 3 2 1 0
B B A 9 8 7 6 5 4 3 2 1 0
C C B A 9 8 7 6 5 4 3 2 1 0
D D C B A 9 8 7 6 5 4 3 2 1 0
E E D C B A 9 8 7 6 5 4 3 2 1 0
F F E D C B A 9 8 7 6 5 4 3 2 1 0
10 10 F E D C B A 9 8 7 6 5 4 3 2 1
11 11 10 F E D C B A 9 8 7 6 5 4 3 2
12 12 11 10 F E D C B A 9 8 7 6 5 4 3
13 13 12 11 10 F E D C B A 9 8 7 6 5 4
14 14 13 12 11 10 F E D C B A 9 8 7 6 5
15 15 14 13 12 11 10 F E D C B A 9 8 7 6
16 16 15 14 13 12 11 10 F E D C B A 9 8 7
17 17 16 15 14 13 12 11 10 F E D C B A 9 8
18 18 17 16 15 14 13 12 11 10 F E D C B A 9
19 19 18 17 16 15 14 13 12 11 10 F E D C B A
1A 1A 19 18 17 16 15 14 13 12 11 10 F E D C B
1B 1B 1A 19 18 17 16 15 14 13 12 11 10 F E D C
1C 1C 1B 1A 19 18 17 16 15 14 13 12 11 10 F E D
1D 1D 1C 1B 1A 19 18 17 16 15 14 13 12 11 10 F E
1E 1E 1D 1C 1B 1A 19 18 17 16 15 14 13 12 11 10 F
1F 1F 1E 1D 1C 1B 1A 19 18 17 16 15 14 13 12 11 10

{____________o___________}

19
CAPITULO 3 LA ORGANIZACION DE LA MEMORIA

Como programador de lenguaje ensamblador, debes tener una idea firme de como está organizada la
memoria de la computadora. En este capitulo aprenderás acerca de las unidades fundamentales de la
memoria y como se almacenan. Aprenderás también dos conceptos importantes del lenguaje
ensamblador, los Registros y la Pila.

Nuevos Términos.

Dirección. El número asignado a un byte, que indica su posición en la memoria; el primer byte tiene una
dirección 0.

Parte baja de la Memoria. En la memoria, son aquellos bytes que están cercanos al byte 0.

Parte alta de la Memoria. En la memoria, son aquellos bytes cercanos al byte con la dirección más alta.

Bits más significativos ó Bits de más alto orden. Los bits de más a la izquierda en un byte.

Bits menos significativos ó Bits de menor orden. Los bits más a la derecha en un byte.

Limite. Una dirección que es un múltiplo de un número especifico.

Limite Word. Una dirección que es un múltiplo de 2.

Alineado (a un limite). Describe a un byte cuya dirección es un límite específico.

Limite Doubleword. Una dirección que es múltiplo de 4.

Limite Quadword. Una dirección que es múltiplo de 8.

Limite Paragraph. Una dirección que es un múltiplo de 16.

Memoria Primaria. Memoria con que el procesador puede trabajar directamente.

Memoria Secundaria. Memoria con que el procesador no puede trabajar directamente. La memoria
secundaria reside en los discos.

Read Only Memory ( ROM ). Memoria primaria que puede ser leída pero no cambiada.

Random Access Memory ( RAM ). Memoria primaria, también llamada de lectura-escritura, que puede
ser leída y modificada.

20
Registro. Uno de los 14 words de memoria lectura-escritura ínter construida en el procesador.

Acumulador. Otro nombre del registro AX.

Registro Base. Otros nombres de los registros BX ó BP.

Registro Contador. Otro nombre del registro CX.

Registro de Datos. Otro nombre del registro DX.


Cargar ( Load ). Copiar un dato a un registro.

Almacenar ( Store ). Copiar datos del contenido de un registro.

Pila ( Stack ). Una estructura de datos que te permite almacenar y recuperar datos de manera last-in, first-
out, (el último que entra es el primero en salir).

Vacía ( Empty ). Describe una pila que no contiene información.

Meter ( Push ). Almacenar un dato en la pila.

Sacar ( Pop ). Recuperar un dato de la pila.

Tope de la pila ( Top ). La locación en la pila que contiene el último campo del dato que fue metido.

Procedimiento, Función o Subrutina. Un modulo auto contenido, también llamado una función o una
subrutina, que es una parte de un programa grande.

Llamada a un procedimiento. Empezar a ejecutar un procedimiento.

Regreso de un procedimiento. Cuando un procedimiento se termina, para continuar ejecutando el


programa que lo llamo.

Dirección de regreso. La locación a que un procedimiento regresa cuando este termina.

3.1 Direcciones de Memoria.

La memoria de la computadora esta organizada en bytes. Todos los bytes están numerados
empezando desde 0. El número de un byte es llamado su dirección. Cuando el procesador necesita leer un
byte, este manda la dirección de la memoria, la memoria regresa entonces la información en ese byte al
procesador. Entonces cuando el procesador necesita escribir un byte, este manda dos señales a la
memoria: la dirección del byte y la información a ser escrita. La memoria copia la información al byte
específico.

Los bytes cercanos al byte 0 se conocen como la parte baja de la memoria. Los bytes cercanos a la más
alta dirección son llamados parte alta de la memoria. Piensa que los bytes están acomodados en orden,
uno hasta arriba de los otros, con el byte de la dirección 0 hasta abajo.

Te puedes referir a un byte con su dirección. Cuando te refieras a una palabra (word), doble palabra
(doubleword), palabra cuádruple (quadword), o párrafo (paragraph), puedes usar la dirección de su primer
byte. Por ejemplo, si dices que una palabra esta almacenada en las locaciones 10926H y 10927H, te
21
puedes referir a ellas como la doble palabra en 10926H. Se asume que el segundo byte está en la
dirección de más arriba.

3.2 Como son almacenadas las Palabras ( words ).

Los viejos procesadores podían transferir de 1 a 2 bytes a la vez, pero los 386 y 486 pueden
transferir más de 4 bytes a la vez.
Cuando los bytes son escritos a la memoria, estos son almacenados de manera recta. Sin embargo, cuando
las palabras son escritas a la memoria, los dos bytes de cada palabra son almacenados en orden inverso.
Aquí hay un ejemplo digamos que tu programa escribe una palabra que contiene 1234H en la dirección de
la memoria 500H. Los dos bytes de la palabra son almacenados en 500H y 501H. Podrías esperar que la
información fuera almacenada como:

4FEH 4FFH 500H 501H 502H 503H

Sin embargo, están actualmente almacenadas como:

4FEH 4FFH 500H 501H 502H 503H

De los detalles se encargan automáticamente la memoria y el procesador. Cuando el procesador necesita


una palabra, sabe cambiar el orden de los bytes que recibe.

La única vez que tienes que preocuparte acerca del orden de los bytes es cuando usas un debugger para
desplegar un área de la memoria. En tales casos debes mantener en mente que las palabras están
almacenadas con los bytes en orden inverso.

Si los datos están almacenados como bytes esto si están en el orden normal.

3.3 Como están almacenados los Bits.

Un byte es la unidad más pequeña que puede ser almacenada o recuperada de la memoria. El único
modo de trabajar con un bit es acceder al byte en el cual el bit esta contenido. Para hacer esto hay
instrucciones especiales que te permiten examinar y cambiar bits particulares.

En un byte, el orden de los bits es especialmente importante, por esta razón tenemos un modo estándar de
referirnos a cada bit, los bits están numerados desde el 0 al 7, iniciando por la derecha. La figura 3-1
muestra un byte que contiene el código ASCII de la letra “D”.

Figura 3-1

7 6 5 4 3 2 1 0
22
Los bits de más a la izquierda son llamados los más significativos y los de más a la derecha son los menos
significativos. Cuando almacenas información todos los bits son importantes.

3.4 Limites.

En el capitulo 2 explique como los bits se agrupan en bytes, palabras, palabras dobles, palabras
cuádruple y párrafos. Como sabemos la memoria de la computadora consiste de muchos bytes. La
dirección del primer byte es 0H, la del segundo 1H y así. Algunas veces los datos necesitan ser
almacenados de manera que empiecen en algún tipo de dirección particular, llamado un limite
(boundary). Un límite es una dirección que es el múltiplo de un número específico.

Todas las direcciones que tiene un límite de 2 se dice que tiene límite de una palabra. Dicho de otra
forma, empezando con la dirección 0H, cada segundo byte en la memoria tiene como limite una palabra.
Si el primer byte de algún dato esta en una dirección limite hexadecimal, podemos decir que el dato esta
alineado con el limite de la palabra (esto es, el dato inicia en el limite de la palabra). Por ejemplo, el dato
que inicia en la locación 108A6H esta alineado con el limite de una palabra (acaba en par); los datos que
inician en 108A7H no están alineados con el limite palabra.

Similarmente, cada cuatro bytes, empezando desde 0H, decimos que es el límite de una palabra doble.
Los límites de las palabras dobles son direcciones que finalizan en 0H, 4H, 8H, o CH. Para continuar con
la analogía los límites de palabras cuádruples son direcciones que finalizan en 0H o 8H; los límites de un
párrafo terminan siempre en 0H. Ocasionalmente cierto tipo de datos deben de estar alineados a un límite
particular. Algunas veces el ensamblador lo hace automáticamente pero otras tendrás que hacerlo tú
mismo.
Como referencia, la tabla 3-2 tiene los límites de las direcciones de memoria.

Tabla 3-2 Límites de alineación

Limite Definición La dirección finaliza en...


Word Cada 2 bytes 0H, 2H, 4H, 6H, 8H, AH, CH, ó EH
Doubleword Cada 4 bytes 0H, 4H, 8H, CH
QuadwordCada 8 bytes 0H, ó 8H
Paragraph Cada 16 bytes 0H

3.5 Memoria Primaria y Secundaria.

Generalmente cada computadora tiene dos tipos de memorias: memoria primaria y secundaria. Los
procesadores pueden trabajar directamente sólo con la memoria primaria, la cual es la memoria que
es
almacenada en pequeños chips incluidos en los circuitos de la tarjeta en la computadora.

Una computadora personal utiliza dos tipos de memoria chip. Read-Only Memory (ROM), que puede ser
leída pero no puede ser cambiada. La ROM es usada para mantener datos importantes que están definidos
para ella. Estos datos no desaparecen cuando la computadora se apaga.

23
Los chips Read-Write Memory, proveen memoria que puede ser leída y cambiada, estos chips son
usualmente llamados random-access memory (RAM).

La RAM es usada para mantener en la memoria primaria lo que usa la computadora. Cuando usas un
programa de computadora, debes preparar ciertas áreas de la memoria para almacenar datos. Cuando
corres un programa, las instrucciones con sus propias áreas de datos deben ser cargadas dentro de la
RAM por el sistema operativo.

La memoria secundaria reside en los discos. El procesador no puede acceder directamente a este tipo de
memoria. Antes de que los datos puedan ser usados, estos deben ser leídos a la memoria primaria. Cuando
trabajes con datos en discos debes leer la información a un área de la memoria primaria que tú apartas
cuando diseñas tu programa. Similarmente, el procesador no puede escribir directamente a los discos,
debes transferir los datos de un área de la memoria.

Por conveniencia, cuando use el término “memoria” significará memoria primaria.

3.6 Registros.

Debe parecer un poco lento el que el procesador trabaje sólo con la memoria primaria, cada byte a
ser usado debe ser transferido y regresado. Sin embargo hay una pequeña cantidad de memoria construida
dentro del procesador. Esta memoria consiste de 14 palabras de memoria read-write. Cada una de estas
palabras es llamada un registro y cada registro tiene su propio nombre y propósito.

La tabla 3-3 es una lista de los registros y sus abreviaciones. Como podemos ver los registros caen en tres
categorías: Registros generales, registros de desplazamiento y registros de segmento.

Tabla 3-3 Los registros.

Nombre Abreviación Categoría


Acumulador AX (AH, AL) Registro General
Registro Base BX (BH, BL) Registro General
Registro Contador CX (CH, CL) Registro General
Registro de Datos DX (DH, DL) Registro General
Apuntador Base BP Registro de Desplazamiento
Apuntador de Instrucción IP Registro de Desplazamiento
Apuntador de Pila SP Registro de Desplazamiento
Índice Destino DI Registro de Desplazamiento
Índice Fuente SI Registro de Desplazamiento
Registro de Segmento de Datos DS Registro de Segmento
Registro de Segmento Extra ES Registro de Segmento
Registro de Segmento de Pila SS Registro de Segmento
Registro de Segmento Código CS Registro de Segmento
Registro de Banderas (ninguno) (ninguno)

3.7 Lo Registros Generales.


24
Existen cuatro registro generales: AX, BX, CX y DX. Estos registros ofrecen un almacenamiento
temporal muy conveniente para cualquier tipo de información. Ellos son usados especialmente para
aritmética, debido a que los registros están construidos dentro del procesador, las instrucciones que los
usan se ejecutan de manera más rápida que las instrucciones que utilizan la memoria regular.
Puedes usar estos registros como desees. Sin embargo AX, BX y CX también tienen propósito especiales.

AX es el registro principal usado para instrucciones aritméticas (también puedes usar los otros registros
generales). AX puede ser usado para guardar el resultado de un cálculo. Por esta razón algunas veces es
llamado acumulador.

BX (también BP) algunas veces es llamado registro base, debido a que pude ser usado para mantener una
dirección base.

CX es usado con ciertas instrucciones para ejecutar operaciones repetitivas. En tales casos, CX debe
contener el número de veces que quieres repetir una operación. Así CX, es considerado como el registro
contador.

DX es llamado registro de datos porque es usado para mantener datos para propósitos generales.

Cuando copias información al registro, Cargas datos dentro del registro. Así se pueden almacenar datos.
Todos estos registros contienen 2 bytes (16 bits), puedes cargar y almacenar 2 bytes a la vez. Sin
embargo, a veces es necesario cargar y almacenar sólo 1 byte a la vez. El procesador permite hacer esto
reconociendo nombres alternativos para estos registros. Estos nombres alternativos se refieren a una
mitad del registro.

Los nombres alternativos para el registro AX, son AH y AL. La “H” y “L” significan “High” y “Low”.
AH se refiere a la parte alta del registro AX; AL se refiere a la parte baja de ese registro. Puedes acceder
al registro completo usando AX, o la parte izquierda o derecha usando AH o AL.

De hecho, si decimos que AX contiene 1234H, AH contiene 12H, y AL contiene 34H. Si cargas 00H a
AH, AX contendrá 0034H. Como se muestra en la siguiente figura:

AX AX

AH AL AH AL

Puedes tener el mismo acceso para los registros BX, CX y DX.

3.8 La Pila.

Una pila es una estructura de datos que te permite almacenar y recuperar información de manera
last-in, first-out. Casi todos los programas en ensamblador utilizan una pila, así que al principio de cada
programa debe tener algunas instrucciones para dar de alta a la pila.

Cuando tu programa inicia, la pila no tiene datos en ella, Nosotros podemos decir que la pila esta vacía.
Cuando usas la pila para almacenar un dato se dice que haces un push a la pila. Cuando recuperas
este dato, haces un pop fuera de la pila. Existen instrucciones especiales para hacerles push y pop a los
datos.
25
Cuando hace un pop de dato, este es removido de la pila. Como parte del pop puedes especificar a donde
va a ir el dato que sacaste. Por ejemplo podrías hacer un pop al registro AX.

Piense que la pila es una pila de platos de una cafetería. Haces push a los platos uno a la vez para
colocarlos en la pila de platos, y cuando necesitas un plato haces un pop de un plato a la vez. El plato al
que le hiciste un pop siempre es el último que colocaste en la pila. La locación de almacenamiento que
contiene el último campo con dato que fue puesto en la pila se conoce como el tope (top) de la pila.

La figura 3-3 muestra un ejemplo, que ilustra las operaciones de push y pop a la pila. El ejemplo inicia
con una pila vacía en la cual se hace un push al número 10 y 87, después se le hace un pop al tope de
la pila

Para recuperar el 87, se le hace un nuevo push a la pila con un valor de 68, y después dos pop para
recuperar los números 68 y 10 respectivamente.

Tabla 3-3 Un ejemplo del uso de la pila

push push pop push pop pop


10 87 68

Cuando escribes un programa, debes instruir al ensamblador para apartar un espacio para la pila. Cada
entrada de la pila es de 1 palabra (2 bytes) y la pila puede tener una longitud de 64K bytes. Así la pila
puede mantener 32K (32768) entradas. En otras palabras, puedes hacer un push a 32768 palabras de datos
a la pila.

Usualmente, no es necesario tener una pila tan grande. Sin embargo, puedes escoger el tamaño que sea
más adecuado. Como regla general si no estas seguro del espacio que necesitas, aparta 256 palabras
(200H bytes). Los programadores que trabajan con programas pequeños dejan sólo 128 palabras (100H
bytes).

3. 9 Como es usada la Pila.

La pila esta diseñada para mantener información temporalmente. Esto es especialmente importante
cuando estas trabajando con procedimientos. Muchos programas de tamaño medio y grande están
separados en módulos auto contenidos llamados procedimientos. (En lenguajes de alto nivel estos se
conocen como funciones o subrutinas). Cuando un procedimiento invoca a otro procedimiento, decimos
que el primero llama al segundo. Cuando el segundo procedimiento termina, el programa regresa al
primer procedimiento en la siguiente instrucción después de la llamada.

26
Como parte de la ejecución de una instrucción de llamada, el procesador debe salvar la locación dentro
del primer procedimiento en el cual continuará cuando el otro procedimiento termine. Esta
locación es conocida como la dirección de regreso.

El procesador salva la dirección de regreso poniéndola en la pila. Cuando el segundo procedimiento


termina, el procesador saca la dirección de regreso de la pila y continúa con la ejecución en esa locación.
Debido a la estructura de la pila un procedimiento puede llamar a otros y esos otros a otros y siempre se
regresará a la locación correcta.

Otro uso importante de la pila es salvar el contenido de los registros antes de llamar a un procedimiento.
Antes de que el procedimiento regrese los registros pueden ser restaurados sacando los viejos valores de
la pila, esto permite a los procedimientos hacer un uso libre de los registros.

La pila también puede pasar información de un procedimiento a otro. Un procedimiento puede poner sus
datos en la pila (parámetros) y entonces el segundo procedimiento acceda a los datos. Si es necesario, el
segundo procedimiento podría usar la pila para devolver datos también.

Un uso final de la pila es mantener resultados temporalmente sobre todo para operaciones complejas.
Cada método a escoger varia dependiendo de la lógica de un programa especifico.

{____________o___________}

27
CAPITULO 4 TIPOS DE DIRECCIONAMIENTO

En este capitulo aprenderás a apreciar más la arquitectura de los procesadores de la familia Intel 86,
y también las varias maneras de referirse a las locaciones de memoria en un programa.
Este tópico, el direccionamiento es muy complejo, si eres un principiante te recomiendo leer este capitulo
más de una vez. La primera vez lee todo como esta y trata de absorber tanto como puedas, no te
preocupes por comprender todo. Después una vez que inicies a hacer tus primeros programas vuelve a
leerlo con calma.

Nuevos Términos.

Dirección efectiva. Una dirección completa de 20 bits.

Dirección de Segmento. Un número de 20 bits que contiene el valor base desde el cual una dirección
efectiva es calculada.

Offset. Un número de 16 bits que es adicionado a una dirección de segmento para calcular una dirección
efectiva.

Segmento. Un área de la memoria, con más de 64 Kb de longitud, que contiene una parte del programa.

Registro de Segmentos. Un registro que contiene los 16 bits más significativos de una dirección de
segmento; uno de los registros CS, SS, DS o ES.

Segmento de Código. Un segmento que contiene las instrucciones máquina de un programa.

Segmento de Datos. Un segmento que contiene los datos usados por el programa.

Segmento Extra. Un segundo segmento que contiene datos usados por el programa.

Segmento de Pila. Un segmento que contiene a la pila que usa el programa.

Direccionamiento Directo. Una dirección en la cual es desplazamiento esta indicado directamente.


28
Direccionamiento Indirecto. Una dirección en la cual el desplazamiento esta especificado por el valor
de un registro.

Índice. Usado para calcular un offset adicionando un valor (llamado un desplazamiento) a una locación
fija (llamada la dirección base).

Dirección Base. Una locación fija que sirve como una base para calcular un offset.

Desplazamiento. Un valor que adicionado a la dirección base nos da un offset.

Registro Índice. Un registro que contiene un desplazamiento; uno de los registros DI o SI.

4.1 El esquema básico de Direccionamiento en la PC.

Las instrucciones máquina trabajan con direcciones de hasta 16 bits. Esta es una limitante en el
modo real que DOS usa. Así, las posibles direcciones son desde 0000H hasta FFFFH
(0000000000000000B a 1111111111111111B). Esto da como resultado 10000H (64K) direcciones
diferentes. Es otras palabras, usando un esquema de este tipo, el procesador sólo tiene hasta 64K de
memoria disponible.

Pero 64K no es mucha memoria, si tuviéramos que construir programas con sólo 64K estaríamos
severamente limitados, Así que es imperativo que el procesador pueda accesar más memoria.

La solución es ingeniosa, pero algo complicada. Es importante que comprendas este esquema, así que lee
la siguiente sección con cuidado.

Vamos a hacer una analogía. Suponga que tiene un robot con un brazo mecánico que puede mover y que
tiene un largo de 64 pulgadas. Si el robot esta en un cuarto fijo en un lugar, el brazo sólo puede alcanzar
los objetos que están a 64 pulgadas de distancia máxima. Sin embargo si el robot se pudiera mover por el
cuarto, el brazo podría alcanzar cualquier objeto. Si un objeto no esta dentro de las 64 pulgadas del brazo
lo único que tiene que hacer el robot es moverse más cerca.
En otras palabras, piense que si el brazo del robot tiene una distancia 64 pulgadas, puede tomar cualquier
objeto en el cuarto estableciendo una base a 64 pulgadas del objeto.

El procesador de Intel tiene un esquema parecido. Si bien una dirección de 16 bits puede accesar sólo
64K bytes, se puede usar una muy grande cantidad de memoria considerando la dirección de 16 bits como
la dirección base relativa. Para accesar otros 64 K diferentes, todo lo que hay que hacer es mover la base.

Dentro de un programa, la dirección consiste de dos partes: una dirección de segmento y un offset. La
dirección de segmento es un número de 20 bits que corresponde a la posición base del robot. El
desplazamiento es un número de 16 bits que corresponde al brazo movible.

La dirección actual es llamada la dirección efectiva. Para calcular la dirección efectiva, todo lo que tienes
que hacer es sumar el offset al la dirección de segmento.

29
Aquí hay un ejemplo: Digamos que la dirección de segmento es 50000H y el offset es 62A3H. La
dirección efectiva es 50000H + 62A3H, lo cual es igual a 56A3H.
Ahora, si el segmento de dirección es 50000H y el desplazamiento puede tomar los valores desde 0000H
hasta FFFFH, la dirección efectiva varia desde 50000 hasta 5FFFFH. Así, con un segmento de dirección
de 50000H, puedes accesar los 64K (10000H) bytes de memoria que empiezan en 50000H.

Suponga que quiere accesar algunos datos de la dirección 8726BH. Esta dirección no esta en el mismo
rango, así que necesitas cambiar de segmento con sus 64K que este entre 8726H. Por ejemplo, podrías
cambiar la dirección de segmento a 80000H y usar un offset de 726BH.

Actualizando la dirección de segmento apropiadamente, puedes accesar cualquier byte en una memoria
grande. Asegurándote que el byte este dentro de los 64K del segmento de dirección.

El hardware calcula la dirección efectiva de modo que el resultado siempre es un número de 20 bits. Así
la dirección efectiva puede tener un rango desde 00000H hasta FFFFFH. Esto da 100000H posibles
direcciones.

En otras palabras usando una combinación de dirección de segmento y offsets, el procesador puede
direccionar más de 100000H bytes de memoria. Esto es igual a 1024K bytes o 1 Megabyte, mucho mejor
que sólo 64K.
Es importante comprender que diferentes combinaciones de direcciones de segmento y desplazamientos
puede dar la misma dirección efectiva. De hecho los pares de direcciones de segmentos y
desplazamientos en la tabla 4-1 dan la misma dirección especifica 8726BH.

Tabla 4-1 Direcciones de Segmento Offset Dirección efectiva


80000H + 726BH = 8726BH
87000H + 026BH = 8726BH
87260H + 000BH = 8726BH
85AD0H + 179BH = 8726BH

De los detalles se encargan automáticamente el procesador y el ensamblador.

4.2 Segmentos y Registros de Segmento.

Para poder implementar el esquema de direcciones explicado en el punto anterior, el procesador


requiere que todos los programas sean divididos en segmentos. Un segmento es un área de memoria de
más de 64K., si necesitas diseñar un programa largo, debes dividirlo en piezas donde cada pieza quepa en
un segmento. Cuando escribes un programa, existen ciertas instrucciones del ensamblador que indican
donde empieza y termina cada segmento.

Cuando el programa se ejecuta, el procesador mantiene una dirección de segmento para cada segmento.
Estas direcciones son mantenidas para los registros CS, DS, ES y SS.

Un programa puede tener varios segmentos, pero sólo cuatro pueden estar activos a la vez. Los cuatro
segmentos están estandarizados; y se conocen como segmento de código, segmento de datos, segmento
extra y segmento de pila. Todas las direcciones usadas para un segmento dado usan el registro de
segmento como base.

El segmento de código es la parte del programa que contiene las instrucciones máquina actuales (algunas
veces llamadas “código”). El registro CS contiene la dirección de segmento de ese segmento.
30
El segmento de datos es la parte del programa que contiene los datos. El segmento extra también puede
ser usado para almacenar datos, si es necesario. El registro DS contiene la dirección del segmento de
datos; el registro ES contiene la dirección de registro del segmento extra

El segmento de pila contiene a la pila, el registro SS, contiene la dirección del segmento de pila.

Todos los programas deben tener al menos un segmento de código y un segmento de pila. Estrictamente
hablando, los segmentos de datos y extra son opcionales, pero la mayoría de los programas tienen
también un segmento de datos.

4.3 Como son usados los Registros de Segmento.

Como sabes todas las direcciones tiene dos partes: la dirección de segmento y el offset. En un
programa escribes una dirección especificando las dos partes separadas por dos puntos. La primera parte
es el nombre del registro de Segmento.

Por ejemplo, digamos que quieres referirte a información en el segmento de datos. Si el desplazamiento
de la información es 6AH, puedes escribir la dirección como DS:6AH. Para referirte al segmento extra
con un desplazamiento de 1A5H, escribes ES:1A5H.

Cuando el programa se ejecuta el procesador adiciona el offset al contenido del registro de segmento para
obtener la dirección efectiva. Así si la dirección es DS:6AH, el procesador adiciona 6AH al contenido de
DS.

Por supuesto, este esquema sólo trabaja si cada registro de segmento tiene su propio valor. Así es como
pasa: Antes de que un programa sea ejecutado, este debe ser copiado a la memoria, este proceso es
llamado carga, y es realizado por el sistema operativo, cuando un programa es cargado este se copia a un
área de la memoria que este actualmente disponible. Este automáticamente fija las direcciones que el
programa usará.

Cuando escribes un programa no sabes que direcciones tendrá. De hecho cada vez que un programa corre,
los segmentos pueden ser cargados en locaciones diferentes. Sin embargo es importante que un registro
de segmento sea inicializado con un valor apropiado si el programa va a correr correctamente.

Como parte del proceso de carga, el sistema operativo inicializa los registros CS y SS para apuntar al
principio del código y la pila, respectivamente. Así, las instrucciones (el segmento de código) y la pila
son accesados automáticamente.

Debido a que no todos los programas tiene segmento de datos o segmento extra, el sistema operativo no
inicializa automáticamente los registros DS y ES. Esto debes hacerlo tú mismo, hasta que lo hagas, los
datos es esos segmentos no estarán accesibles. Afortunadamente, todas las inicializaciones requieren
pocas instrucciones estándar que colocas al inicio de cada programa.

4.4 El contenido del Registro de Segmento.

31
Como mencione antes, el registro de segmento contiene la dirección de un segmento. La dirección
de segmento debe tener al menos 20 bits, pero todos los registros tienen sólo 16 bits. ¿Como puede
contener un registro de 16 bits, 20 bits?

La solución es asumir que el registro contiene sólo los 16 bits más significativos de la dirección y
asumimos que los otros 4 bits son todos ceros. En otras palabras, si cada dirección de segmento es de 5
dígitos hex (20 bits), un registro de segmento contiene sólo los primeros 4 dígitos hex; el último dígito
hex se considera como 0H.

Aquí hay un ejemplo: Digamos que el sistema operativo carga el segmento de código en 217A0H y el
segmento de pila en 1F8A0H. El registro CS estará dado por el valor 217AH y el valor del registro SS es
1F8AH. En cada caso debes extender los valores por 0H (0000B) para obtener la verdadera dirección de
segmento.

Como puedes ver, asumiendo que todas las direcciones de 20 bits terminan en 0H, sólo necesitas
almacenar los primeros 16 bits de cada uno. La carga de los segmentos es hecho por el ensamblador y el
sistema operativo y los segmentos siempre estarán alineados al limite de un párrafo.

La única vez que necesitas preocuparte acerca de todo esto es cuando calculas una dirección efectiva.
Cuando lo hagas recuerda adicionar el valor extra 0H al valor del registro de segmento para obtener la
verdadera dirección de segmento.
Veamos como hacerlo. Digamos que estas usando un debugger y quieres examinar algunos datos en el
segmento de datos. El offset es 17A5H y el valor de DS es 20ABH. Primero escribe el valor del registro
de segmento:

20ABH

Después adiciona 0H al valor para obtener la dirección de segmento verdadera.

20AB0H

Finalmente adiciona el offset a la dirección de segmento:

20AB0H
+17A5H
22255H

Puedes instruir al debugger para desplegar los datos en esa dirección.

4.5 Como es implementada la Pila.

La imagen de los platos en la cafetería es buena para aprender como trabaja el concepto de una pila,
pero su implementación es un poco diferente, esto es debido a que no es posible mover todos los datos
hacia arriba o hacia abajo cada vez que metemos o sacamos un elemento. En su lugar, la pila es manejada
con un segmento con dos registros especiales.

El registro SS siempre contiene la dirección de segmento de la pila. El sistema operativo pone ese valor
cuando el programa se carga. Cuando el programa se ejecuta, el registro SS permanece sin
cambios (a menos que se use un registro de pila diferente).

32
El registro SP (apuntador de pila) contiene un offset que apunta al tope de la pila. Esto es, en cualquier
caso, la dirección efectiva del tope de la pila estará formada por los contenidos de los registros SS y SP.

La pila esta organizada como una lista con entradas de 16 bits (2 bytes). El registro SS apunta a la
dirección más baja de la lista. La primera instrucción push coloca datos en la entrada con la dirección más
alta, la siguiente instrucción push coloca datos en la entrada con la siguiente dirección más alta, y así.
Todas las veces, el registro SP apuntará al último dato que fue metido (el tope de la pila).

En otras palabras, la pila inicia desde la dirección más alta y crece hacia abajo hasta su base. La figura 4-
1 muestra un diagrama de la pila que fue cargada en 10000H. La pila tiene una longitud de 200H (512
bytes).

En este ejemplo, dos campos son empujados a la pila, el campo nombrado data1 fue empujado primero,
en la locación 101FEH. El campo nombrado data2 fue metido en segundo en 101FCH. Note que cada
dato irá quedando más cerca de la base de la pila. Actualmente la dirección base o registro SS contiene
1000H y el registro SP contiene 01FCH.

Figura 4-1 La Pila

En general, esto es lo que pasa cuando un dato es introducido a la pila.

1. SP es decrementado en 2 para apuntar a la siguiente locación libre.

2. El campo a ser metido es copiado al offset especificado por SP.

Esto pasa cuando un dato es sacado de la pila:

1. El campo al que SP apunta es copiado a la locación apropiada.

2. SP es incrementado en 2 para apuntar a la siguiente entrada de la lista.

En el ejemplo anterior el registro SP esta inicializado a 200H. EL primer push decrementará SP a 01FEH
antes de almacenar el data1, si todos los campos en la pila son sacados y la pila esta vacía SP apuntará de
nuevo a 0200H.

4.6 Direccionamiento Directo.

33
Dentro de un programa puedes referirte a locaciones de memoria específicas. Para acceder esas
locaciones de memoria, el procesador necesita conocer la dirección del segmento y el offset. Puedes
escribir esta información especificando el nombre del segmento y un offset, separado por dos puntos.

Por ejemplo, para especificar la locación en el segmento de datos que tiene un offset de 10AH, puedes
escribir

DS:10AH

Como es un poco problemático referirte a los desplazamientos usando números, el ensamblador te


permite usar nombres. Cuando creas el segmento de datos y el segmento extra, dejas un espacio para los
datos que usaras, así puedes asignarle nombres a esos campos de datos.

El ensamblador mantiene una tabla con cada nombre y su desplazamiento. Cuando usas una instrucción
refiriéndose a un nombre, el ensamblador lo substituye por su desplazamiento actual. Así cuando te
refieres a un campo dato, se dice que usas un direccionamiento directo.
Por ejemplo, digamos que el nombre SUM tiene un desplazamiento de 10AH en el segmento de datos.
Puedes referirte a esa locación como

DS:SUM

Lo bueno de esto, es que no tienes que rastrear su desplazamiento actual por ti mismo.

Muchas de las veces, puedes simplificar las cosas aún más. Cuando haces referencia a una locación de
memoria, el procesador asume que está en el segmento de datos a menos que especifiques otra cosa. Así
puedes referirte a la locación sólo como:

SUM

Tiene que especificar un registro de segmento si el dato no está en el segmento de datos. Por ejemplo, si
SUM estuviera en el segmento extra, debes hacer referencia a la locación como:

ES:SUM

4.7 Direccionamiento Indirecto.

Algunas veces, quieres usar un desplazamiento que esta en un registro. Para hacer referencia a esa
locación, debes especificar el nombre del registro encerrado entre paréntesis cuadrados.

Por ejemplo, digamos que el registro SI contiene 1000H, si una instrucción contiene

SI

Se refiere al valor en SI, llamada 1000H. Sin embargo, si la instrucción contiene

[SI]

34
Este se refiere al dato en el offset 1000H. En otras palabras, cuando un registro esta encerrado entre
paréntesis cuadrados, este representa al dato en el desplazamiento contenido en el registro, esto es
llamado direccionamiento indirecto.

Cualquiera de los registros SP, BP, BX, SI, y DI pueden ser usados para direccionamiento indirecto.
Muchas de las veces, no es necesario especificar un registro de segmento debido a que el procesador
asume uno.

Cuando usas SP o BP, el procesador asume que el desplazamiento se refiere al segmento de pila y usa el
registro de segmento SS. Cuando usas BX, SI, y DI, el procesador asume que te refieres al segmento de
datos y usa el registro de segmento DS. Así no es necesario especificar el registro a menos que quieras
suprimir el default.

Por ejemplo, digamos que BX contiene el offset de datos del segmento extra, tú puedes usar

ES:[BX]

Hay una excepción importante a estas reglas. Cuando usas una instrucción que mueve strings, el
procesador asume que el registro DI contiene un desplazamiento al segmento extra.

Los defaults implícitos para los registros de segmentos se muestran en la tabla 4-2

Tabla 4-2 Registro Registro de segmento implícito


[SP] SS
[BP] SS
[BX] DS
[SI] DS
[DI] DS (ES con instrucciones para strings)

4.8 Direccionamiento Indexado.

Algunas estructuras de datos consisten de más de una sola parte. Por ejemplo, un arreglo
unidimensional puede ser considerado como una lista de datos. Una tabla bidimensional puede
considerarse como renglones y columnas.

En todos los casos, los datos actuales están almacenados como una secuencia de bytes, palabras, dobles
palabras, etc. Sin embargo, cuando usas esos datos, ayuda darles un orden. De hecho, cuando usas un
arreglo es conveniente pensar que el arreglo completo tiene un sólo nombre y campos separados son los
elementos del arreglo.

Para hacer esto, tienes la facilidad de utilizar una indexación. La indexación permite hacer referencia a
campos relativos a una locación fija. De hecho puedes accesar cada elemento del arreglo relativo por su
inicio. Esta locación fija es llamada la dirección base.

Considere un arreglo de 100 elementos que existe en el segmento de datos como 100 bytes consecutivos.
Cada byte contiene un elemento. El nombre del arreglo es LIST. Estrictamente hablando, LIST es el
desplazamiento del primer byte. Así puedes accesar el primer elemento del arreglo usando

35
LIST

Si quieres accesar otros elementos, el ensamblador te permite adicionar valores al nombre. Por ejemplo,
para accesar el siguiente elemento puedes especificar

LIST+1

El número que adicionas a la dirección base es llamada un desplazamiento, en el último ejemplo, el


desplazamiento fue de 1. Puedes accesar cualquier elemento del arreglo usando el desplazamiento
apropiado. Aquí hay algunos ejemplos.

LIST+57
LIST+82
LIST+99

No hay un desplazamiento dado para el primer elemento del arreglo, dando un desplazamiento de 1
obtenemos el segundo elemento, y así. Si hay cien elementos, el rango de desplazamientos va desde 0 a
99. El desplazamiento máximo de n elementos es n-1. Otro modo de verlo es que empezamos a contar
desde cero. Por eso, LIST+57 nos da el elemento 58.

Por supuesto estos desplazamientos funcionan en arreglos consistentes de bytes. Considere un arreglo
llamado BIGLIST consistente de 100 palabras. Cada palabra ocupa 2 bytes, los desplazamientos relativos
a los elementos son como sigue:

Primer elemento ⇐ BIGLIST


Segundo elemento ⇐ BIGLIST+2
Tercer elemento ⇐ BIGLIST+4
.
.
.
Ultimo elemento ⇐ BIGLIST+198

4.9 Los Registros Índice.

Ocasionalmente, necesitas accesar los elementos de la estructura de datos adicionando un


desplazamiento constante a un offset, por ejemplo LIST+4. Sin embargo, es más útil usualmente
almacenar el desplazamiento en un registro. Los registros SI y DI son usados para este propósito y son
algunas veces referidos como el registro índice.

Por ejemplo, considera el arreglo LIST consistente de 100 bytes. Decimos que el programa necesita una
referencia cada elemento en turno, desde el primero hasta el último. Puedes variar el contenido de SI
desde 0 hasta 99 y usar.

LIST[SI]

Note dos cosas: primero, cuando usas un registro para mantener un desplazamiento, debes indicar al
ensamblador el nombre del registro entre paréntesis cuadrados, igual a cuando usas un direccionamiento
indirecto.

36
Segundo, necesitas adicionar un + antes del nombre del registro. La expresión anterior significa “el valor
de del registro SI adicionado al desplazamiento de LIST”.

En el ejemplo anterior, puede indexar cualquier elemento de LIST actualizando SI al valor apropiado. De
hecho, para accesar todos los elementos del último al primero, varía SI de 99 hasta 0; para accesar el
elemento 57, pon SI a 56.

Ahora considera el arreglo BIGLIST, el cual consiste de 100 palabras. Digamos que quieres indexarla
usando el registro DI.

BIGLIST[DI]

Cada elemento de BIGLIST es de 2 bytes de longitud, debes actualizar a DI de acuerdo al tamaño. Por
ejemplo, para accesar todos los elementos del primero al último, cambia a DI a 0, 2, 4, 6... 198. Para
accesar el elemento 57, pon DI a 112, para accesar el elemento n, pon DI a (n-1) x 2.

Si necesitas manejar palabras dobles, debes manejar el índice en múltiplos de 4; con palabras cuádruples,
múltiples de 8.

En lenguajes de alto nivel esto lo hacen por ti, en lenguaje ensamblador, debes hacer esto tú mismo.

4.10 Los Registros Base: El registro BX.

En la sección previa, los ejemplos usaban el nombre para almacenar la locación como una dirección
base. Puedes también utilizar un registro para mantener una dirección base. BX es usada para este
propósito en el segmento de datos; BP es usada por la pila. Estos registros son algunas veces
mencionados como los registros base.

Por ejemplo, puedes indexar el arreglo LIST usando

LIST[DI]

Si pones el desplazamiento de LIST en BX, puedes usar

[BX][DI]

Como puedes ver, el registro base esta encerrado en paréntesis cuadrados. (Note que el último ejemplo
usa direccionamiento indirecto.)

Usando un registro para mantener la dirección base es útil si quieres accesar más de una estructura de
datos con la misma instrucción. Por ejemplo, puedes tener un programa que procese varios arreglos. Todo
lo que tienes que hacer es poner a BX al desplazamiento de cada arreglo en turno y entonces usar DI o SI
para mantener el desplazamiento.

Por ejemplo, digamos que tienes dos arreglos llamados LIST1 y LIST2. Para accesar al primer arreglo,
pon BX a LIST1; para accesar el segundo pon BX a LIST2.
Para estructuras más complicadas, el ensamblador permite usar un nombre, una dirección base, y un
desplazamiento, por ejemplo:
37
TABLE[BX][SI]

El contenido de ambos registros es adicionado al desplazamiento del campo de datos. De hecho, si BX


mantiene un 20 y DI mantiene un 4, el ejemplo anterior tiene el valor

TABLE+24

La ventaja de esta doble indexación es permitirte establecer una dirección base dentro de una estructura
de datos. Por ejemplo, digamos que TABLE es una tabla de bytes, almacenada renglón por renglón. Hay
30 renglones y cada renglón tiene 100 elementos; esto es, TABLE tiene 30 renglones y 100 columnas.

Puedes poner a BX apuntando a un renglón particular y usar DI o SI para indexar a través de los
elementos de ese renglón. En este caso, cada renglón tiene 100 bytes. El primer renglón consiste del byte
0 al 99; el segundo renglón del byte 100 a 199; y así. Para accesar el elemento 57 del renglón 18,
ponemos BX a 1700 y SI a 56, y usamos

TABLE[BX][SI]

Esto podría ser lo mismo que

TABLE+1756

Si usas una estructura de datos que mantiene elementos más grandes de un byte, puedes ajustar la
indexación de acuerdo al tamaño. Por ejemplo, si TABLE mantiene palabras en lugar de bytes, los
renglones empezarían en 0, 200, 400, etc.

4. 11 Los Registros Base: El registro BP.

Muchas veces, el direccionamiento en una pila es manejado con los registros SS y SP. SS mantiene
el registro de dirección y SP mantiene el offset del tope de la pila. Cuando usas una pila haciendo push´s
y pop´s, el procesador actualiza SP automáticamente y te permite accesar la información en el tope de la
pila.

Sin embargo, algunas veces quieres accesar información dentro de la pila. En este caso, usas el registro
BP para mantener una dirección base en la pila.

Por ejemplo, digamos que tienes dos procedimientos llamados A y B. El procedimiento A pasa
información a B, y llama al procedimiento B. Si la cantidad de información es pequeña, esta puede ser
pasada en uno o varios de los registros generales. Sin embargo si necesitas pasar más información una
estrategia es que el procedimiento A empuje la información en la pila antes de llamar al procedimiento B.

Así, tan pronto como el procedimiento B inicia, este puede obtener información para accesar la pila. Sin
embargo, el procedimiento B no puede hacer esto de manera directa. Ciertamente, el procedimiento B
puede empujar datos por si mismo. Esto hace difícil rastrear la locación de la información que fue
empujada por el procedimiento A.

La solución esta en el procedimiento B, tan pronto como toma el control, pude salvar el offset del tope de
la pila en BP. En otras palabras, la primera cosa que el procedimiento B debe hacer es copiar el contenido

38
de los registros SP o BP. Esto significa que no importa que tipo de información sea metida en la pila, los
datos del procedimiento A pueden ser accesados usando el contenido de BP como una dirección base.

Por ejemplo, digamos que el procedimiento A mete 10 palabras de información en la pila. Cuando el
procedimiento B inicie, la primera cosa que hará es salvar el desplazamiento del tope de la pila copiando
su contenido al SP o al BP. Ahora el procedimiento B puede accesar la diez palabras, a su conveniencia,
desde SS:BP hasta SS:BP+18. (Recuerde que 10 palabras ocupan 20 bytes). Por ejemplo la segunda
palabra puede ser accesada como

SS:BP+2

En cualquier punto dentro del procedimiento B. Piense que esto se refiere a la palabra que esta dos bytes
más allá del tope de la pila cuando el procedimiento B empezó.

Para hacer las cosas fáciles, siempre que uses a BP como el registro base, el procesador asume que BP
contiene un offset en el segmento de pila y automáticamente utiliza SS como el registro de segmento. En
el ejemplo anterior también podría usar

BP+2

4.12 Regla generales para especificar una dirección.

Cuando especificas una dirección directa, usas el nombre de campo dato y un registro base,
registro índice, o desplazamiento constante opcional.

Aquí hay algunos ejemplos:

TABLE
TABLE[SI]
TABLE[DI]+8
TABLE[BX][SI]+8

Cuando especificas una dirección indirecta, puedes usar SP, BP, BX, SI o DI. Con BP o BX, puedes usar
SI ó DI como índice. Con todos los direccionamientos indirectos, puedes usar un desplazamiento
opcional. Veamos algunos ejemplos.

[BP]
[SI]
[BX][DI]
[SI]+4
[BP][DI]+4

Con un direccionamiento directo o indirecto, no puedes usar más de un registro base ni más de un registro
índice.

El ensamblador te permite especificar los valores indexados de varias maneras, sujetas a las siguientes
reglas:
39
Los valores índices pueden estar en cualquier orden

Los nombres de registros deben ir entre paréntesis cuadrados

Puedes combinar nombres de registros y desplazamientos constantes en un conjunto de paréntesis


cuadrados si los separas con +.

Si pones un desplazamiento constante enfrente de un nombre de registro, no necesitas usar un +.

Aquí hay varias direcciones directas equivalentes que ilustran estas reglas.

TABLE[BX][SI]+8
TABLE[BX+SI+8]
TABLE[8+SI+BX]

Aquí hay varias direcciones indirectas equivalentes.

[BP][SI]+12
12[BP][SI]
12[BP+SI]

Obviamente, la mejor idea es tomar un patrón y utilizarlo. Te recomiendo los siguientes patrones. Para la
dirección directa, usa

nombre[base][indice]+constante

por ejemplo :

TABLE[BX][SI]+8

Para un direccionamiento indirecto, usa

[base][indice]+constante

por ejemplo :

[BP][DI]+8

4.13 Direccionamiento con el Segmento de Código.

Un programa puede tener cuatro segmentos activos: el segmento de código, el segmento de pila, el
segmento de datos, y el segmento extra. De estos cuatro, solo el segmento de código no puede ser
modificado o explícitamente accesado mientras el programa es ejecutado. El segmento de código no
puede ser accesado porque es el contenido de las instrucciones actuales del programa.

40
La dirección en el segmento de código es hecha automáticamente por el procesador con los registros CS e
IP. El registro CS contiene la dirección de segmento del segmento de código. El registro IP contiene el
offset de la siguiente instrucción a ser ejecutada. Cuando cada instrucción se ejecuta, el registro IP es
actualizado apuntando a la siguiente instrucción en el programa.

Cuando el programa cambia la secuencia de instrucciones, el procesador actualiza el registro IP


apropiadamente. Por ejemplo, cuando un procedimiento llama a otro procedimiento, el desplazamiento
del otro procedimiento es colocado en IP. Si el segundo procedimiento esta en un segmento diferente, el
registro CS debe ser actualizado también. Esto también lo realiza el procesador cuando llama una
instrucción CALL.

El direccionamiento dentro de un segmento de código es automático, puedes ignorar los registros CS e IP


muchas de las veces. Sin embargo, son importantes cuando usas un debugger para probar un programa. Si
ejecutas un programa una instrucción a la vez, puedes examinar CS e IP para mantener el rastreo. Si algo
falla, puedes usar a CS e IP para ayudarte a determinar la dirección de la instrucción que causo el error.

{_____________o___________}

CAPITULO 5 LAS PARTES ATOMICAS DE UN PROGRAMA EN ENSAMBLADOR

Los programas en lenguaje ensamblador son más complejos que los de alto nivel. Cada programa en
ensamblador tiene parte diferentes que debes comprender. En este capitulo aprenderás las diversas partes
y las reglas para crearlas. Este material es básico y debes dominarlo para escribir tus programas.

Nuevos Términos

Programa Principal. Dentro de un programa, es el primer procedimiento a ejecutarse.

Estatuto. Una línea de un programa en ensamblador.

Comentario. Todas las partes de un estatuto que son ignorados por el ensamblador; los comentarios son
usados para mantener información descriptiva.

Instrucción. Un estatuto que puede ser traducido a lenguaje máquina.

Directiva ó Pseudo-operación. Un estatuto que da instrucciones al ensamblador.

Opcode. La parte de una instrucción o directiva que identifica una operación especifica.

Operando. La parte de una instrucción o directiva que representa el valor sobre el cual la instrucción o
directiva actúa.

41
Tabla de símbolos. Una tabla de nombres creada por el ensamblador para procesar un programa.

Referencia forward. En un programa, la aparición de un nombre, que no ha sido definido aún.

Nombre reservado. Una palabra a la cual el ensamblador asigna un significado especial; las palabras
reservadas no pueden ser usadas como nombres.

5.1 Como ve el programador un programa.

Como programador piensa que el programa tiene dos partes -instrucciones y datos. Las instrucciones
describen la lógica y los datos es el material sobre el que actúan las instrucciones. Cuando veas un
programa puedes hacerte las siguientes preguntas ¿Que hace este programa (instrucciones)? ¿Con qué
trabaja el programa (datos)?

La parte de instrucciones de un programa consiste de uno o más procedimientos (o subrutinas). El primer


procedimiento a ejecutar es llamado el programa principal. Un programa pequeño podría consistir solo
del programa principal y los datos. Un programa grande consiste de datos y de varios procedimientos.

5.2 Como ve el ensamblador un programa.

Cuando tú ves un programa que consiste de instrucciones y datos, el ensamblador ve un programa


fuente consistente de una secuencia de estatutos, uno por línea. El ensamblador lee esos estatutos (dos o
más veces), y genera un programa en lenguaje máquina consistente de segmentos.

Este programa en lenguaje máquina debe conocer los requerimientos del procesador, estos requerimientos
son altamente técnicos y detallados. Afortunadamente el ensamblador y el Linker se encargan de la
mayoría de esos detalles.

Esto significa que, desde el punto de vista del programador el ensamblador es un conjunto de estatutos
que tiene oscuros significados de lo que hace un programa. Esto es el porque los programas en
ensamblador son más difíciles de comprende que los de alto nivel. Tú defensa contra esta confusión es
desarrollar bueno hábitos de programación.

La conexión entre tu punto de vista del programa y el del ensamblador es que las instrucciones están
contenidas en el segmento de código. Sí tu programa tiene más de un procedimiento, todos estos estarán
contenidos dentro del segmento de código. La parte de datos estará contenida en los segmentos de pila,
datos y extra.

5.3 Como ve el programa el Linker.

El linker no ve el programa como una entidad conceptual o como una secuencia de estatutos. En su
lugar, el linker ve uno o más archivos que contienen módulos. El trabajo del linker es usar estos archivos
para crear un sólo archivo que contenga un modulo ejecutable.

Por conveniencia, puedes mantener un conjunto de módulos objeto en una colección llamada una librería.
El linker buscará la librería y extraerá los módulos objeto particulares que especifiques.
42
5.4 Que pasa durante el proceso de Ensamblamiento.

Piense que el ensamblador lee el programa línea por línea. Algunas líneas son instrucciones y otras
son requerimientos para definir datos.

El ensamblador crea el modulo objeto, byte por byte. Si una línea en el programa fuente contiene una
instrucción, el ensamblador genera la instrucción máquina equivalente y lo adiciona al modulo objeto. Si
la línea especifica un requerimiento de dato, el ensamblador adiciona el número apropiado de bytes (para
almacenar el dato) al modulo objeto. Así, el orden de las instrucciones y las áreas de datos en el modulo
objeto depende del orden en el cual el ensamblador encuentra las líneas del programa.

5.5 Comentarios.

Los programas en lenguaje ensamblador consisten de una secuencia de estatutos. Aquí hay tres tipos
de estatutos: comentarios, instrucciones y directivas.

Los comentarios son partes de estatutos que son ignorados por el ensamblador. Puedes usar los
comentarios para incluir descripciones, en el programa. Los comentarios son partes importantes del
programa ya que en posiciones claves de tu programa sirven como guías maestras del significado y
propósito del programa.

Hay varias maneras de incluir comentarios. La primera es que cualquier estatuto diferente del blancos que
este después de un punto y coma es considerado un comentario e ignorado por el ensamblador.

; Este es un comentario

Segundo, puede adicionar un comentario al final de una instrucción o directiva marcando el comentario
con un punto y coma. La siguiente instrucción pone le valor 99 en el registro AX:

MOV AX,99 ; Aquí hay un comentario

En general la regla es esta: mientras procesa una línea el ensamblador ignora todo lo que este después de
un punto y coma. Hay una excepción, ocasionalmente el programa pondrá caracteres con comillas
simples o dobles. En estos casos, el punto y coma no define comentarios, como en el siguiente ejemplo.

“ Hola; adiós “
‘ Hola; adiós ‘

5.6 Instrucciones y Directivas.

En general, una instrucción es un estatuto que pude ser traducido a lenguaje máquina. Una directiva
es un estatuto que le dice que hacer al ensamblador. Las directivas no pueden ser traducidas a lenguaje
máquina; sin embargo, son necesarias para que un programa se ensamble apropiadamente.

43
Consideremos algunas ejemplos que ilustran esta distinción. Veamos las siguientes dos instrucciones. La
primera adiciona el contenido del registro BX al contenido del registro AX. La segunda copia el
contenido de AX a la locación de memoria llamada SUM.

ADD AX,BX
MOV SUM,AX

Lo que siguen son dos directivas. La primera le dice al ensamblador que aparte un byte de memoria y le
de el nombre SUM. La segunda le dice al ensamblador que empieza el segmento llamado CSEG.

SUM DB 1
CSEG SEGMENT

Existen muchas instrucciones y directivas en el ensamblador, y al principio es difícil diferenciarlas, sin


embargo si quieres aprender a diferenciarlas pregúntate si generan una instrucción en lenguaje máquina o
si le dan una orden al ensamblador. (Por supuesto también puedes ver el manual).

La ventaja es que las directivas te dan un control sobre las cosas que están pasando. Esto es
especialmente importante si escribes programas sofisticados. La desventaja es que cada directiva requiere
una línea separada, la cual tiende a obscurecer el diseño lógico del programa.

5.7 El formato de los estatutos del lenguaje Ensamblador.

Las reglas del lenguaje ensamblador dependen del ensamblador que uses. Aquí hay algunas reglas
generales:
Cada línea contiene solo un estatuto.

Un estatuto puede empezar donde sea en una línea.

Puedes usar mayúsculas o minúsculas como desees.

Las instrucciones y directivas tienen un formato más estructurado. Cada uno de estos tipos de elementos
tiene tres partes: un nombre, un opcode y un operando(s). Las tres partes siempre deben estar en este
orden, y ellas deben estar separadas por lo menos con un espacio.
Aquí hay un ejemplo de un par de estatutos de un programa:

; actualizar el total
NEWSUM LABEL NEAR
MOV AX,TOTAL
ADD AX,SUBTOTAL
PUSH AX

Los programadores se refieren a un estatuto por su opcode. De hecho, en el ejemplo anterior, puedes decir
que el primer estatuto es la directiva LABEL y los otros estatutos son las instrucciones MOV, ADD y
PUSH.

Los ejemplos previos ilustran tres puntos importantes. Primero, para hacer las instrucciones entendibles,
usa mayúsculas.
Segundo, algunos estatutos contienen dos operandos como el estatuto

44
MOV AX,TOTAL

En tales casos debes separar los operandos con una coma.

Tercero, todas las instrucciones tendrán un opcode pero no todas tendrán un nombre y/o operandos.

Para mantener cada parte en su lugar, haz el hábito de separar esto por columnas. Empieza con los
nombres en la columna 1, los opcodes en la columna 9, y los operandos en la columna 17, si incluye
comentarios pon estos a partir de la comuna 41. Esto ayuda a estandarizar el programa. La figura 5-1
muestra una instrucción MOV con este formato:

Nombre Opcode Operando Comentario

SETUP MOV DX,OFFSET MESSAGE ;actualizar DX

col 1 col 9 col 17 col 41

5.8 Como usar los Nombres.

El nombre es la parte de una instrucción o directiva que comprende la dirección donde esta
localizada. Por ejemplo, considere la siguiente directiva, la cual le dice al ensamblador que inicia un
nuevo procedimiento:

GETDATA PROC

Esta directiva tiene un nombre GETDATA, y un opcode, PROC, pero no tiene ningún operando.
Como parte del proceso de ensamblamiento, el ensamblador mantiene una tabla de nombres llamada
tabla de símbolos. Cada vez que el ensamblador encuentra un nuevo nombre, lo adiciona a la tabla con la
dirección (el offset) en el cual apareció. Entonces cuando otras instrucciones se refieren a ese nombre, se
puede usar como una dirección con un operando. El ensamblador puede buscar el nombre en la tabla de
símbolos y sustituir la dirección.

Por ejemplo, una vez que GETDATA es establecida como la representación de una dirección al principio
del procedimiento, una instrucción CALL puede llamar al procedimiento así:

CALL GETDATA

La instrucción tiene un opcode CALL y un operando GETDATA pero no tiene nombre.


Aquí hay otro ejemplo. La siguiente directiva le dice al ensamblador que aparte 5 palabras de memoria:

SUM DW 5 DUP(?)

Esta directiva tiene un nombre, SUM; un opcode, DW; y un operando 5 DUP(?). El nombre SUM
representa la dirección donde está la primera de las 5 palabras. Así la siguiente instrucción copia el
contenido del registro AX a esta palabra:

MOV SUM, AX

Esta instrucción tiene un opcode MOV, y un operando SUM,AX; pero no tiene nombre.

45
SUM se refiere a la dirección de la primera de las 5 palabras, para referirte a la dirección de la segunda
palabra, puedes usar SUM+2. Así si quieres copiar el contenido del registro AX al la segunda palabra
puedes usar:

MOV SUM+2,AX

Sin embargo que pasa si el ensamblador no encuentra aún el estatuto de la definición de un nombre. Por
ejemplo, que pasa si la instrucción anterior está antes de la directiva en la cual SUM es definida. Esta es
llamada una referencia forward. El ensamblador maneja la referencia forward dejando un espacio para la
dirección, la cual debe ser llenada después.

Los programadores experimentados evitan el forward siempre que es posible, debido a que el
ensamblador no le es posible crear código eficiente cuando encuentra una referencia forward. La mejor
forma de evitar esto es colocando los segmentos que define los datos antes del segmento que contiene las
instrucciones.

5.9 Las reglas para especificar Nombres.

Los nombres son símbolos que escoges para representar de una manera más sencilla direcciones de
memoria en un programa. Debes crear un nombre de acuerdo a las siguientes reglas:

Los nombres pueden usar letras, dígitos y los siguientes caracteres especiales:
? @ _ $

El primer carácter no debe ser un dígito. Esto le permite al ensamblador distinguir entre nombres y
números.

No es buena idea usar el carácter @ al principio de un nombre. Los nombres que empiezan con @
tienen un propósito especial.
Los nombre pueden ser tan largos como quieras; sin embargo, el ensamblador solo reconoce
los primeros 31 caracteres.

Aquí hay algunos ejemplos de nombres validos:

HELLO $MARKET A12345 LONG_NAME PART_3

Aquí hay algunos nombres inválidos:

LONG-NAME 3_PART

Como siempre la recomendación es que escojas nombres concisos, pero que representen el uso práctico
de la variable o sus posibles valores.

Esta absolutamente prohibido, como en los lenguajes de alto nivel, que utilices nombre idénticos a las
instrucciones y directivas de ensamblador.

5.10 Reglas para especificar Números.

46
Puedes utilizar números en decimal, hexadecimal, y binario. Para especificar números decimales
escríbelos con dígitos como siempre. Por ejemplo la siguiente instrucción carga el registro AX con el
valor decimal 855.

MOV AX,855

Para especificar un número en hexadecimal, use los dígitos hex del 0 al 9 y de la letra A-F. Debes
adicionar una H al final del número para especificar que es un hex, la siguiente instrucción carga el
registro AX con el valor hex 855H (2133 decimal).

MOV AX,855H

Si el número inicia con una letra de la A-F, debes poner un 0 antes del número, para que el ensamblador
no piense que es un nombre de dirección de memoria. Por ejemplo la siguiente instrucción carga el
registro AX con el valor FFH (255 decimal):

MOV AX,0FFH

Si usas

MOV AX,FFH

el ensamblador asumirá que FFH es un nombre y ocurrirá un error.

Para especificar un número binario, use solo los dígitos 1 y 0, y adicione una letra B al final del número.
Por ejemplo si quisiera cargar el registro AX con el valor binario 011010010110B (1686 decimal, 696H)

MOV AX,011010010110B

{_____________o___________}

CAPITULO 6 COMPRENDER UN PROGRAMA EN LENGUAJE ENSAMBLADOR

En este capitulo aprenderás como construir un programa básico en lenguaje ensamblador, usando un
programa de ejemplo aprenderás cual es la función de cada parte y como se construye. Cuando termine
este capítulo debes sentirte cómodo con un esqueleto que es común para cualquier tipo de programa en
ensamblador.

Nuevos Términos

Listado. Un reporte o impresión que el ensamblador genera cuando procesa un programa.

Punto de entrada. La dirección en la cual un procedimiento inicia su ejecución.

Salvar (un registro). Almacenar un registro para recuperarlo después.

Restaurar (un registro). Cambiar el valor de un registro por un valor anterior.

Side effect. Un cambio inadvertido en el ambiente de un programa.


47
6.1 Un programa Prototipo.

En este capítulo el programa de la figura 6-1 es usado como prototipo de un programa de propósito
general en lenguaje ensamblador. Después de que leas este capítulo usa un editor para hacer tu propia
copia del programa prototipo. De este modo cuando veas como ensamblar y linkear un programa podrás
hacerlo con este.

Note que en la figura hay un número de línea, este sólo es utilizado como referencia, y no debe aparecer
en la copia que hagas con el editor. Cuando explique las diferentes instrucciones y directivas solo las
explicare ligeramente. Estas serán explicadas más a fondo en otros capítulos adelante.

Figura 6-1 Un programa prototipo

1 PAGE 58,132
2 ;------------------------------------------------------------------------------------
3 ; DISPLAY
4 ;
5 ; Proposito:
6 ; para desplegar un mensaje en la pantalla
7 ; -----------------------------------------------------------------------------------
8
9 ; Ajustar el titulo y el conjunto de instrucciones
10 TITLE DISPLAY - programa prototipo
11 .286
12
13 ;-------------------------------------------------------------- segmento STACK
14 SSEG SEGMENT STACK
15 DB 32 DUP(“STACK---”)
16 SSEG ENDS
17
18 ;--------------------------------------------------------------- segmento DATA
19 DSEG SEGMENT
20 MESSAGE DB “Hola, ¿como estás viejo?”, 0DH, 0AH
21 L_MESSAGE EQU $-MESSAGE
22 DSEG ENDS
23
24 ;--------------------------------------------------------------- segmento CODE
25 CSEG SEGMENT ‘CODE’
26 ASSUME CS:CSEG, SS:SSEG; DS:DSEG
27
28 PAGE
29 ;----------------------------------------------------------------------------------
30 ; MAIN (programa principal)
31 ;
32 ; Proposito:
33 ; Desplegar un mensaje en la pantalla
34 ;
35 ; Entrada:
36 ; -- ninguna --
48
37 ;
38 ; Salida:
39 ; El mensaje es desplegado en la pantalla
40 ;
41 ; Procedimientos:
42 ; -- ninguno --
43 ;----------------------------------------------------------------------------------
44
45 ; Procedimiento: MAIN
46 MAIN PROC FAR
47
48 ; Salvar la direccion para poder regresar a DOS
49 PUSH DS
50 PUSH 0
51
52 ; Actualizar el registro de segmento
53 MOV AX,DSEG
54 MOV DS,AX
55
56 ; Desplegar el mensaje
57 MOV BX,0001H
58 LEA DX,MESSAGE
59 MOV CX,L_MESSAGE
60 MOV AH,40H
61 INT 21H
62
63 ; Regresar a DOS
64 RET
65
66 ; Fin de procedimiento: MAIN
67 MAIN ENDP
68
69 ; Fin de segmento de codigo
70 CSEG ENDS
71
72 ;--------------------------------------------------------------- Fin de programa
73 END MAIN

6.2 Como usar los comentarios bien.

Es importante que todo programa en ensamblador tenga un encabezado que explique brevemente el
propósito del programa y su nombre línea 2-7. Ahora vea los comentarios en las líneas 29-43. Estos
muestran información general acerca del programa principal. Debes tener un conjunto de comentarios que
antecedan cada procedimiento de tu programa.

Puedes ver que después del nombre del procedimiento (línea 30), siguen cuatro encabezados estándar:

“Propósito” describe que es lo que hace el procedimiento. Este debe ser un comentario de dos líneas.

“Entrada” y “Salida” describen que necesita el procedimiento y que va a obtener como resultado.

49
“Procedimientos” nos indica cuales procedimientos llama el programa principal.

Ahora vea los comentarios que anteceden a cada bloque pequeño de instrucciones y directivas - líneas 9,
13, 18, 24, 45, 48, 52, 56, 63, 66, 69 y 72. Estos comentarios son de dos tipos. El primero de los
comentarios describe el principio o el fin de segmento o procedimiento. Este tipo de comentarios
consisten de simples anuncios.

El segundo tipo de comentarios -los cuales son bastantes en un programa - describen el propósito de los
estatutos que están a continuación. Por ejemplo, la línea 56 dice “Desplegar el mensaje”. Note que no
dice “Desplegará el mensaje” ó “Mensaje desplegado”. Los mejores comentarios son directos: “Hace esto
o aquello”, en lugar de “Hizo o va a hacer esto o aquello”.

Puedes ayudarte a diseñar un procedimiento, empezando con una serie de estatutos, que te indiquen una
guía sobre lo que tiene que realizar el programa. Por ejemplo el diseño para este programa es

Salvar direcciones para poder regresar a DOS.

Actualizar los registros de segmento.

Desplegar el mensaje en la pantalla.

Regresar a DOS.

Una vez que terminas el diseño puedes transformar los estatutos en un par de líneas del lenguaje
ensamblador.

Evita como a la peste a aquellos programadores y programas de ensamblador que tienden a poner
comentarios a cada una de las instrucciones y directivas en el programa, (reserva este caso solo para
partes del programa demasiado oscuros o complicados), esto solo hace muy confuso el programa e indica
que ni el programador sabe exactamente que hace su programa, hay que ser breve pero conciso. Para
evitar esto aprende a diseñar tu programa usando metas iniciales como comentarios principales. Debes ser
capaz de comprender un programa completamente leyendo los comentarios en secuencia.

Siempre documenta tus programas como si fueran para otra persona que tuviera que comprenderlos sin
ayuda. Esto te permite desarrollar programas rápidos y de manera más precisa, también te permite
recordar el propósito de tus programas viejos que no has visto por un tiempo.

6.3 Indicar el fin de un programa.

El último estatuto en todo programa debe ser una directiva END. Esta directiva tiene dos propósitos:
indicar el fin de un programa, y decirle al ensamblador donde va a empezar a ejecutarse un programa.
En este caso, el operando de la directiva END es MAIN (línea 73). Esto le dice al ensamblador que
cuando el programa sea cargado, la ejecución debe empezar con el estatuto con el nombre MAIN (línea
46).

6.4 Actualizando el listado.

50
El listado es un reporte, que se puede imprimir, que el ensamblador genera cuando procesa un
programa. El listado contiene cada estatuto en el programa con las instrucciones máquina
correspondientes al estatuto. Si el ensamblador encuentra un error en un estatuto, el listado contendrá un
mensaje de error después del estatuto. Al final del programa, el listado muestra información acerca de los
nombres que el programa usa.

Hay varias directivas que puedes usar para actualizar el listado. Una es la directiva PAGE (línea 1 y 28) y
la directiva TITLE (línea 10).

La directiva PAGE puede ejecutar dos funciones. Cuando es usada con operandos PAGE controla el
ancho y las páginas del listado. En la línea 1 del programa prototipo

PAGE 58,132

La directiva PAGE pone una longitud de cada página de 58 líneas y un ancho en cada página de 132
caracteres. Esta directiva debe ir en la primera línea del programa, antes de los comentarios
introductorios, para asegurarnos que imprimirá los comentarios adecuadamente.

Cuando la directiva PAGE no tiene operandos, le dice al ensamblador que inicie una nueva página en el
listado.

La directiva TITLE (línea 10) especifica un titulo a ser impreso cerca del tope de cada página. En este
caso, el titulo es “DISPLAY - programa prototipo”.

6.5 Especificar el Conjunto de Instrucciones.

Por default, el ensamblador puede reconocer sólo las instrucciones 8086. Sin embargo puedes poner
una directiva especial al inicio del programa para decirle al ensamblador que planeas usar un procesador
diferente. El ensamblador te permite entonces usar todas las instrucciones que funcionan en ese
procesador.
Como explique en el capitulo 1, las instrucciones 8086 son todas las que necesitas muchas de las veces.
Sin embargo, existen un par de instrucciones que no son del 8086 que son muy convenientes de usar.
Estas instrucciones requieren por lo menos un procesador 286. (Específicamente me refiero a las
instrucciones PUSHA y POPA usadas en la pila).

Para decirle al ensamblador que quiero usar el conjunto de instrucciones 286 (en modo real, uso la
directiva .286 (línea 11).

Puedes encontrar un par de instrucciones 286 extra que son útiles en un programa, por ejemplo en la línea
50 meto un valor 0 en la pila. El procesador 8086 no acepta este tipo de instrucciones. El 8086 necesita
que primero le pases el valor a algún registro y de ahí a la pila.

Así si no tuviéramos la directiva .286 no podrías usar la instrucción

PUSH 0

Tendría que poner un cero en algún registro AX por ejemplo, y entonces meter el registro:

MOV AX,0
PUSH AX
51
6.6 Actualizar los Segmentos.

Muchos de tus programas tendrán un segmento de pila, un segmento de datos, y un segmento de


código. Usualmente puedes ponerlos en ese orden. Algunas veces necesitaras usar el segmento extra, en
tal caso el segmento extra debe estar entre el segmento de datos y el segmento de código.

La directiva SEGMENT marca el inicio de un segmento; la directiva ENDS marca el fin de un segmento.
Cada segmento debe tener un nombre. Por ejemplo, si el segmento es el de pila, la directiva SEGMENT
debe tener el operando “STACK”.

En el programa prototipo, el segmento de pila es actualizado en los estatutos de las líneas 14 y 16:

SSEG SEGMENT STACK


SSEG ENDS

El segmento de datos es actualizado en las líneas 19 y 22:

DSEG SEGMENT
DSEG ENDS

Y el segmento de código es actualizado en las líneas 25 y 70:

CSEG SEGMENT ‘CODE’


CSEG ENDS

Si tienes un segmento extra, puedes definirlo con estatutos similares:

ESEG SEGMENT
ESEG ENDS
Puedes escoger cualquier nombre que quieras en lugar de SSEG, DSEG, CSEG y ESEG. Sin embargo
estos nombres creo que están bien y dan la idea.

Cuando defines el segmento de código, debes usar el operando ‘CODE’ con la directiva SEGMENT. LA
razón es obscura y no esta documentada en el manual.

Note que he usado comentarios con una línea de guiones (líneas 13, 18 y 24) para marcar el principio de
cada segmento. El segmento de código consiste de instrucciones máquina que el ensamblador generará a
partir de las instrucciones fuente. Los segmentos de pila, datos y extra consisten de espacio de memoria
que el ensamblador apartará.

El espacio para el segmento de datos es requerido por la directiva DB en la línea 20. Brevemente, DB
significa “definir byte”. Esta directiva le dice al ensamblado el número de bytes que tiene que apartar.
Además de especificar los bytes también se pueden inicializar a un valor particular.
La directiva DB en la línea 20:

MESSAGE DB “Hola, ¿como estas viejo?”, 0DH, 0AH

le dice al ensamblador que aparte bytes, tantos como caracteres o valores estén especificados.

52
Los primeros 24 contienen los caracteres “Hola, ¿como estas viejo?”. Los siguientes 2 bytes contienen los
valores hex DH y AH. Estos dos valores hex hacen que el cursor avance al principio de la siguiente línea
después de que el mensaje es desplegado. Así la directiva DB aparta 26 bytes. Los detalles de la
definición de espacio de memoria se explicaran en un capitulo posterior.

En resumen, el segmento de código consiste a las instrucciones máquina que corresponden a las
instrucciones entre

CSEG SEGMENT ‘CODE’

CSEG ENDS

El segmento de datos consiste de las áreas de memoria que serán reservadas por medio de directivas que
están entre

DSEG SEGMENT

DSEG ENDS

Si necesitas un segmento extra, las directivas que reservan su área de memoria estarán entre

ESEG SEGMENT

ESEG ENDS

Las directivas que reservan área de memoria para la pila están entre

SSEG SEGMENT STACK

SSEG ENDS

6.7 Apartar área de memoria para la Pila.

El estatuto que aparta espacio de memoria para la pila es la directiva DB, en el programa prototipo,
esta en la línea 15:

DB 32 DUP(“STACK---”)

53
Veamos esta instrucción más al detalle. Supongamos que quieres reservar 256 bytes de memoria para la
pila. Existen varias maneras en las que puedes apartar tal área de memoria. La más simple es apartar los
256 bytes directamente con la directiva DB.

DB 256 DUP(?)

significa “aparta 256 bytes”. DUP significa duplicado. El signo de interrogación significa que no es
necesario inicializar los valores de la pila a un valor específico. Sin embargo, puede que quieras
inicializar cada uno de los 256 caracteres de la pila a un carácter particular. Cuando corres un programa
usando un debugger, puedes examinar la memoria del programa ejecutado. Si la pila contiene caracteres
reconocibles cuando el programa inicia, puedes encontrarlos o distinguirlos más fácilmente.

Por ejemplo si quisieras apartar 256 bytes para la pila cuyo contenido sean puros asteriscos (*) . Los
haces con la siguiente directiva:

DB 256 DUP(“*”)

Sin embargo es probable que otras direcciones de memoria contengan también una buena cantidad de
asteriscos lo que en cierto punto seria un poco molesto de comprobar y segundo con una secuencia de
asteriscos no podríamos reconocer rápidamente cuanta cantidad de la pila esta siendo ocupada por los
datos.

La siguiente directiva aparta 256 bytes y los inicializa a “STACK---”. Cada grupo de caracteres de
STACK--- requiere 8 bytes, así tienes una requisición de 32 grupos para apartar 256 bytes (32 x 8 = 256).

DB 32 DUP (“STACK---”)

Si necesitas una pila más grande o pequeña, ajusta el estatuto DB como necesites. Por ejemplo, si quieres
una pila de 1024 bytes puedes usar:

DB 128 DUP(“STACK---”)

así 128 x 8 = 1024.

6.8 Direccionar los segmentos de Pila, Datos y Código.

El ensamblador se encarga de casi todos los detalles involucrados con la generación de las
direcciones apropiadas. Sin embargo, puedes ayudarle al ensamblador de dos maneras: Primero, debes
decirle al ensamblador que valores contendrán los registros de segmento cuando el programa se ejecute y
segundo, debes asegurarte que los registros de segmento contiene esos valores actualmente.

Para completar el primer requerimiento, usamos las directiva ASSUME. En el programa prototipo en la
línea 26:

ASSUME CS:CSEG, SS:SSEG, DS:DSEG

Esto le dice al ensamblador que asuma qué cuando el programa se ejecute, el registro CS contendrá la
dirección de CSEG; SS estará contenido en la dirección SSEG; y DS contendrá la dirección de DSEG.
Basados en estos reconocimientos, el ensamblador generará los desplazamientos correctos para los
nombres de los segmentos varios. En otras palabras, cuando uses un nombre como parte de un operando,

54
el ensamblador reemplazará el nombre con el offset relativo correcto, para el registro de segmento
apropiado.

Afortunadamente no necesitas actualizar los registros CS y SS por ti mismo. Cuando DOS carga un
programa, este automáticamente pone CS apuntando al primer byte del segmento de código y SS
apuntando al primer byte de la pila. Sin embargo si necesitas actualizar los registros DS y ES. Esto
puedes hacerlo al principio del programa, antes de cualquier referencia a nombres usados en el segmento
de datos o extra.

Para actualizar el registro DS, todo lo que tienes que hacer es cargar la dirección del primer byte del
segmento de datos. Si llamaste a tu segmento de datos DSEG, usa la instrucción MOV para copiar el
valor de DSEG al registro DS. Sin embargo la instrucción MOV no permite copiar directamente una
locación de memoria a un registro de segmento. En otras palabras no puedes usar

MOV DS,DSEG

Sin embargo, si puedes copiar DSEG a un registro general, digamos AX, y entonces de AX a DS. Esto
esta dado por las líneas 52 y 54 del programa prototipo:

; Actualizar el registro de segmento


MOV AX,DSEG
MOV DS,AX

6.9 Direccionar el Segmento Extra.

Existen dos maneras de usar un segmento extra. La primera, es usa el segmento extra como una
segunda área de datos distinta del segmento de datos. En este caso, el segmento extra estará después del
segmento de datos definido entre las líneas:

ESEG SEGMENT
ESEG ENDS

Como en el segmento de datos, las directivas del segmento extra para apartar memoria estarán entre los
estatutos anteriores.

en este caso la directiva ASSUME debe decirle al ensamblador que ES contendrá la dirección del
segmento extra:

ASSUME CS:CSEG, SS:SSEG, DS:DSEG, ES:ESEG

Cuando actualizas el registro DS, también debes actualizar el registro ES. En otras palabras, reemplaza
las líneas 52 a la 54 del programa prototipo con lo siguiente:

; Actualizar los registros de segmento


MOV AX,DSEG
MOV DS,AX
MOV AX,ESEG
MOV ES,AX

55
La segunda manera de usar el segmento extra es hacer que ocupe el mismo espacio que el segmento de
datos. Esto te permite referenciar los campos de datos que están en ambos segmentos (como una persona
que responde a dos nombres diferentes).

Esto es útil con ciertas instrucciones: MOVS, MOVSB, y MOVSW, como explicare después. Estas
instrucciones tiene dos operandos: uno debe estar en el segmento de datos y otro debe estar en el
segmento extra. Sobrelapar los dos segmentos de datos te permiten usar el mismo campo de dato en
ambos operandos.

Cuando actualizas el segmento de datos y extra para que sea la misma área de memoria, use los estatutos
SEGMENT y ENDS para definir un área de datos única:

DSEG SEGMENT
DSEG ENDS

Y cambie el estatuto ASSUME por este:

ASSUME CS:CSEG, SS:SSEG, DS:DSEG, ES:DSEG

Note que DS y ES están en DSEG. Esto le indica al ensamblador que ambos registros DS y ES apuntan al
mismo segmento DSEG.

Para actualizar los registros DS y ES, copie los valores de DSEG a ambos registros , reemplazando las
líneas 52-54 del programa prototipo por estas líneas:

; Actualizar los registros de segmento


MOV AX,DSEG
MOV DS,AX
MOV ES,AX

6.10 Actualizar el programa MAIN

Los programas están divididos en uno o más procedimientos. El procedimiento que empieza la
ejecución del programa es llamado main.

El programa principal puede llamar a otros procedimientos, los cuales pueden llamar a otros, etc. Por
supuesto muchos programas pequeños consisten de un solo procedimiento principal.

La dirección en la cual un procedimiento comienza a ejecutarse es llamada el punto de entrada. El punto


de entrada del programa principal en donde el programa comienza. La directiva END en el programa
prototipo (línea 73) tiene un operando MAIN, el punto de entrada de este programa es el nombre MAIN.
La primera instrucción que será ejecutada actualmente será el PUSH de la línea 49.

Para poder ser ejecutado un procedimiento debe ser llamado por otro procedimiento, y el procedimiento
principal no es la excepción; este debe ser llamado por un procedimiento DOS, tu programa principal es
sólo otro procedimiento.

56
Las directivas SEGMENT y ENDS le dicen al ensamblador donde empieza y termina un segmento, las
directivas PROC y ENDP le dicen al ensamblador donde empieza y termina un procedimiento, en el
programa prototipo, el programa principal empieza en la línea 46:

MAIN PROC FAR

y termina en la línea 67:

MAIN ENDP

En la línea 46, la directiva PROC tiene un operando FAR. Este le dice al ensamblador que el
procedimiento será llamado desde otro segmento. (En este caso, el procedimiento principal será llamado
por un procedimiento DOS, que definitivamente esta en otro segmento.)

La última instrucción de cualquier procedimiento debe ser la instrucción RET. Esta instrucción regresa al
procedimiento que hizo la llamada. En el caso del programa principal, la instrucción RET regresa al
sistema operativo DOS.

La instrucción RET espera la dirección de regreso que se encuentra en la pila. Recuerda que una
dirección consiste de un desplazamiento y una dirección de segmento. El offset debe estar en el tope de la
pila, la dirección de segmento debe ser lo segundo en la pila.

Cuando DOS encuentra y carga un programa, el registro DS contiene el registro de dirección al cual el
programa volverá cuando termine. Esta dirección es exacta sólo necesita un offset de 0.
Una de tus responsabilidades es que el programa principal meta la dirección de regreso a la pila. Esto es
mejor hacerlo al principio de cada programa antes de cambiar el contenido del registro DS. En el
programa prototipo esta en la línea 48-50:

; salvar la dirección de regreso a DOS


PUSH DS
PUSH 0

La dirección de segmento es metida primero, seguida de un desplazamiento 0. Como los valores están en
Last-in, first-out, el desplazamiento será recuperado primero, seguido por la dirección de segmento. Esto
es lo que la instrucción RET necesita y lo hace automáticamente sin que tengas que intervenir.

Si colocas datos en la pila en el transcurso de tu programa, debes asegurarte de sacarlos antes de que una
instrucción RET se ejecute. En otras palabras debes asegurarte que la instrucción RET encuentre los
valores apropiados en la pila.

6.11 Los Estatutos que hacen el trabajo.

¿Donde están las instrucciones que despliegan el mensaje? En el programa prototipo están en las
líneas 56-61:

; Desplegar el mensaje
MOV BX,0001H
LEA DX,MESSAGE
MOV CX,L_MESSAGE
MOV AH,40H
INT 21H

57
Para escribir un programa diferente, sólo tienes que reemplazar estas líneas por instrucciones diferentes y
reemplazar el segmento de datos por nuevos datos. La mejor forma de empezar un programa nuevo es
modificar el programa prototipo.

6.12 Actualizar un programa para llamar procedimientos.

El programa prototipo de la figura 6-1 tiene un sólo procedimiento, el programa principal, sin
embargo frecuentemente diseñaras programas que tengan varios procedimientos.
Actualizar un programa para adicionarle algunos procedimientos es fácil. Sólo sigue estas guías:

Todos los procedimientos deben estar dentro del segmento de código.

Use las directivas PROC y ENDP para indicarle al ensamblador donde empieza y acaba el
procedimiento.

La figura 6-2 muestra una guía de un programa que tiene varios procedimientos. Note que los tres
procedimientos son llamados MAIN, PROC1 y PROC2. Dentro de un procedimiento, usas la instrucción
CALL para llamar a otro procedimiento. Por ejemplo, si quisieras llamar al procedimiento PROC1 de la
siguiente manera:

CALL PROC1

Para regresar al procedimiento que hizo la llamada, usa

RET

La instrucción CALL automáticamente empuja la dirección de regreso en la pila, en preparación para la


instrucción RET

Figura 6-2 Patrón de un programa que llama procedimientos

comentarios introductorios

SSEG SEGMENT STACK


-Segmento de la pila-
SSEG ENDS

DSEG SEGMENT
-Segmento de datos-
DSEG ENDS

ESEG SEGMENT
-Segmento extra-
ESEG ENDS

CSEG SEGMENT ’CODE’

MAIN PROC FAR


-Programa principal-
58
MAIN ENDP

PROC1 PROC
-Procedimiento 1-
PROC1 ENDP

PROC2 PROC
-Procedimiento 2-
PROC2 ENDP

CSEG ENDS

END MAIN

6.13 El prototipo de un Procedimiento.

Excepto por el programa principal, todos los procedimientos son llamados dentro del programa.
Estas llamadas a los procedimientos tienen una estructura similar a la del programa principal. La figura 6-
3 contiene un prototipo de un procedimiento.

Figura 6-3 Prototipo de un procedimiento.

1 PAGE
2 ;-----------------------------------------------------------------------------------------
3 ; DISPM1
4 ;
5 ; Proposito:
6 ; Desplegar el primero de dos mensajes
7 ;
8 ; Entrada:
9 ; -ninguna-
10 ;
11 ; Salida:
12 ; El mensaje se despliega en pantalla
13 ;
14 ; Procedimientos:
15 ; -ninguno-
16 ;-----------------------------------------------------------------------------------------
17
18 ; inicio del procedimiento: DISPM1
19 DISPM1 PROC
20
21 ; salvar los registros
22 PUSHA
23
24 ; despliega el primer mensaje
25 MOV BX,0001H
26 LEA DX,MSG1
27 MOV CX,L_MSG1
28 MOV AH,40H
59
29 INT 21H
30
31 ; restaurar registros
32 POPA
33
34 ; regresar al procedimiento principal
35 RET
36
37 ; fin del procedimiento: DISPM1
38 DISPM1 ENDP

6.14 La estructura de un Procedimiento.

Demos un vistazo al procedimiento en la figura 6-5. Note que el procedimiento empieza con la
directiva página sin operandos.

Las líneas 2 a la 16 son comentarios similares al programa main, incluyendo el nombre, propósito,
entrada de datos, salida, etc. El procedimiento esta definido por las directivas PROC y ENDP (líneas 19 y
38). El procedimiento regresa usando una instrucción RET (línea 35).

6.15 Salvar y restaurar los registros.

La diferencia más notable entre el procedimiento y el programa principal, son las instrucciones para
salvar y restaurar registros (líneas 21-22 y 31-32). Salvar un registro significa recordar cual era su valor.
Restaurar los registros significa copiar los valores recuperados de regreso a los registros.

Cada llamada a un procedimiento obliga a este a dejar los registros tal y como estaban después de que
este termina. La idea es que un procedimiento no debe cambiar el ambiente. Esto es importante. Cuando
un procedimiento inadvertidamente cambia algo del ambiente, tal como el valor de un registro, lo
llamamos un cambio side effect. Debes aprender a diseñar programas para minimizar los side effects, que
pueden causar errores obscuros y perplejos.

La mejor manera de salvar los registros es poner sus valores en la pila. Esto es hecho por la instrucción
PUSHA (push all) en la línea 22. Esta instrucción salva todos los registros exceptuando los registros de
segmento. La instrucción POPA (pop all) restaura los registros (línea 32). Salvar y restaurar registros
tiene dos propósitos fundamentales. Primero evita el riesgo que una llamada a un procedimiento cause un
side effect cambiando accidentalmente el valor de algún registro, y segundo, permite a los procedimientos
utilizar los registros de manera libre ya que estos son restaurados antes de regresar del procedimiento.

6. 16 Un prototipo para un programa que llama procedimientos.

La figura 6-4 contiene un prototipo de un programa que llama procedimientos. Este programa de
ejemplo, DISPLAY manda dos mensajes a la pantalla. Para hacer esto, DISPLAY llama a dos
procedimientos: DISPM1, para desplegar el primer mensaje y DISPM2, para desplegar el segundo, este
programa es un prototipo; normalmente no debes separar en procedimientos algo tan sencillo como
desplegar mensajes.
60
Figura 6-4 Un programa prototipo para llamar procedimientos

PAGE 58,132
;------------------------------------------------------------------------------------
; DISPLAY2
;
; Proposito:
; para desplegar dos mensajes en la pantalla
; -----------------------------------------------------------------------------------

; Ajustar el titulo y el conjunto de instrucciones


TITLE DISPLAY2 - programa prototipo con procedimientos
.286

;-------------------------------------------------------------- segmento STACK


SSEG SEGMENT STACK
DB 32 DUP(“STACK---”)
SSEG ENDS

;--------------------------------------------------------------- segmento DATA


DSEG SEGMENT
MSG1 DB “Este es el mensaje 1”, 0DH, 0AH
L_MSG1 EQU $-MSG1
MSG2 DB “Este es el mensaje 2”, 0DH, 0AH
L_MSG2 EQU $-MSG2
DSEG ENDS

;--------------------------------------------------------------- segmento CODE


CSEG SEGMENT ‘CODE’
ASSUME CS:CSEG, SS:SSEG; DS:DSEG

PAGE
;----------------------------------------------------------------------------------
; MAIN (programa principal)
;
; Proposito:
; Desplegar dos mensajes en la pantalla
;
; Entrada:
; -- ninguna --
;
; Salida:
; Los mensajes son desplegados en la pantalla
;
; Procedimientos:
; DISPM1 -- despliega el primer mensaje
; DISPM2 -- despliega el segundo mensaje
;----------------------------------------------------------------------------------

; Procedimiento: MAIN
MAIN PROC FAR

61
; Salvar la direccion para poder regresar a DOS
PUSH DS
PUSH 0

; Actualizar el registro de segmento


MOV AX,DSEG
MOV DS,AX

; Desplegar los mensajes


CALL DISPM1
CALL DISPM2

; Regresar a DOS
RET

; Fin de procedimiento: MAIN


MAIN ENDP

PAGE
;-----------------------------------------------------------------------------------------
; DISPM1
;
; Propósito:
; Desplegar el primero de dos mensajes
;
; Entrada:
; -ninguna-
;
; Salida:
; El mensaje se despliega en pantalla
;
; Procedimientos:
; -ninguno-
;-----------------------------------------------------------------------------------------

; inicio del procedimiento: DISPM1


DISPM1 PROC

; salvar los registros


PUSHA

; despliega el primer mensaje


MOV BX,0001H
LEA DX,MSG1
MOV CX,L_MSG1
MOV AH,40H
INT 21H

; restaurar registros
POPA
; regresar al procedimiento principal

62
RET

; fin del procedimiento: DISPM1


DISPM1 ENDP

PAGE
;-----------------------------------------------------------------------------------------
; DISPM2
;
; Proposito:
; Desplegar el segundo de dos mensajes
;
; Entrada:
; -ninguna-
;
; Salida:
; El mensaje se despliega en pantalla
;
; Procedimientos:
; -ninguno-
;-----------------------------------------------------------------------------------------

; inicio del procedimiento: DISPM2


DISPM2 PROC

; salvar los registros


PUSHA

; despliega el segundo mensaje


MOV BX,0001H
LEA DX,MSG2
MOV CX,L_MSG2
MOV AH,40H
INT 21H

; restaurar registros
POPA

; regresar al procedimiento principal


RET

; fin del procedimiento: DISPM2


DISPM2 ENDP

; Fin de segmento de código


CSEG ENDS

;--------------------------------------------------------------- Fin de programa


END MAIN

{_____________o___________}

63
CAPITULO 7 PROCESAR UN PROGRAMA EN ENSAMBLADOR

En este capitulo aprenderás como convertir un programa fuente en un programa ejecutable,


explicare el uso del ensamblador y el linker, el programa de referencias cruzadas y como leer la salida de
estos programas.

Nuevos Términos

Listado. Un registro del proceso del ensamblador que muestra cada estatuto del programa fuente además
de otra información importante.

Referencia cruzada. Un reporte que muestra cada nombre del programa con el número de líneas en
donde el nombre aparece.

Librería. Un archivo que contiene una colección de módulos objeto que pueden ser unidos al programa.
Mapa. Un reporte generado por el linker del proceso de un modulo objeto.

Desensamblar. Reconstruir una instrucción d lenguaje ensamblador de una instrucción en lenguaje


máquina. También referido como unassembling.

Operando inmediato. Un operando que está en un valor actual.

Instrucción inmediata. Una instrucción que contiene un operando inmediato.

Relocalizable. Describe una dirección que no ha sido completamente determinada hasta que el programa
ha sido cargado.

7.1 Procesar y Correr un programa.

Una vez que creas un programa fuente con un editor, debes ensamblarlo y linkearlo para crear un
programa ejecutable. El ensamblador que vamos a utilizar MASM 5.0 de Microsoft ó IBM, este proceso
necesita ejecutar por lo menos dos programas, el ensamblador y el encadenador o linker, paso por paso.

7.2 Los archivos usados por el Ensamblador.

Los ensambladores usan ciertos archivos para ejecutar su trabajo, y cada tipo de archivo tiene una
extensión única. La entrada del ensamblador es el programa fuente, que debe estar almacenado en un
archivo con la extensión ASM. Por ejemplo, el programa fuente llamado DISPLAY podría estar en un
archivo llamado DISPLAY.ASM.

La salida del ensamblador son tres archivos. El primero es el modulo objeto, la traducción actual del
programa fuente en lenguaje máquina.

El segundo archivo es el listado. Este es un registro del proceso del ensamblador que muestra cada
estatuto del programa fuente con otra información importante.

64
El tercer archivo contiene información que puede ser usada para crear una referencia cruzada. Esta es una
lista de todos los nombres que el programa define con el número de líneas en donde aparece en el
programa.

A menos que especifiques otra cosa, siempre producirá el modulo objeto. Puedes no generar el listado o
las referencias cruzadas cuando ensambles.

Por convención, el ensamblador usa extensiones estándar para estos archivos : OBJ para los módulos
objeto y LST para los listados. Así un programa fuente llamado DISPLAY.ASM podría generar un
modulo objeto llamado DISPLAY.OBJ y un listado llamado DISPLAY.LST.

La extensión para el archivo de referencias cruzadas depende del ensamblador que uses MASM 5.0 de
Microsoft ó IBM usa la extensión CRF. La tabla 7-1 muestra los archivos de extensión. La figura 7-1
muestra las extensiones de entrada y salida del ensamblador.

Tabla 7-1 Extensión Significado


ASM Programa Fuente
OBJ Modulo Objeto
LST Listado
CRF referencia Cruzada

Figura 7-1

7.3 Ensamblar un programa usando el Ensamblador Microsoft ó IBM.

Para ensamblar un programa usando el ensamblador Microsoft ó IBM, use el comando MASM.
Después del nombre del comando escriba el nombre del archivo que quieres usar como programa fuente,
objeto modulo, listado e información de referencia cruzada.

Por ejemplo, digamos que quieres ensamblar un programa fuente llamado DISPLAY .ASM. De la
instrucción :

MASM DISPLAY,DISPLAY,DISPLAY,DISPLAY

El ensamblador asumirá que quieres usar las extensiones convencionales de archivos. En este saco el
modulo objeto será DISPLAY.OBJ, el listado será DISPLAY.LST, y la referencia cruzada se llamará
DISPLAY.CRF. En otras palabras, el comando anterior es lo mismo que :

MASM DISPLAY.ASM,DISPLAY.OBJ,DISPLAY.LST,DISPLAY.CRF

Por omisión, MASM crea el modulo objeto, pero no los otros archivos. Para usar el default, simplemente
quite el nombre del archivo que no quiera crear. Sin embargo a veces es necesario poner las comas :

65
Por ejemplo, para obtener el modulo objeto, sin listado, con información de referencia cruzada, teclea
esto :

MASM DISPLAY,,,DISPLAY
Si terminas el comando el comando con un punto y coma, el ensamblador usará los defaults de cualquier
nombre que omitas. Por ejemplo si tecleas

MASM DISPLAY,DISPLAY,DISPLAY;

obtendrás un modulo objeto, un listado, pero sin información de referencia cruzada. Muchas de las veces,
probablemente quieras usar este comando más simple :

MASM DISPLAY;

Esto generará el modulo objeto, sin listado, y sin referencia cruzada.


Un programa no podrá generar un archivo OBJ hasta que todos los errores de sintaxis que el ensamblador
detecte en el programa fuente sean corregidos. Si existe un error en el momento de ensamblar un
programa fuente el ensamblador marcará el tipo de error que encuentre, y en que línea lo encontró. Si
existen errores en un programa se debe volver a editar el programa fuente corregir el error, salvar el
programa y tratar de ensamblarlo de nuevo.

Existen otro tipo de errores de menor grado llamados warnings (advertencias). Un programa que
contenga 0 errores y algunos warnings si puede generar un modulo objeto. Por lo general estas
advertencias se refieren a nombres declarados en el segmento de datos, que nunca son utilizados en el
segmento de código u otros errores benignos que no afectan en mucho al programa, sin embargo lo mejor
es tratar de reducir los warnings a 0.

7.4 Los archivos usados por el Linker.

El linker o encadenador lee un modulo objeto generado por el ensamblador y produce un programa
ejecutable. El modulo objeto tiene una extensión OBJ y el programa ejecutable una extensión EXE.
El linker es un programa complejo que debe hacer muchas cosas, en particular, en linker puede combinar
más de un modulo objeto en un sólo programa ejecutable. Esto muchas veces te permite ensamblar partes
de un programa separado y unirlas dentro del proceso de encadenamiento.

El linker puede usar colecciones de módulos objeto llamados librerías. Una librería es un archivo, con la
extensión LIB, que contiene una colección de modulo objeto. Si escribes un programa que llama a un
procedimiento que está en una librería, puedes especificar el nombre de la librería cuando inicies el
encadenamiento. El linker buscará la librería y extraerá los modulo objeto necesarios. Esto permite
referenciar procedimientos estándar dentro del programa.

Como el ensamblador el linker puede producir reportes de su trabajo. Este reporte es llamado un MAPA.
Muchas de las veces no lo necesitas.

La tabla 7-1 muestra las extensiones usadas por el linker. La figura 7-2 muestra las entradas y salidas del
linker.

Tabla 7-1 Extensiones de archivos usados por el Linker


Extensión Significado

66
OBJ Modulo objeto
EXE Programa ejecutable
MAP Mapa
LIB Librería

Figura 7-2 Las entradas y salidas del Linker

7.5 Linkear con el programa Microsoft ó IBM.

Una vez que el programa ha sido ensamblado, debes encadenarlo usando el comando LINK. La
salida del linker es un programa ejecutable que puedes correr.

Teclea el comando seguido por el nombre de los archivos que quiere usar para el modulo objeto, el
programa ejecutable, el mapa, y la librería(s). Coloca una coma entre cada nombre.

Por ejemplo, digamos que quieres linkear un modulo objeto llamado DISPLAY.OBJ usando una librería
llamada MYLIB.LIB. Teclee:

LINK DISPLAY,DISPLAY,DISPLAY,MYLIB

El linker asume que quieres usar las extensiones convencionales de los archivos. En este caso, el
programa ejecutable será DISPLAY.EXE, y el mapa será DISPLAY.MAP. En otras palabras, el comando
anterior es lo mismo que:

LINK DISPLAY.OBJ,DISPLAY.EXE,DISPLAY.MAP,MYLIB.LIB

Por omisión, LINK crea un programa ejecutable sin el mapa y sin buscar librerías. Para usar el default,
simplemente deja sólo el nombre del OBJ.

Por ejemplo, para usar DISPLAY.OBJ y una librería llamada MYLIB.LIB para generar un programa
ejecutable sin el mapa teclee:

LINK DISPLAY,,,MYLIB

Si terminas el comando con un punto y coma, el linker usa los defaults para cualquier nombre que omitas.
Por ejemplo, si tecleas

LINK DISPLAY,DISPLAY,DISPLAY;

generarás un programa ejecutable con su mapa, pero sin librerías. Muchas de las veces querrás usar la
opción más simple posible:

LINK DISPLAY;
67
Esto genera un programa ejecutable sin mapa y sin librerías.

7.6 Ejecutar un Programa.

Una vez que el linker termina su labor puedes correr un programa de dos maneras diferentes. La
primera es invocar al programa ejecutable escribiendo su nombre en el símbolo del sistema del sistema
operativo. El segundo es bajo los auspicios de un debugger.

La ventaja de usar el debugger es que es fácil probar el programa. Puede ejecutar una instrucción a la vez
y desplegar los valores de los registros y las locaciones de memoria que desees. Hasta puedes cambiar los
valores de los registros y locaciones.

En orden de desplegar el programa, un debugger debe leer las instrucciones máquina y reconstruir las
instrucciones originales. Este proceso es llamado desensamblar o unassebling. Obviamente un debugger
no puede reconstruir los nombre que usas en el programa. Estos nombres son desplegados como las
direcciones actuales donde están los datos.

7.7 Usar un Archivo Batch para procesar un Programa.

Para hacer tu vida más fácil puedes usar un archivo Batch para procesar un programa en
ensamblador con el MASM y el LINK. La figura 7-5 muestra el archivo batch, llamado
ENSAMBLA.BAT.
Para usa ENSAMBLA.BAT, especifica el nombre del archivo del programa fuente como el primer
parámetro. ENSAMBLA.BAT ensambla, linkea y ejecuta el programa (si no hay errores). Si especificas
/D como el segundo parámetro, ENSAMBLA.BAT ejecuta el programa dentro de un debugger.
Por ejemplo, para procesar un programa llamado DISPLAY.ASM, teclea

ENSAMBLA DISPLAY

Si quieres ejecutar el programa bajo un debugger, teclea

ENSAMBLA DISPLAY /D

Si el archivo DISPLAY.ASM no existe, ENSAMBLA.BAT despliega el siguiente mensaje:

*** Error: DISPLAY.asm -- no se encuentra el archivo

ENSAMBLA.BAT te permite usar /D como segundo parámetro. Por ejemplo si tecleas ENSAMBLA
DISPLAY /P. El programa despliega el siguiente error:

*** Error: Parámetro ilegal: /P

Si el ensamblador o el linker encuentran un nivel de error en alguna variable mayor o igual a 1 o si


detectan un error en el programa, ENSAMBLA.BAT se detiene inmediatamente.

Figura 7-5 Archivo batch para procesar un programa en ensamblador

:-----------------------------------------ENSAMBLA.BAT ---------------------------------------:

68
: Archivo batch para procesar un programa en ensamblador :
: Parametros: %1 -- nombre del archivo con el programa fuente :
: %2 -- “/d” correr el programa con un debugger :
:--------------------------------------------------------------------------------------------------------:

:--Apagar el echo y limpiar la pantalla


@ECHO OFF
CLS:

:--Asegurarse que el código fuente existe


IF EXIST %1.ASM GOTO FOUND
ECHO *** Error: %1.asm -- no se encuentra el archivo
GOTO STOP

:--Ensamblar el programa:
:FOUND
MASM %1;

:--Si hay un error en el ensamblado detenerse


IF ERRORLEVEL 1 GOTO STOP

:--Linkear el programa
LINK %1;

:--Si hay un error en el encadenamiento detenerse


IF ERRORLEVEL 1 GOTO STOP

:--Si %2 es “/D”, ejecutar el programa con el debugger


:--Si %2 esta vacio, ejecutar el programa por si mismo
IF x%2 == x/D GOTO DEBUGGER
IF x%2 == x GOTO EXECUTE
ECHO *** Error: Parametro ilegal: %2
GOTO STOP
:DEBUGGER
DEBUG %1.EXE
GOTO STOP
:EXECUTE
%1

:--Terminar
:STOP

7.8 Desplegar el Listado.

Como sabemos el ensamblador puede generar un listado del proceso que lleva a cabo. En esta
sección te mostrare como leer el listado y comprender la información valiosa que contiene. Primero
obtenga el listado, por ejemplo el listado del programa DISPLAY.ASM, o sea DISPLAY.LST e
imprímalo. El listado que obtuve aparece en la Figura 7-6.

Figura 7-6 Listado de DISPLAY.LST

Microsoft (R) Macro Assembler Version 5.10 6/28/97 09:50:21


DISPLAY - programa prototipo Page 1-1

69
1 PAGE 58,132
2 ;-------------------------------------------------------------------------
3 ; DISPLAY
4 ;
5 ; Proposito:
6 ; para desplegar un mensaje en la pantalla
7 ; ------------------------------------------------------------------------
8 ;
9 ; Ajustar el titulo y el conjunto de instrucciones
10 TITLE DISPLAY - programa prototipo
11 .286
12
13 ;--------------------------------------------------------- segmento STACK
14 0000 SSEG SEGMENT STACK
15 0000 0020[ DB 32 DUP("STACK---")
16 53 54 41 43 4B
17 2D 2D 2D
18 ]
19
20 0100 SSEG ENDS
21
22 ;---------------------------------------------------------- segmento DATA
23 0000 DSEG SEGMENT
24 0000 48 6F 6C 61 2C 20 MESSAGE DB "Hola, ¨como estas viejo?", 0DH, 0AH
25 A8 63 6F 6D 6F 20
26 65 73 74 A0 73 20
27 76 69 65 6A 6F 3F
28 0D 0A
29 = 001A L_MESSAGE EQU $-MESSAGE
30 001A DSEG ENDS
31
32 ;---------------------------------------------------------- segmento CODE
33 0000 CSEG SEGMENT 'CODE'
34 ASSUME CS:CSEG, SS:SSEG; DS:DSEG
35

Microsoft (R) Macro Assembler Version 5.10 6/28/97 09:50:21


DISPLAY - programa prototipo Page 1-2

36 PAGE
37 ;-----------------------------------------------------------------------
38 ; MAIN (programa principal)
39 ;
40 ; Proposito:
41 ; Desplegar un mensaje en la pantalla
42 ;
43 ; Entrada:
44 ; -- ninguna --
45 ;
46 ; Salida:
47 ; El mensaje es desplegado en la pantalla
48 ;
49 ; Procedimientos:
50 ; -- ninguno --
51 ;----------------------------------------------------------------------
52
53 ; Procedimiento: MAIN
54 0000 MAIN PROC FAR
55
56 ; Salvar la direccion para poder regresar a DOS
57 0000 1E PUSH DS
58 0001 6A 00 PUSH 0
59
60 ; Actualizar el registro de segmento
61 0003 B8 ---- R MOV AX,DSEG
62 0006 8E D8 MOV DS,AX
63
64 ; Desplegar el mensaje

70
65 0008 BB 0001 MOV BX,0001H
66 000B 8D 16 0000 R LEA DX,MESSAGE
67 000F B9 001A MOV CX,L_MESSAGE
68 0012 B4 40 MOV AH,40H
69 0014 CD 21 INT 21H
70
71 ; Regresar a DOS
72 0016 CB RET
73
74 ; Fin de procedimiento: MAIN
75 0017 MAIN ENDP
76
77 ; Fin de segmento de codigo
78 0017 CSEG ENDS
79
80 ;------------------------------------------------------ Fin de programa
81 END MAIN

Microsoft (R) Macro Assembler Version 5.10 6/28/97 09:50:21


DISPLAY - programa prototipo Symbols-1

Segments and Groups:

Name Length Align Combine Class

CSEG . . . . . . . . . . . . . . 0017 PARA NONE 'CODE'


DSEG . . . . . . . . . . . . . . 001A PARA NONE
SSEG . . . . . . . . . . . . . . 0100 PARA STACK

Symbols:

Name Type Value Attr

L_MESSAGE . . . . . . . . . . . NUMBER 001A

MAIN . . . . . . . . . . . . . . F PROC 0000 CSEG Length = 0017


MESSAGE . . . . . . . . . . . . L BYTE 0000 DSEG

@CPU . . . . . . . . . . . . . . TEXT 1287


@FILENAME . . . . . . . . . . . TEXT display
@VERSION . . . . . . . . . . . . TEXT 510

73 Source Lines
73 Total Lines
12 Symbols

47436 + 179839 Bytes symbol space free

0 Warning Errors
0 Severe Errors

7.9 Lectura del listado: Introducción.

Vamos a empezar con las primeras dos páginas del listado. El programa aparece en el lado derecho
de la página. A la izquierda del programa esta el lenguaje máquina y las áreas de datos que son generadas
por el ensamblador.

Todos los números están traducidos a hex. Los primeros cuatro dígitos son el desplazamiento dentro del
segmento. A la derecha del offset están los dígitos hexadecimales actuales que representan la instrucción
en lenguaje ensamblador en esa línea. Por ejemplo, en la línea 68 podemos ver que el ensamblador genero
la instrucción máquina B440H en el offset 0012H.
71
El ensamblador inicia cada nuevo segmento en un desplazamiento de 0000H. Tantos como bytes son
apartados, el ensamblador incrementa el offset., hasta un máximo de FFFFH. Esto nos da un segmento
máximo de 64 K bytes.

7.10 Lectura del listado: El segmento de Pila.

En la línea 14 empieza el segmento de pila. Los 0000 a la izquierda muestran que el desplazamiento
inicia en 0000H. Ahora vea la línea 15. Este es un estatuto DB que aparta 256 espacios para la pila.
Recuerde que los bytes están representados por 8 caracteres “STACK---”, repetidos 32 veces. A la
izquierda podemos ver

0020 [ 53 54 41 43 4B 2D 2D 2D ]

El 0020 significa 20 hex (32 decimal). Esto significa que existen 8 números hexadecimales que serán
repetidos 20 veces hex. Los ocho números hex son la representación ASCII de los caracteres “STACK---
”.
Ahora veamos la línea 20, la última línea del segmento de pila. El 0100 a la izquierda significa que el
desplazamiento es ahora 0100H (256 decimal). EL segmento de pila inicia con un desplazamiento de
0000H y termina en el offset 0100H.

7.11 Lectura del listado: El segmento de Datos.

El segmento de datos esta definido por las líneas 23 a la 30. En la línea 23, el desplazamiento
empieza de nuevo en 0000H debido a que empezó un nuevo segmento. La línea 24 contiene la directiva
que aparta los bytes para guardar el mensaje. En las líneas 24 a la 28, podemos ver los valores actuales
para estos bytes: 48H, 6FH, 6C, etc. Estos son los valores ASCII de los caracteres del mensaje en la
directiva DB.

En la primera línea, 48H es la “H”, esta en el offset 0000H; el segundo carácter 6FH (la letra “o”, esta en
el offset 0001H, etc. Así en la línea 29 el offset tiene un valor de 001AH.

En la línea 29 la directiva EQU le dice al ensamblador que el símbolo L_MESSAGE tendrá un valor
igual a la longitud del mensaje.

7.12 Lectura del listado: El segmento de Código.

El segmento de código inicia en la línea 33. Note que el offset de nuevo se ha reseteado a 0000H
para iniciar un nuevo segmento. Como se van generando las instrucciones máquina, el offset se
incrementa - desde 0000H (línea 57) a 0001H (línea 58) a 0003H (línea 61) a 0006H (línea 62), etc. La
última instrucción que genera una instrucción máquina (línea 72) deja un offset de 0016H.
En otras palabras la primera instrucción empieza en 0000H, la segunda inicia en 0001H, la tercera
comienza en 0003H, etc. La ultima instrucción empieza en 0016H. Si hubiera otra instrucción
comenzaría en 0017H.

72
El último byte usado en el segmento de código esta en el offset 0016H. Así el segmento de código esta en
el rango 0000H (cero) hasta 0016H (22 decimal).

A la derecha de los desplazamientos, puedes ver las instrucciones en lenguaje máquina que se generaron.
Por ejemplo en la línea 69, podemos ver que la instrucción que inicia en el desplazamiento 0014H (INT
21) esta traducida como CD21H.

El primer byte de cada instrucción máquina es un número de dos dígitos hex que representan el opcode.
Por ejemplo en la línea 68, el opcode INT esta representado por CDH; el opcode RET en la línea 72 esta
representado por CBH.

Algunas formas diferentes de un instrucción están representadas por números diferentes. Por ejemplo, la
instrucción PUSH en la línea 57 empuja el valor de un registro. Este tipo de push esta representado por
1EH. La instrucción PUSH en la línea 58 empuja un valor actual (0 en este caso). Este tipo de instrucción
PUSH se representa con el código 6AH.

Muchas instrucciones tiene operandos. Puedes ver la traducción del operando a la derecha de la
traducción del opcode. Por ejemplo, ve la línea 62. La instrucción máquina es 8ED8H. El 8EH representa
el opcode (MOV); el D8H representa el operando.

Como sabemos los operandos pueden ser registros, por ejemplo:

PUSH AX

o nombres de locaciones de memoria, por ejemplo:

PUSH TEST

o valores actuales, por ejemplo:

PUSH 5

Un operando que tiene un valor actual es llamado un operando inmediato; una instrucción que tiene un
operando inmediato es conocida como instrucción inmediata. Ve la instrucción inmediata en la línea 58.
Esta instrucción empuja un valor 0 a la pila. La instrucción máquina es 6A00H. El 6AH representa el
opcode; el 00H representa el operando. Así podemos comprender que los operando inmediatos son
ensamblados directamente con la instrucción máquina. Esto ocurre en la línea 69, con el operando
inmediato 21H.

La última cosa que debes comprender acerca del segmento de código es el modo en que están
representadas las direcciones en hex.

Ve la línea 66. Esta instrucción usa el desplazamiento de MESSAGE como un operando. Si checas el
segmento de datos, verás que el desplazamiento de MESSAGE es 0000H línea 24.

Así, podemos ver la traducción a lenguaje máquina de la línea 66 es 8D160000H.. El 8D16H representa
el opcode. El 0000H representa el segundo operando- en este caso la solicitud de un offset. Si el offset
fuera diferente, digamos 218AH, la instrucción máquina sería 8A16218AH.

Note que la dirección en la línea 66 (0000H) tiene una “R” después. La “R” significa relocalizable. Una
dirección es relocalizable si su valor no esta completamente determinado hasta que el programa es
cargado. Casi todas las direcciones son relocalizables.

73
Estas direcciones son llamadas relocalizables debido a que el programa que usa esa direcciones puede
cargarlas donde sea en la memoria, actualizando los registros de segmento adecuadamente.
Ahora vea la línea 61. Note que el operando esta representado por “----”. El ensamblador usa “----” para
indicar la dirección de segmento. Note que también es relocalizable.

7.13 Lectura del listado: La última Página.

Vea la última página del listado. Esta página contiene información acerca de los nombres definidos
en el programa. Primero vemos cada nombre de segmento seguido de información acerca del segmento.
De hecho, vemos que SSEG define un segmento de pila de longitud 0100H (256 decimales) que están
alineados al limite del párrafo.

Seguido del nombre de segmentos, vemos otros nombres: MAIN, el cual es un procedimiento, y
L_MESSAGE y MESSAGE los cuales son símbolos. Estos nombres, los cuales representan direcciones
están seguidos por información descriptiva: el tipo de campo, su valor y sus atributos.

Para MAIN, vemos que el tipo es “F Proc”, lo cual significa procedimiento far. Este es un procedimiento
que es llamado por otro segmento. Cuando un procedimiento es de tipo “N Proc”, este procedimiento es
llamado dentro de su segmento. El valor de un procedimiento es su dirección. Si un nombre representa
solo una dirección tendrá una “L”.

La información de L_MESSAGE y MESSAGE son claras. L_MESSAGE es un número con un valor de


1AH. MESSAGE es la dirección del byte en la locación 0000H en el segmento de datos.
Finalmente, el listado finaliza con un sumario de warnings y errores.

{_____________o___________}

74
CAPITULO 8 DEFINIR DATOS

El ensamblador traduce un programa generando un modulo objeto. El modulo objeto solo contiene
instrucciones máquina; también contiene espacio para mantener los datos que el programa usará. Dentro
de un programa, debes apartar locaciones de memoria para cada datos que vas a usar. Puedes hacer esto
con las directivas para definir datos que describen los campos de datos en el ensamblador. La directivas
definen bytes, palabras, palabras dobles y palabras cuádruples, etc.

Nuevos Términos

Directiva definir dato. Una de las varias directivas que definen campos de datos usados por un
programa; las directivas son DB, DW, DD, DQ, y DT.

Constante. Un campo dato cuyo valor no cambia cuando el programa se ejecuta.

Variable. Un campo de dato cuyo valor puede cambiar cuando el programa se ejecuta.

Notación científica. Un modo de escribir números muy grandes con una notación pequeña, generalmente
multiplicada por una potencia de 10.

Atributo. Una característica particular de un campo dato (TYPE, LENGTH, SIZE, SEG y OFFSET) o
una etiqueta (TYPE, SEG, OFFSET).

Operador. Un símbolo usado durante el ensamblamiento para afectar el valor de un operando.


Etiqueta (Label). Un nombre definido con un atributo especifico.

Equate. Una definición (usando la directiva EQU) que da a un símbolo un valor especifico durante el
ensamblamiento de un programa.

8.1 Constantes y Variables.

Existen dos tipos de datos que puedes usar en un programa: constantes y variables. La diferencia es
que las constantes retienen un solo valor que no puede cambiar en un programa y las variables tienen un
valor que si cambia.

Cuando defines una constante, debes especificar que su valor será inicializado por el ensamblador.
Cuando defines una variable, debes especificar que su valor no será inicializado.

Aquí hay dos ejemplos usando la directiva DB:

COSTO DB 100
TOTAL DB ?

Ambos ejemplos apartan un byte de memoria. El primer ejemplo define una constante llamada COSTO
que es inicializada con un valor de 100 decimal. El segundo ejemplo define una variable llamada
TOTAL. Cuando se especifica un ?, el ensamblador no inicializará el valor de TOTAL.

75
Cuando el ensamblador traduce estos ejemplos, aparta un byte de memoria a COSTO y otro byte a
TOTAL. El byte que contiene COSTO tendrá un valor de 100 por otro lado TOTAL no tendrá ningún
valor especifico. Mejor asegúrate que contenga un valor cuando el programa inicie.

Es importante que entiendas que las variables y las constantes son entidades lógicas de programación.
Escoges datos que serán constantes o variables basado en los requerimientos del diseño del programa. a
diferencia de otros lenguajes el ensamblador no sigue estas reglas. Yo puedo cambiar el valor de una
constante; y también puedo usar una variable que no ha sido inicializada aún (aunque contenga basura).
Así, para mantener la cosas coherentes, asegurémonos que cada campo de datos definido sea tratado
puramente como una variable o una constante. Cuando el ensamblador inicialice un campo dato, no lo
modifiques dentro del programa, cuando definas una variable, no hagas que el ensamblador la inicialice;
inicializala dentro del programa. Algunas veces es más fácil inicializar una variable como una constante,
sin embargo está es un práctica pobre de programación.

Por ejemplo la variable constante COSTO mantendrá un valor de 100 a través de todo el programa. Por el
contrario TOTAL puede tomar el valor que necesite cuando lo necesite.

8.2 Tipos de Datos.

Cuando defines los campos de datos, puede usar uno de varios bloques constructores. Por ejemplo,
podrías una variable que consista de 200 palabras, o podrías definir una constante que consista de 50
bytes, cada uno inicializado a 0. Existen varios bloques constructores que puedes usar, cada uno para
tamaño diferente. Se muestran en la tabla 8-1.

Tabla 8-1 Bloque constructor Tamaño (en bytes)


BYTE 1
WORD 2
DWORD 4
QWORD 8
TBYTE 10

Para definir un dato, primero debes decidir que tipo de datos vas a guardar: BYTE, WORD, DWORD,
QWORD, ó TBYTE. Muchas de las veces podrás usar bytes y palabras. Los bytes son usados para
números pequeños o secuencias de caracteres. Las palabras son usadas para guardar números grandes.
Las palabras dobles, palabras cuádruples y 10-bytes (TBYTES) son usados en cálculos hechos por el
coprocesador matemático.

Las palabras y palabras dobles son usadas para guardar direcciones. Cuando trabajas con
desplazamientos, puedes guardarlos en una palabra. Cuando trabajas con direcciones completas, puedes
guardarlas en una palabra doble. La dirección de segmento en los 16 bits de más a la izquierda de la
DWORD y el offset en los 16 bits de más a la derecha.

8.3 Las Directivas para definir datos.

El siguiente par de secciones describen las directivas usadas para definir datos. Estas directivas son
DB, DW, DD, DQ y DT. Cada una de estas define un sólo tipo de dato como se muestra en la tabla 8-2.

76
Tabla 8-2 Directiva Tipo Descripción
DB BYTE byte
DW WORD palabra
DD DWORD palabra doble
DQ QWORD palabra cuádruple
DT TBYTE 10 bytes

Para definir un dato, usa la directiva apropiada. Pon el nombre del campo dato seguido del tipo de
directiva. Si vas a definir una variable, pon el símbolo ? en la parte del operando; si defines una
constante, especifica su valor en el operando. Por ejemplo:

TOTAL DB ?
ZERO DW 0

El primer ejemplo define una variable consistente de un byte, el segundo define una constante de tipo
palabra con un valor de 0.

Algunas veces querrás definir una variable que consista de más de un sólo bloque constructor. Por
ejemplo si quisieras una lista de 5 bytes, puedes definir un campo dato que contenga cinco ? en el
operando separados por comas. Aquí hay un ejemplo que define una variable de cinco palabras dobles.

LISTA DD ?,?,?,?,?

Para definir una constante de más de un bloque constructor especifica una lista de valores. Por ejemplo,
aquí hay un estatuto que define una constante llamada TABLA, consistente de una lista de 10 números,
cada uno almacenado en un byte.

TABLA DB 50,100,25,75,99,104,23,45,101,87

Puedes especificar valores en decimal, hexadecimal y binario. Por ejemplo aquí hay un estatuto que
define una constante, llamada MIX consistente de tres palabras. La primera guarda 100 decimal, la
segundo F6AEH, y la tercera guarda 1001101B.

MIX DW 100,0F6AEH,1001101B

Puedes inicializar constantes para guardar caracteres. Simplemente pones los caracteres dentro de
comillas dobles o simples. Aquí hay dos ejemplos equivalente para la constante llamada STAR

STAR DB “*”
STAR DB ´*´

8.4 Definir valores repetidos

Puedes obtener campos de datos que contengan un valor repetido más de una vez. De hecho puedes
obtener una constante de 35 bytes, inicializada a puros “-”, o puedes obtener una variable consistente de
200 palabras.

Para hacer esto tienes que especificar el número de valores duplicados, con la palabra “DUP” seguida del
valor entre paréntesis. Por ejemplo, para definir una constante llamada GUIONES de 35 bytes, donde
cada byte sea igual a “-”, usa

77
GUIONES DB 35 DUP(“-”)

Para definir una variable llamada WLISTA consistente de 200 palabras, usa:

WLISTA DW 200 DUP(?)

Debes poner uno o más espacios entre el número de repeticiones y la palabra “DUP” por ejemplo lo
siguiente causa un error

WLISTA DW 200DUP(?)

Puedes crear constantes más complejas combinando el DUP con otros valores. Por ejemplo la siguiente
directiva define una lista de palabras conteniendo los números 1, 2, 3, (10 ceros), 99, 100.

WLISTA DW 1,2,3,10 DUP(0),99,100

También puedes usar un DUP dentro de otro DUP. Por ejemplo, digamos que vamos a definir una tabla
de 100 números cada uno de longitud word. Los números consisten de la secuencia 0, 0, 0, 0, 0, 6, 7, 8, 9,
10 repetidos 10 veces:

WLISTA DW 10 DUP(5 DUP(0),6,7,8,9,10)

8.5 Referenciar campos de datos.

Cuando usas un nombre de un campo de dato dentro de una instrucción, el nombre se refiere al
offset del primer bloque constructor del campo de datos. Por ejemplo, esta directiva DB define una
variable llamada LISTA, consistente de tres bytes:

LISTA DB ?,?,?

Dentro de una instrucción, el nombre LISTA se refiere al offset del primer byte de la variable. Por
ejemplo, digamos que la variable inicia en un offset 120H del segmento de datos, la instrucción:

MOV AH,LISTA

es equivalente a la instrucción

MOV AH,DS:120H

En ambos casos, el efecto de la instrucción es mover el contenido del primer byte de LISTA al registro
AH.

Si quieres bloques subsecuentes del campo de datos, usa una dirección consistente de un valor adicionado
al nombre de la variable. Por ejemplo, para copiar el segundo byte de LIST al registro AH, use un
estatuto como

MOV AH,LIST+1

En general, puedes adicionar un valor al nombre de cualquier campo de datos de esta manera. Por
ejemplo, aquí hay una variable consistente de 500 bytes

78
BLISTA DB 500 DUP(?)

Puedes referirte al primer byte como BLISTA, al segundo como BLISTA+1, al 50 como BLISTA+49, al
188 como BLISTA+187, etc.

Considere la variable WLISTA con 500 palabras.

WLISTA DW 500 DUP(?)

Te puedes referir a la primer palabra como WLISTA, a la segunda como WLISTA+2, a la tercera como
WLISTA+4, etc.

Si tienes campos dato consistentes de bloques grandes, puedes referirte a ellos de manera análoga. Aquí
hay tres variables de tipo palabra doble, palabra cuádruple y 10 bytes.

DLISTA DD 500 DUP(?)


QLISTA DQ 500 DUP(?)
TLISTA DT 500 DUP(?)

Por ejemplo, para referirte a la segunda palabra doble de DLISTA, usa DLISTA+4; para referirse a la
tercera palabra cuádruple de QLISTA, use QLISTA+16; para referirte al grupo 13 de TLISTA usa
TLISTA+120.

Un modo conveniente de procesar todos los elementos de tales listas es usar un registro índice. Por
ejemplo para procesar cada palabra doble, puedes usar el operando
DLISTA[DI]

Inicie DI con el valor 0 e incremente DI en 4. Así DI tomará los valores 0, 4, 8, 16...

DLISTA[DI] toma los valores

DLISTA+0, DLISTA+4, DLISTA+8, DLISTA+16...

8.6 Usar la directiva DB con caracteres.

La directiva DB define campos dato para guardar bytes. Cada byte puede mantener un carácter o un
número. Puedes definir constantes que contengan un carácter, especificando el carácter dentro de comillas
dobles o simples. Si quieres definir más de un carácter consecutivo, puede especificar una serie de
caracteres:

BIRD DB “R”,”o”,”b”,”i”,”n”

Para ahorrar espacio el ensamblador te permite especificar una secuencia de caracteres entre comillas
como un conjunto. Cada carácter es almacenado en un byte de la constante:

BIRD DB “Robin”

El ensamblador no te permite almacenar bytes que contengan comillas, a menos que utilices una comillas
diferentes para la expresión, por ejemplo:

79
SCOMILLA DB “Don´t forget to ”
DCOMILLA DB ‘say “Hello” to the litte Nipper for me.’

Ocasionalmente querrás usar un carácter que no tenga un símbolo asociado a el. En este caso especifica el
código ASCII del carácter, en decimal o hex, sin comillas. Ejemplo:

MSG DB “Hola, ¿como estas viejo?”, 0DH, 0AH

También puedes combinar caracteres definidos por comillas y código ASCII. Veamos un ejemplo. Vamos
a definir una constante similar a lo anterior, con la excepción que adicionaremos al final un $. (Hay
ocasiones en que el símbolo de pesos es útil como marcador de fin de línea).
Puedes especificar el último carácter como $ o en su código ASCII 24H (36 decimal). Aquí están los
estatutos equivalentes:

MESSAGE DB “Hola, ¿como estas viejo?, 0DH, 0AH, “$”


MESSAGE DB “Hola, ¿como estas viejo?, 0DH, 0AH, 24H
MESSAGE DB “Hola, ¿como estas viejo?, 0DH, 0AH, 36

8.7 Usar la Directiva DB con números.

Sabemos que cuando quieres almacenar números puedes usar bytes, usualmente de dos maneras
aritméticas. Números con signo o sin signo.

Si usas números sin signo, estarán en el rango 0 a 255; si usas números con signo, estarán entre -128 a
127. Estos rangos representan los valores más pequeño y más grandes que puedes almacenar en un byte (
8 bits). Si quieres usar números que están más allá de esos valores, debes usar unidades de
almacenamiento más grandes (palabras, palabras dobles, etc.) Cuando especificas números puedes usar
decimales, hex, o binarios. Sólo asegúrate que los números no sean muy grandes o muy pequeños. La
información relevante esta reunida en la tabla 8-3

Veamos un par de ejemplos. El primero muestra una constante que esta inicializada con una secuencia de
números sin signo

URIGHT DB 0,34,0FFH,47,1001101B

Aquí hay una constante que es inicializada con una secuencia de números con signo.

SRIGHT DB 0, -34, 127, -128, -80H, -100010B

Aquí hay dos estatutos que pueden causar errores. el primero usa números sin signo que ocupan mucho
espacio; el segundo usa números con signo que usan mucho espacio también.

UWRONG DB 256,123H,101010101010B
WRONG DB -3333, 999, -0FFFH, -111110000011111B

(Podrías preguntar ¿Como le hace el procesador para saber si estoy usando números con signo o sin
signo?. en algunos casos esto no importa, en otros es tú responsabilidad, usar instrucciones diferentes
para cada tipo.)

80
Tabla 8-3 Tipo de número Rango permitido
sin signo 0 a 255
con signo -128 a 127

8.8 Usar la directiva DW con caracteres.

La directiva DW define campos datos del tamaño de una palabra. Cada palabra puede mantener dos
caracteres, un número o un offset.

Por ejemplo el siguiente estatuto define una constante llamada DOSCAR a un valor de “13”:

DOSCAR DW “13”

En este caso, el byte izquierdo de DOSCAR esta puesto al valor del código ASCII 1, el byte derecho esta
puesto al valor del código ASCII de “3”. Así DOSCAR contiene 3133H.

Si especificas un sólo carácter, el ensamblador pone el lado izquierdo a 00H y el lado derecho al valor
del código ASCII del carácter. Por ejemplo el estatuto

UNCAR DW “1”

Pone la constante UNCAR a 0031H.


No puedes usar la directiva DW para definir una constante consistente de una secuencia de caracteres. Por
ejemplo obtendrás un error si tratas de definir palabras como

WCARAC DW “12345”

8.9 Usar la Directiva DW con números.

Como los bytes, las palabras pueden guardar números con signo y sin signo. La diferencia principal
es la longitud la palabra puede guardar valores más grandes que el byte.. Los rangos de ambos tipos se
muestran en la tabla 8-4.

Veamos un par de ejemplos. El primero muestra una constante que esta inicializada con una secuencia de
números sin signo

URIGHT DW 0,34999,0FFAEH,47,101101001101B

Aquí hay una constante que es inicializada con una secuencia de números con signo.

RIGHT DW 0, -31999, -800H, 32767, -128, -100010001000B

Aquí hay dos estatutos que pueden causar errores. el primero usa números sin signo que ocupan mucho
espacio; el segundo usa números con signo que usan mucho espacio también.

UWRONG DW 65536,0FFFABH,101010101010101010101010B
WRONG DW -876543, 99999, -0FFFFFH, -1111100000111110000B

81
Tabla 8-4 Tipo de número Rango permitido
sin signo 0 a 65535
con signo -32768 a 32767

8.10 Usar la Directiva DW con offsets.

Una palabra que contiene 16 bits puede contener un offset. Esto es útil cuando necesitas trabajar con
direcciones. Típicamente esta podría ser una dirección de un campo dato o un procedimiento. Para
inicializar una palabra con un offset, especifica el nombre del offset del campo dato que quieras. Por
ejemplo aquí hay estatutos que definen campos dato. El primero es una variable llamada LISTA,
consistente de 100 bytes, el segundo es una constante llamada LOFFSET que contiene el desplazamiento
del primer byte a LISTA.

LISTA DB 100 DUP(?)


LOFFSET DW LISTA

También puedes especificar el nombre de un procedimiento. Por ejemplo, si DISPLAY es el nombre de


un procedimiento, puedes usar

OFFSET DW DISPLAY

Por supuesto el offset es relativo al inicio del segmento en el cual aparece. (Los campos dato estarán en el
segmento de datos o extra; los procedimientos estarán en el segmento de código).

8.11 Usar la Directiva DD con caracteres.

La Directiva DD define campos de datos del tamaño de un palabra doble. Cada doble palabra puede
almacenar dos caracteres o un número o una dirección completa. El ensamblador te permite definir una
doble palabra que tenga uno o dos caracteres. Los caracteres definidos estarán al inicio de la primer
palabra. Los otros bytes serán 0.
Por ejemplo en estatuto

UNCAR DD “A”

aparta una doble palabra que contiene 41000000H. El estatuto

DOSCAR DD “AB”

Apartará una doble palabra que contiene 41420000H.


No puedes especificar más de dos caracteres. Por ejemplo la siguiente instrucción causará un error:

BADCHAR DB “ABC”

82
8.12 Usar la directiva DD con números.

Las palabras dobles son usadas para guardar números. Desafortunadamente, sin mucha
programación extra, el procesador sólo puede trabajar con bytes y words. Sin embargo un coprocesador
matemático puede trabajar con números más grandes.

Primero las dobles palabras pueden guardar números en el rango -2147483648 a 2147483647. Segundo
las palabras dobles pueden guardar un número llamado de punto flotante.

Cuando usas palabras dobles, puedes almacenar números positivos y negativos en punto flotante en el
rango aproximado de 10-38 a 1038 .

La tabla 8-5 muestra el rango de números que puedes almacenar en una palabra doble.

Tabla 8-5 Tipo de número Rango permitido


con signo -2147483648 a 2147483647
punto flotante positivos y negativos: 10-38 a 1038

El ensamblador considera los números de notación científica como los números de punto flotante.
Aquí hay un par de ejemplos. El primero muestra una constante que es inicializada a una secuencia de
números con signo

SRIGHT DD 0,83,123456789,0F5BCDH,11110000111100001111B

Aquí hay una constante incializada a una secuencia de números en punto flotante

FRIGHT DD 0,2.178,-3.141592,3.12E10,56.1E-12,1E-10

Aquí hay dos estatutos que pueden causar un error. El primero utiliza números con signo que usan mucho
espacio; el segundo usa punto flotante fuera de rango:

SWRONG DD 12345678901,-999999999999
FWRONG DD 1.23E100,1E-100

en el último ejemplo, el segundo número es muy pequeño; 1x10 -100. En lugar de desplegar un mensaje
de error, el ensamblador tratará este número como 0.

8.13 Usar la Directiva DD con direccionamiento.

Como explique en el capitulo 4, una dirección completa consiste de una dirección de segmento y un
offset. Una palabra doble tiene suficiente espacio (32 bits) para guardar una dirección completa. Esto es
útil cuando necesitas usar una dirección de un dato o un procedimiento, si necesitas sólo el offset puedes
guardarlo en la palabra.

Para inicializar un doble palabra con una dirección completa, especifica el nombre del campo cuyo offset
necesitas. Por ejemplo, aquí hay dos estatutos que definen dos campos de datos. El primero es la variable
llamada LISTA que consiste de 100 bytes, la segunda es una constante llamada LDIREC que contiene el
segmento y el offset del primer byte de LISTA.

LISTA DB 100 DUP(?)


83
LDIREC DD LISTA

También puedes especificar el nombre de un procedimiento. Por ejemplo, si DISPLAY es un


procedimiento, podrías usar

DDIREC DD DISPLAY

NOTA: El ensamblador pone el offset en la izquierda de la palabra doble y el segmento de dirección a la


derecha.

8.14 Usar la directiva DQ.

La directiva DQ define una palabra cuádruple, y puede guardar un número o uno o dos caracteres.
En la práctica las palabras cuádruples son usadas para guardar número del coprocesador matemático. Una
palabra cuádruple puede almacenar números con signo o de punto flotante. Esto lo muestra la tabla 8-6.

Tabla 8-6 Tipo de número Rango permitido


con signo -922337203685477808 a 922337203685477807
punto flotante positivos y negativos: 10-308 a 10308

8.15 Usar la directiva DT.

La directiva DT define datos que constan de 10 bytes. Para este tipo de datos se aplican las mismas
reglas que para las palabras dobles y cuádruples. La directiva DT es usada normalmente para guardar
datos del coprocesador matemático.

Un grupo de 10 bytes puede guardar dos tipos de números: punto flotante y el “decimal empacado”. La
tabla 8-7 muestra el rango que puede almacenar cada grupo de valores

Tabla 8-7 Tipo de número Rango permitido


punto flotante positivos y negativos: 10-4932 a 104932
decimal empacado -999999999999999999 a 999999999999999999

8.16 Direccionar campos sin nombre.

No tienes que asignar un nombre a cada campo que definas, puedes referirte a cualquier locación
dando la dirección relativa a un nombre previo. Por ejemplo, considere la siguiente definición de datos:

TABLA DB 1,2,3,4,5
DB ?,?
DB 8,9,10

Este estatuto define una tabla de 10 bytes. Los primeros cinco bytes y los últimos tres están inicializados
a valores específicos. Los bytes 6 y 7 están definidos como variables. Esta combinación de definiciones
es útil cuando tienes campos con parte variable y constante.

84
Lo importante es que el ensamblador aparta espacio para datos en el mismo orden en que los encuentra.
Ya sea que puedo referirme a los bytes separados de la tabla como TABLA, TABLA+1, TABLA+2, etc.
También puedes referirte a bytes subsecuentes de la misma manera, aunque no estén declarados en la
misma línea.

Por ejemplo puedes referirte a los bytes 6 y 7 como TABLA+5 y TABLA+6, y puedes referirte a los
siguientes bytes como TABLA+7, TABLA+8 y TABLA+9. En la práctica también podrías usar una
expresión como esta

TABLA[SI]

Para referirte a los bytes de la tabla.

Debes tener cuidado en mantener las referencias adecuadas. En el caso anterior, debes tener cuidado de
que el valor de SI no exceda a 9. Si lo hace tu programa puede hacer referencia a bytes equivocados. Por
ejemplo si SI tiene un valor de 100, la expresión

TABLE[SI]

hará referencia a un byte que esta 100 bytes más allá de tabla, sin importar si está en otro segmento.

8.17 Atributos.

Un atributo describe una característica particular de un dato. Cada dato puede tener cinco diferentes
atributos, mostrados en la tabla 8-8. Aparte de los datos, la única entidad que también tiene atributos son
las etiquetas, que serán vistas después. Una etiqueta tiene tres atributos.

Tabla 8-8 Los cinco atributos de un campo dato.

TYPE
LENGTH
SIZE
SEG
OFFSET

Cada atributo describe una característica de un dato. El atributo TYPE es el número de bytes reservados
para cada bloque constructor en el campo del dato. Por ejemplo, si el campo dato consiste de bytes, su
tipo es 1; si el campo dato consiste de palabras, su tipo es 2; si el campo dato consiste de palabras
cuádruples su tipo es 8, etc.

El atributo LENGTH es el número total de bloques constructores de que consta el campo dato. Por
ejemplo, una variable que consiste de 200 palabras tiene una longitud de 200; una constante de 50 bytes
tiene una longitud de 50.

El atributo SIZE es el número total de bytes reservados para el campo de datos. Por ejemplo, una variable
que consiste de 200 palabras tiene un tamaño de 400 bytes; una constante que consta de 50 bytes tiene un
tamaño de 50 bytes.

85
También puedes obtener el tamaño de un campo multiplicando el tipo por la longitud (TYPE x
LENGTH).
Los últimos dos atributos de un campo dato son SEG y OFFSET. El atributo SEG es la dirección de inicio
de segmento en el cual el dato está situado. Como los datos usualmente están en el segmento de datos o
extra, el atributo SEG tiene la misma dirección que los registros DS o ES. El atributo OFFSET es la
dirección del primer byte del campo de datos.

Los atributos SEG y OFFSET juntos describen la dirección completa de un dato. Estos atributos son
útiles cuando quieres usar la dirección de un dato, en lugar de su valor.

Aquí hay algunos ejemplos de definiciones de datos, con la descripción de algunos de sus atributos.

TEMP DW 200 DUP(?)


CEROS DB 50 DUP(0)
VALOR DQ ?

El primer ejemplo define una variable llamada TEMP que consiste de 200 palabras. TEMP tiene un tipo
2, una longitud de 200, y un tamaño de 400.

El segundo ejemplo define una constante llamada CEROS que consiste de 50 bytes inicializados todos a
0. CEROS tiene un tipo de 1, una longitud de 50, y un tamaño de 50.

El tercer ejemplo define una variable llamada VALOR consistente de una palabra cuádruple. VALOR
tiene un tipo de 8, una longitud de 1 y un tamaño de 8.

8.18 Operadores que usan los atributos: TYPE, LENGTH, SIZE, SEG Y OFFSET.

Un operador es un símbolo usado en el ensamblamiento para afectar el valor de un operando. Como


las directivas, los operadores son reconocidos por el ensamblador, y no corresponden a lenguaje máquina.
El ensamblador ofrece varios tipos de operadores. En este capítulo describiré los más útiles aplicables a
los campos de datos.

Existen cinco operadores que te permiten hacer referencia a los atributos de un campo de datos.

TYPE LENGTH SIZE SEG OFFSET

Para usar estos operadores en un operando, coloca el operador antes del nombre del campo dato. El
ensamblador evaluara la expresión y substituirá su valor a la instrucción máquina generada.

Aquí hay algunos ejemplos. Digamos que LISTA es una secuencia de palabras definidos como:

LISTA DW 100 DUP(?)

considere la instrucción

MOV AX,TYPE LISTA

El ensamblador substituirá la expresión “TYPE LISTA”, en esta caso, el número 2. Así la instrucción
máquina tendrá el mismo efecto que

86
MOV AX,2

La instrucción

MOV AX, LENGTH LISTA

es lo mismo que

MOV AX,100

y la instrucción

MOV AX,SIZE LISTA

es lo mismo que

MOV AX,200

Para copiar el desplazamiento de LISTA use esto

MOV AX,OFFSET LISTA

Los operadores de atributos hacen los programas más fáciles de comprender. Por ejemplo la instrucción

MOV AX,LENGTH LISTA

es más significativa que

MOV AX,100

Los operadores son más efectivos cundo modificas tus programas. Si por alguna causa aumentaras la
longitud de LISTA de 100 a 250, no tendrías que cambiar ningún estatuto que hiciera referencia a la
longitud de la lista con el uso del operador LENGTH.

8.19 Operadores Aritméticos.

Existen varios operadores que pueden usarse para hacer aritmética simple con las expresiones de
operandos. Estos operadores están en la tabla 8-9.

Aquí hay algunos ejemplos:

MOV AX,TABLA+10
MOV AX,TABLA+2*(100+66)
TABLA DB 2*1024 DUP(?)
CONTA1 DB 97/10
CONTA2 DB 97 MOD 10

Tabla 8-9 Operadores aritméticos


Operador Significado
+ adición
87
- resta
* multiplicación
/ división
MOD división modular

Es importante que comprendas que los operadores aritméticos son usado sólo para especificar cálculos
que serán traducidos por el ensamblador. Estos operadores no ejecutan cálculos matemáticos cuando el
programa corre.

Los operadores aritméticos, como los atributos, hacen los programas más fáciles de comprender. Por
ejemplo, digamos que quieres definir una variable llamada BUFFER, que tenga 12 Kb. 12K es igual a
12,288. Así si usas

BUFFER DB 12288 DUP(?)

el significado del número es obscuro. Si usas

BUFFER DB 12*1024 DUP(?)

la definición habla por si misma.

Puedes usar los operadores aritméticos con los operadores de atributos. Por ejemplo aquí hay definiciones
de dos variables. LISTA y GLISTA. GLISTA está definida del doble del tamaño de LISTA.

LIST DB 56 DUP(?)
BLISTA DB 2*(SIZE LISTA) DUP(?)

8.20 El operador PTR (pointer).

Cuando haces referencia a un campo de dato, el ensamblador checa el tipo y se asegura que está de
acuerdo con lo que estas tratando de hacer. Por ejemplo digamos que una instrucción copia el valor de la
variable llamada VALOR al registro AH.

MOV AH,VALOR

Si AH tiene un tamaño de 8 bits. VALOR debe ser del mismo tamaño.

Si VALOR consiste de bytes todo está bien. Sin embargo si VALOR, consiste de un tipo bloque
constructor más grande (digamos palabra o palabras dobles), el ensamblador encontrará un error. El
mensaje de error será parecido a:

Operand types must match


Illegal size for item

Muchas de la veces el error es tuyo. Sin embargo, ocasionalmente quieras suprimir esta condición del
ensamblador. Por ejemplo, digamos que defines TOTAL como una variable consistente de una palabra:

TOTAL DW ?

88
Normalmente, tratas a TOTAL como una palabra. Por ejemplo, puedes copiar el contenido de TOTAL a
un registro de 16 bytes (AX por decir algo).

MOV AX,TOTAL

Sin embargo, puedes accesar los dos bytes de TOTAL, separadamente. Para hacer esto, usa el operador
PTR para decirle al ensamblador que suprima el default del tipo para esa instrucción. Especifica el tipo
que desees después de PTR, y después el nombre del campo dato.

El nombre PTR significa apuntador; el operador PTR le dice al ensamblador que pretenda que el
operando es de un tipo particular de dato. Los posibles tipos que puedes usar con el operador PTR están
en la tabla 8-10.

Tabla 8-10 Tipo Significado


BYTE byte
WORD palabra
DWORD palabra doble
QWORD palabra cuádruple
TBYTE grupo de 10 bytes

Aquí hay dos ejemplos:

MOV BH,BYTE PTR TOTAL


MOV CH,BYTE PTR TOTAL+1

La primera instrucción le dice al ensamblador que permita copiar el byte en la locación TOTAL dentro
del registro BH. La segunda instrucción copia el siguiente byte en el registro CH. Normalmente, no
puedes copiar parte de una variable de 16 bits a un registro de 8 bits. Por ejemplo, si TOTAL es un
palabra, la siguiente instrucción causa un error.

MOV BH,TOTAL

Aquí hay otro ejemplo. Digamos que defines una variable llamada PAR consistente de dos bytes:

PAR DB ?,?

Si quieres copiar los dos bytes de PAR al registro AX, normalmente el ensamblador no te permite copiar
lo siguiente:

MOV AX,PAR

Ahora, si usas el operador PTR suprime el tipo de PAR

MOV AX,WORD PTR PAR

Antes de usar el operador PTR, hay una cosa importante que debes entender. En el capítulo 3 mencione
que el ensamblador y el procesador almacenaban los dos bytes de una palabra en orden inverso. DE
hecho si consideramos las siguientes definiciones:

X DB “AB”
Y DW “AB”

89
La constante X consiste de dos bytes. El código ASCII de “A” es 41H y el de “B” es 42H, así X está
inicializada a 4142H. La constante Y consiste de una palabra, así que los bytes están en orden inverso, Y
está inicializada como 4241H.

Normalmente, esto no representa problemas. El procesador sabe cuando guardo los valores en orden
inverso. Sin embargo, cuando usas el operador PTR para suprimir el tipo de un campo, el procesador trata
a los datos como si fueran del nuevo tipo. Si accesas la palabra como bytes separados estos estarán en
orden inverso.

Similarmente, cuando accesas 2 bytes como si fueran una palabra el procesador los invierte. La solución
es usar el operador PTR con cuidado. Algunas veces puedes obtener lo que quieres de maneras diferentes.
Por ejemplo, si realmente quieres copiar los dos bytes de PAR a AX, debes usar instrucciones separadas
para copiar el primer byte en AH y el segundo en AL.

MOV AH,PAR
MOV AL,PAR+1

8.21 La Directiva LABEL.

La directiva LABEL te permite definir nombres con atributos específicos. El formato es

nombre LABEL tipo

El nombre no ocupa espacio en lenguaje máquina del programa. En su lugar, permite hacer referencia de
un modo alternativo a una locación particular de tu programa. Un nombre definido de este modo es una
etiqueta (LABEL).

Aquí hay un ejemplo:

BNAME LABEL BYTE


WNAME DW 100 DUP(?)

El primer estatuto define una etiqueta BNAME que apunta a un campo dato consistente de bytes. El
segundo estatuto define un campo dato WNAME consistente de palabras.

Como la directiva LABEL no ocupa espacio en lenguaje máquina en el programa, BNAME y WNAME
tiene el mismo desplazamiento. Así puedes usar el nombre BNAME para accesar el campo dato como
bytes y ,el nombre WNAME para accesar el campo dato como palabras. Esto es como si el campo dato
tuviera dos nombres.

De hecho, para copiar el contenido de la segunda palabra de WNAME al registro AX, usa

MOV AX,WNAME+2

Sin embargo, si quisieras copiar la primera mitad de esa palabra (1 byte) al registro AH, no puedes usar

MOV AH,WNAME+2

90
debido a que el ensamblador no te permite referenciar bytes dentro de campos dato que consistan de
palabras, debes usar el nombre alterno:
MOV AH,BNAME+2

Esto es muy similar al apuntador PTR para suprimir el tipo:

MOV AH,BYTE PTR WNAME+2

Cuando defines una etiqueta, puedes usar cualquiera de los tipos BYTE, WORD, DWORD, QWORD o
TBYTE. También puedes usar la directiva LABEL para definir etiquetas dentro del segmento de código.
El programa puede usar estas etiquetas para saltar de una instrucción a otra.

8.22 La Directiva EQU (igual a).

La directiva EQU permite definir símbolos que tiene un valor especifico mientras el programa es
ensamblado. El formato es

nombre EQU expresión

Por ejemplo, la siguiente directiva le dice al ensamblador que considere el símbolo K equivalente a 1024:

K EQU 1024

Un igual es útil cuando se tiene un programa que usa el mismo valor repetidamente. Puedes usar un igual
para asignar el valor al símbolo que será tomado así en todo el programa
Por ejemplo, digamos que defines varios campos de datos con valores que son múltiplos de 1K (1024):

CAMPO1 DB 1024 DUP(?)


CAMPO2 DB 2*1024 DUP(?)
CAMPO3 DB 3*1024 DUP(?)

Si defines al símbolo K como lo hicimos antes, puedes usar

CAMPO1 DB K DUP(?)
CAMPO2 DB 2*K DUP(?)
CAMPO3 DB 3*K DUP(?)

Es importante que entiendas que los iguales no generan código máquina, sólo son abreviaciones que el
ensamblador tomará en cuenta cuando haga su trabajo.
Los iguale son particularmente útiles cuando tienes estatutos que dependen de valores fundamentales.
Usando iguales, puedes representar los valores de manera simbólica. Si estos valores necesitan cambiarse,
lo haces y reensamblas el programa.

Por ejemplo, considere el siguiente igual, el cual pone LSIZE como igual a 100:

LSIZE EQU 100

Dentro del programa puede haber varios estatutos que dependan de ese valor, por ejemplo

WNAME DB LZISE DUP(?)


91
WLISTA DB LSIZE DUP(?)
MOV AX,LSIZE
MOV BX,LSIZE

Cuando ensamblas el programa el ensamblador substituye todos los valores del igual por el valor
respectivo. Sin embargo si decides que el valor nuevo de LSIZE es 200 sólo tienes que cambiar el igual y
reensamblar el programa sin cambiar ninguna otra línea de código.

8.23 Reglas para usar EQUATES.

El nombre de un EQU debe ser único y no debe ser una palabra reservada. La expresión puede ser
un número sin signo entre 0 y 65,535. Puedes usar operadores aritméticos y de atributos, y también
puedes especificar el número decimal, hex o binario. El binario es usado particularmente para especificar
patrones de bits. Aquí hay algunos ejemplos:

MAX_VAL EQU 157


K EQU 1024
TEN_K EQU 10*K
ESTO EQU 1000011110100110B
HEXVAL EQU 0AF3H
LTIPO EQU TYPE LISTA

Debes asegurarte que el ensamblador comprenda cualquiera de los nombres que usas. Esto es, en un
ejemplo use “10*K”, el nombre K debe estar definido antes. Similarmente LISTA debe estar ya
especificado antes de hacer mención de el, o en otro caso ocurrirá un error.

Algunos ensambladores te permiten especificar valores no numéricos para guardar secuencias de


caracteres. Sin embargo checa la documentación del ensamblador para ver si esto se permite. Por ejemplo
podrías poner un estatuto como el siguiente:

MESSAGE EQU “Hola, ¿como estas viejo?”


SALIDA DB MESSAGE

Si necesitas instrucciones igual dentro de tu programa ponlas al comienzo del programa en una sección
separada, para facilitar su lectura.

Algunos iguales pueden aparecer en el segmento de datos. Estos se explican en la siguiente sección.

8.24 El contador de locaciones: La directiva $ y la directiva ORG.

El ensamblador mantiene un valor llamado el contador de locaciones que contiene el offset de la


siguiente locación disponible. Cuando el ensamblador empieza a procesar el programa, el contador de
locación está en 0. Cada vez que el ensamblador aparta espacio de memoria, para datos o instrucciones, el
valor del contador de locaciones se incrementa.

Veamos un ejemplo. El ensamblador está procesando un programa y el contador de locaciones está en 0.


El ensamblador encuentra la directiva

92
DB 32 DUP(“STACK---”)

y aparta 100H bytes de almacenamiento. El contador de locaciones tiene el valor 100H.

Dentro del programa, puedes hacer referencia al valor actual del contador de locaciones usando el
símbolo $, por ejemplo, si defines el igual

CURRENT EQU $

El ensamblador definirá a CURRENT con el valor del contador de locaciones en el momento en que el
igual se procese.

El carácter $ es usualmente utilizado para encontrar la longitud de una secuencia de caracteres. Por
ejemplo, el siguiente estatuto define un campo dato llamado MSG y uno igual nombrado L_MSG, MSG
contiene una secuencia de caracteres; L_MSG contiene la longitud de MSG.

MSG DB “Hola”
L_MSG EQU $-MSG

En este ejemplo, la expresión $-MSG representa el valor actual del contador de locación menos el offset
de MSG -en otras palabras la longitud de MSG.

Esta técnica es útil debido a que permite cambiar la cadena sin cambiar el igual. También hace
innecesario que tengas que definir los caracteres contados como

L_MSG EQU 5

Si cambias a MSG, su longitud es automáticamente recomputada.

También puedes poner el valor del contador de locaciones a un valor exacto usando la directiva ORG
(origen). El formato es

ORG valor

Existe sólo un uso común para esta directiva: instruir al ensamblador para que empiece con un contador
de locaciones mayor que 0. Esto es importante cuando usas archivos COM. En tales casos, la siguiente
directiva es usada al inicio del programa:

ORG 100H

Esto le dice al ensamblador que inicie a ensamblar con un desplazamiento de 100H.

E principio puedes usar la directiva ORG para propósitos más exóticos. Por ejemplo, dejar un hueco de
diez bytes a la mitad del programa usando

ORG $+10

Sin embargo estas situaciones son raras.

8.25 Un ejemplo del uso de iguales.


93
El programa de la figura 8-1 ilustra como y donde se pueden usar los iguales.
Figura 8-1 Un programa que usa iguales

PAGE 58,132
;------------------------------------------------------------------------------------
; EQUATES
;
; Proposito:
; un ejemplo de un programa que usa iguales
; -----------------------------------------------------------------------------------

; Ajustar el titulo y el conjunto de instrucciones


TITLE EQUATES -programa que usa iguales
.286

;-------------------------------------------------------------- segmento STACK


SSEG SEGMENT STACK
DB 32 DUP(“STACK---”)
SSEG ENDS

;--------------------------------------------------------------- EQUATES
CR EQU ODH ; Código ASCII del retorno de carro
LF EQU 0AH ; Código ASCII de nueva línea
DISPLAY EQU 0001H ; Manejador de archivo para el mensaje

;--------------------------------------------------------------- segmento DATA


DSEG SEGMENT
MSG DB “Hola, ¿como estás viejo?”, CR, LF
L_MSG EQU $-MSG
DSEG ENDS

;--------------------------------------------------------------- segmento CODE


CSEG SEGMENT ‘CODE’
ASSUME CS:CSEG, SS:SSEG; DS:DSEG
PAGE
;----------------------------------------------------------------------------------
; MAIN (programa principal)
;
; Proposito:
; Desplegar un mensaje en la pantalla
;
; Entrada:
; -- ninguna --
;
; Salida:
; El mensaje es desplegado en la pantalla
;
; Procedimientos:
; -- ninguno --
;----------------------------------------------------------------------------------

; Procedimiento: MAIN
MAIN PROC FAR
; Salvar la direccion para poder regresar a DOS
PUSH DS
PUSH 0

; Actualizar el registro de segmento


MOV AX,DSEG
MOV DS,AX

94
; Desplegar el mensaje
MOV BX,DISPLAY
LEA DX,MSG
MOV CX,L_MSG
MOV AH,40H
INT 21H

; Regresar a DOS
RET

; Fin de procedimiento: MAIN


MAIN ENDP

; Fin de segmento de codigo


CSEG ENDS

;--------------------------------------------------------------- Fin de programa


END MAIN

{_____________o___________}

95
CAPITULO 9 LAS INSTRUCCIONES GENERALES

En este capítulo aprenderás cuales son las instrucciones más generales del ensamblador. Estas
instrucciones copian datos entre los registros, las locaciones de memoria y la pila. También conocerás la
instrucción más extraña de todas. La instrucción que no hace nada.

Nuevos Términos.

Fuente. Un operando al cual los datos serán copiados.

Destino. Un operando del cual son copiados los datos.

NOP. Un instrucción que no hace nada.

9.1 Operandos Fuente y Destino.

Como explique en el capitulo 5, un operando es la parte de una instrucción sobre el cual el


procesador actúa. Por ejemplo, en la instrucción

PUSH AX

el operando es AX. Muchas instrucciones tienen dos operandos, separados con una coma, como en el
ejemplo:

MOV AX,TABLA

Algunas de estas instrucciones involucran la copia de datos de un operando a otro. En tales casos, los
datos que son copiados son llamados fuente, y el lugar a donde los datos son copiados es llamado destino.
Como regla el fuente permanece sin cambio y el destino cambia.

Con las instrucciones que requieren de especificar fuente y destino, el destino siempre va primero,
seguido por una coma, y después el fuente. De hecho, en el ejemplo anterior, la fuente es TABLA y el
destino es AX. Aquí hay otro ejemplo:

ADD CX,DX

Esta instrucción adiciona el valor de la fuente DX al valor del destino CX. Sólo el destino cambia de
valor.

9.2 Copiar información: MOV.

96
La instrucción MOV copia datos de un lugar a otro. (Piensa que en lugar de MOV la instrucción es
un copy). MOV es la más importante de las instrucciones. Por ejemplo, de las 500 instrucciones en el
BIOS para un tipo particular de computadora el 34.6% (173) son instrucciones MOV.

El formato de la instrucción MOV es:

MOV destino,fuente

MOV copia los datos del operando de la derecha al operando de la izquierda, algo parecido a lo siguiente

MOV destino ← fuente

Los datos de la fuente no cambian, el destino puede ser un registro o una locación de memoria (un campo
dato); el fuente puede ser un registro, una locación de memoria o un valor inmediato.

Veamos algunos ejemplos de la instrucción MOV que copia valores inmediatos. La primera instrucción
copia un cero al registro AX, el segundo copia 200H al registro BP; el tercero copia el valor de 2*1024 a
un campo dato llamado TOTAL:

MOV AX,0
MOV BP,200H
MOV TOTAL,2*1024

Ahora, aquí hay algunas instrucciones que copian su valor a un registro. La primera instrucción copia el
valor de SI a BX; el segundo copia AX a ES; el tercero copia DS al campo dato SEGADDR:

MOV BX,SI
MOV ES,AX
MOV SEGADDR,DS

Finalmente, aquí hay instrucciones que copian el valor de una locación de memoria. La primera
instrucción copia el valor de TOTAL al registro AX; el segundo copia el valor de LISTA[SI] a BH:

MOV AX,TOTAL
MOV BH,LISTA[SI]

9.3 Restricciones del uso del comando MOV.

Está sección discute las restricciones de la instrucción MOV para copiar datos.
Primero, la longitud de la fuente debe ser igual que la longitud del destino. Por ejemplo, no puedes copiar
un byte a una palabra. La siguiente instrucción causa un error, cuando trata de copiar el registro AX (16
bits) al registro BL (8 bits):

MOV BL,DX

Similarmente, la siguiente instrucción no se permite a menos que SEGADDR sea una palabra y LISTA
consista de bytes:

MOV SEGADDR,DS
MOV BH,LISTA[SI]
97
La siguientes dos restricciones evitan que uses el registro IP como fuente o destino, y que uses el registro
CS como destino. Por ejemplo, la siguientes instrucciones causan un error:

MOV CS,8970H
MOV AX,IP

El porque de estas dos restricciones es la siguiente: Como explique en el capitulo 4, los registros IP y CS
son usados para rastrear el direccionamiento dentro del segmento de código cuando el programa se
ejecuta. El registro CS contiene la dirección de segmento y la instrucción IP contiene el offset de la
siguiente instrucción a ejecutar.

Similarmente, todos los registros de segmentos contienen valores importantes que deben ser protegidos.
Así, estas son las siguientes restricciones: No puedes copiar un valor inmediato a un registro de segmento
y no puedes copiar un registro de segmento a otro. Por ejemplo, no puedes usar los siguientes comandos:

MOV SS,1000H
MOV ES,DS

Existe una restricción más: No puedes copiar una dirección de segmento directamente a un registro de
segmento. Entonces al principio del programa no puedes direccionar el segmento de datos o extra
copiándolo directamente la dirección de segmento para DS y ES. Debes copiar la dirección a un registro
general, tal como AX y entonces copiarlo a DS o ES. Veamos lo anterior:

MOV AX,DSEG
MOV DS,AX

La última restricción del comando MOV es que no puedes copiar datos desde un campo dato (locación de
memoria) a otro. Por ejemplo, si CAMPO1 y CAMPO2 son dos campos de datos, entonces no puedes
copiar la instrucción:

MOV CAMPO1,CAMPO2

Tienes dos opciones para copiar un campo de datos a otro. Si el dato es corto, puedes copiarlo usando un
registro como intermediario:

MOV AX,CAMPO1
MOV CAMPO2,AX

La otra alternativa será usar las instrucciones MOVSB o MOVSW, las cuales serán explicadas en el
capítulo 15. La figura 9-1 muestra un sumario de las restricciones de la instrucción MOV

Figura 9-1 Restricciones


El fuente y el destino debe ser de la misma longitud

No puedes copiar
De o hacia el registro IP
Hacia el registro CS
De un valor inmediato a un registro de segmento
De un registro de segmento a otro registro de segmento
De una dirección de segmento directamente a otro registro de segmento
De una locación de memoria a otra

98
9.4 Intercambio de información: XCHG.

La instrucción XCHG intercambia información entre dos registros o entre un registro y un campo
dato. Aquí hay algunos ejemplos:

XCHG AX,BX
XCHG AX,SUM
XCHG BLISTA[SI]
XCHG CH,CL

EL primer ejemplo intercambia el contenido de AX y BX. El siguiente intercambia el contenido de AX


con el valor de un campo dato SUM. El tercer ejemplo intercambia los valores de BLISTA[SI] y DL. El
último ejemplo intercambia CH y CL.

Existe solo dos restricciones para usar la instrucción XCHG. Primero ambos operandos deben ser de la
misma longitud, ya sea bytes o words. En el ejemplo anterior, SUM debe ser una palabra y BLISTA debe
consistir de bytes. Las siguientes instrucciones causan un error debido a sus diferencias de tipo.
(Asumiendo que WLISTA consta de palabras):

XCHG AX,BL
XCHG WLISTA[SI],DL

La segunda restricción es que no puedes intercambiar el contenido de un segmento de registro. Por


ejemplo la siguiente instrucción causará un error

XCHG AX,ES

La instrucción XCHG no te permite intercambiar el contenido de dos campos dato. Por ejemplo si tienes
CAMPO1 y CAMPO2 ambos palabras, no puedes usar el comando:

XCHG CAMPO1,CAMPO2

Sin embargo hay varias maneras de lograr el mismo efecto. por ejemplo

MOV AX,CAMPO1
XCHG AX,CAMPO2
MOV CAMPO1,AX

9.5 Obtener un desplazamiento: LEA.

Como explique en el capítulo 8, puedes obtener un desplazamiento de un campo dato usando el


operador OFFSET. Por ejemplo, para copiar el desplazamiento del campo dato TOTAL al registro AX,
use

MOV AX,OFFSET TOTAL


99
Sin embargo, este método tiene una limitación. El campo dato sobre el cual actúa el OFFSET solo
funciona con el nombre; no funciona sobre un direccionamiento indexado como TOTAL[BX].

Obtener el direccionamiento de un campo dato es útil cuando pasas direcciones de un procedimiento a


otro. Afortunadamente, hay una instrucción que puede hacer el trabajo: LEA (la cual significa cargar la
dirección efectiva). Cuando usas la instrucción LEA no necesitas usar el operador OFFSET. De hecho,
LEA obtiene automáticamente el offset del segundo operando y se lo pasa al primero.

Por ejemplo, para copiar el offset de TOTAL[BX] a AX, use el comando:

LEA AX,TOTAL[BX]

Esta es la forma de usar la instrucción LEA: Digamos que necesitas que un procedimiento llamado
FIND_SUM sume una lista de números almacenados en palabras y regrese la suma. El procedimiento
espera a AX apuntado a la lista y BX apuntado al lugar para almacenar la suma, si defines esto último
como

LISTA DW 1,2,3,4,5,6,7,8,9,10
SUM DW ?

podrías usar la siguiente instrucción para llamar al procedimiento

LEA AX,LISTA
LEA BX,SUM
CALL FIND_SUM

9.6 Obtener una dirección completa: LDS y LES.

Las instrucciones LDS y LES están diseñadas para un propósito particular: copiar una dirección
completa (offset y dirección de segmento) a un par de registros. LDS significa “cargar el registro de
segmento de datos” y LES significa “cargar el registro del segmento extra”.

La instrucción LDS tiene dos operandos un registro y la dirección de una palabra doble. La palabra doble
debe contener la dirección completa (esto es, los 16-bits del offset, seguidos de los 16-bits de la dirección
de segmento). LDS copia este offset y la dirección de segmento a dos registros; el offset se copia al
registro que especifiques y la dirección de segmento se copia al registro DS.

Por ejemplo, digamos que T_ADDR es una palabra doble que guarda la dirección completa de TABLA y
T_ADDR está definida dentro del segmento de datos como sigue:

TABLA DB 100 DUP(?)


T_ADDR DD TABLA

La instrucción

LDS DX,T_ADDR

Copia el offset de TABLA a DX y el segmento de dirección de TABLA a DS, como lo harían las
siguientes instrucciones:

100
LEA DX,
MOV DS,SEG TABLA

La instrucción LES es similar a LDS. La única diferencia es que LES copia la dirección de segmento al
registro ES.

Podrías preguntarte cuando es pertinente usar estas instrucciones. El uso más común es para preparar las
instrucciones MOVSB y MOVSW (que se explicarán en el capítulo 15): Para usar estas instrucciones,
debes colocar primero cierto valor en los registros. En particular, debes poner ES y DI al segmento de
dirección y offset del campo dato.

Aquí hay un ejemplo de como puedes usar LES. Digamos que CAMPO y I_ADDR están definidas en el
segmento de datos como

CAMPO DW 20 DUP(?)
I_ADDR DD CAMPO

La siguiente instrucción copia la dirección de segmento de CAMPO a ES y el offset de CAMPO a DI:

LES DI,I_ADDR

9.7 Copiar desde y hacia la Pila: PUSH y POP.

La instrucción PUSH copia información hacia la pila; la instrucción POP saca datos de la pila.
Ambos PUSH y POP ocupan un operando de 16-bits. Aquí hay algunos ejemplos:

PUSH AX
PUSH TABLA[BX]+24
PUSH TYPE LISTA
PUSH 1024
PUSH 0
POP DS
POP VALOR

La instrucción PUSH copia el operando al tope de la pila. La instrucción POP copia del tope de la pila a
un operando, después de lo cual la siguiente entrada se convierte en la pila. Existen varios usos
importantes para el PUSH y el POP. Primero, los procedimientos pueden pasar información de regreso y
hacia otros procedimientos por medio de la pila.

Aquí está un ejemplo: Digamos que un procedimiento necesita llamar a otro llamado CALCULO.
CALCULO requiere cinco valores en orden para ejecutar algunos cálculos. En el procedimiento que
llama, estos valores son llamados TOTAL; SUBTOTAL, IMP, DESC, y CAMPO. El programa que llama
podría meter esos valores en la pila y entonces hacer la llamada a CALCULO:

PUSH TOTAL
PUSH SUBTOTAL
PUSH IMP
PUSH DESC
PUSH CAMPO
101
CALL CALCULO

El procedimiento calculo podría extraer sus datos de entrada de la pila apropiadamente. (Estos detalles se
explican en el capítulo 12).
El segundo uso importante del PUSH y el POP es usar la pila como área de almacenamiento temporal.
Esto es útil para almacenar resultados intermedios de cálculos complejos o preservar los valores actuales
de los registros que de otra forma se perderían.

Por ejemplo, las siguientes instrucciones podrían usarse para mover el contenido del CAMPO1 y el
CAMPO2. (La instrucción MOVSB se explica en el capítulo 15).

; copiar el contenido del CAMPO1 al CAMPO2


LEA SI,CAMPO1
LEA DI,CAMPO2
MOV CX,LENGTH CAMPO1
CLD
REP MOVSB

Infortunadamente esta secuencia de instrucciones cambia los valores de SI, DI y CX. Y que si estos datos
contienen información importante que no debe perderse.
La solución para preservar los valores de DI, SI y CX es meterlos a la pila, después de que MOV y
MOVSB son ejecutadas.

; salvar el contenido de SI, DI, CX


PUSH SI
PUSH DI
PUSH CX
; copiar el contenido del CAMPO1 al CAMPO2
LEA SI,CAMPO1
LEA DI,CAMPO2
MOV CX,LENGTH CAMPO1
CLD
REP MOVSB
; restaurar los valores de los registros SI, DI, y CX
POP CX
POP DI
POP SI

9.8 Salvar todos los registros: PUSHA y POPA

Como puedes ver en el último ejemplo de la sección previa, algunas veces querrás salvar más de un
registro. La instrucción PUSHA mete los valores de los siguientes registros a la pila, en el orden en que
aparecen:

AX CX DX BX SP BP SI DI

PUSHA significa “push all”. Como puedes ver, el nombre está raro porque sólo salva 8 de los 14
registros. (Los otros registros rara vez necesitan ser salvados).

102
La instrucción POPA restaura los valores de los registros sacándolos de la pila. Siempre que uses un
POPA debes haber usado antes un PUSHA. Aquí hay un ejemplo de la sección previa:

; copiar el contenido de CAMPO1 a CAMPO2


PUSHA
LEA SI,CAMPO1
LEA DI,CAMPO2
MOV CX,LENGTH CAMPO1
CLD
REP MOVSB
POPA

Es una buena práctica de programación usar el PUSHA al inicio de cada procedimiento y POPA al final.
Esto asegura que la llamada al procedimiento no causo cambios al programa que llamo.

Por supuesto, si un registro va a ser usado para pasar un valor de regreso al procedimiento que llamo,
debes asegurarte que la actualización del registro está después de la instrucción POPA. Veamos un
ejemplo

; actualizar los registros y regresar al programa que llamo


POPA
MOV AX,VALOR
RET

Si usas

MOV AX,VALOR
POPA
RET

El valor de VALOR será sobrescrito por la instrucción POPA.

Como último punto ya que el registro SP guarda el valor del tope de la pila debemos asegurarnos de no
modificar ese registro o estaremos en problemas.

9.9 Salvar el registro de Banderas: PUSHF y POPF

El registro de banderas es un registro especial el cual esta dividido en 16 bits donde cada bit es
tratado como una entidad separada. Estos bits tiene varios usos que serán discutidos en el capítulo 10.

Por ahora, necesitas comprender que algunas instrucciones tienen un side-effect cambiando algunos de
estos bits. Así es importante algunas veces que puedas salvar y restaurar el registro de banderas; POPF
saca los valores de la pila y restaura este registro.

Salvar el registro de banderas es útil cuando quieres prevenir que ciertas instrucciones cambien esos bits.
Por ejemplo si adicionas el contenido de AX al contenido de BX usando la instrucción:

ADD BX,AX

103
La instrucción ADD modifica algunos de los bits del registro de banderas. si quieres proteger estos bits,
todo lo que necesitas hacer es salvar y restaurar ese registro.

PUSHF
ADD BX,AX
POPF

Similarmente, podrías proteger el registro de banderas de las instrucciones de un procedimiento llamado


DISPLAY de está manera:

PUSHF
CALL DISPLAY
POPF

9.10 La instrucción que no hace nada: NOP

Por extraño que parezca, existe una instrucción que no hace nada llamada NOP. Esta instrucción
puede ser utilizada para sustituir código erróneo en un programa ejecutable, para esto solo tienes que
sustituir el código hex de la parte del programa que falla por el código hex de NOP (90H). Esto te permite
probar un programa sin la parte errónea para detectar si lo demás esta bien. También se puede usar la
instrucción NOP para ajustar un segmento de código al limite de un párrafo de manera exacta
completando con instrucciones NOP:

{_____________o___________}

104
CAPITULO 10 CONTROL DEL FLUJO

El control de flujo es la descripción del orden en el cual las instrucciones en un programa se


ejecutarán. En este capítulo aprenderás cuales son los estatutos que pueden definir patrones utilizados
comúnmente en lenguajes de alto nivel, tales como ciclos, anidaciones y decisiones.
El capítulo inicia con una discusión acerca de las herramientas necesarias (etiquetas y banderas), seguido
por las explicaciones de las instrucciones para el control del flujo.

Nuevos Términos.

Control de flujo. Una descripción del orden en el cual las instrucciones de un programa son ejecutadas.

Far. Describe una dirección que puede ser referenciada por otro segmento.

Near. Describe una dirección que puede ser referenciada sólo dentro de su mismo segmento.

Flag. Un bit cuyo valor 0 ó 1, es usado para indicar una condición particular.

Status flag. Una de seis banderas que describen los resultados generados por ciertas instrucciones.

Control flag. Una de tres banderas usadas para controlar la operación del procesador.

Complemento. El valor inverso de un bit; el complemento de 0 es 1, y el complemento de 1 es 0.

Salto (Jump). Una instrucción que le dice al procesador que continúe la ejecución en la dirección dada
por una bandera particular.

Conditional jump. Un salto que es ejecutado solo si una condición especificada es cierta.

Unconditional jump. Un salto que siempre es ejecutado.

Loop. Una secuencia de instrucciones que es ejecutada repetidamente.

10.1 Etiquetas en un segmento de código.

En el capítulo 8 aprendiste algo sobre la directiva LABEL, la cual define etiquetas en un programa.
En el capítulo 8 usamos esas etiquetas en los segmentos de datos y extra para darles nombres alternativos
a los campos de datos.

También puedes usar las etiquetas para proveer marcas dentro del segmento de código. Puedes controlar
la ejecución de tu programa saltando de una etiqueta a otra.
105
Para definir una etiqueta, usa la directiva LABEL con el siguiente formato:

nombre LABEL tipo

Cuando nosotros definimos etiquetas para campos de datos usamos los tipos BYTE, WORD, DWORD,
QWORD y TBYTE,. Para las etiquetas en el segmento de código vamos a usar los tipos NEAR y FAR,
los cuales explicaré a continuación. Aquí hay algunos ejemplos:

L1 LABEL NEAR
ENTRY LABEL FAR

El primer ejemplo define una etiqueta llamada L!, de tipo NEAR. La segunda define una etiqueta,
ENTRY de tipo FAR.

Una etiqueta dentro del segmento de código marca una dirección. La etiqueta near marca una dirección
que puede ser referenciada sólo dentro de su mismo segmento. Una etiqueta far marca una dirección que
puede ser referenciada desde otro segmento.

La mayoría de las etiquetas son near. En otras palabras, los saltos son casi siempre hechos a locaciones en
el mismo segmento. Ciertamente es una buena técnica de programación practicar saltos sólo en el mismo
segmento.

Las etiquetas far son poco comunes. Sólo los programas grandes y complejos demandan más de un
segmento de código, y es raro que quieras saltar a una etiqueta que se encuentra en un segmento
diferente. Aún así las etiquetas far están ahí por si son necesarias.

El mejor modo de comprender la diferencia entre las etiquetas far y near es comprender como las maneja
el ensamblador. Mientras procesas un programa, el ensamblador construye una tabla para rastrear todas la
etiquetas. Esta tabla contiene cada nombre con sus direcciones y su tipo.

Para etiquetas near, el ensamblador usa una dirección consistente de un offset (1 palabra). Para etiquetas
far, el ensamblador usa una dirección completa - una dirección de segmento con su offset (2 palabras).;
esto es debido a que el procesador necesita la dirección de segmento y el offset para saltar a otro
segmento. Para saltar dentro del mismo segmento, el procesador necesita sólo el desplazamiento.

S casi todas la etiquetas son near, el ensamblador te permite definir etiquetas near de manera abreviada
con este formato:

nombre:

Por ejemplo:

L1:
READ_MORE:

En otras palabras, el ensamblador trata al nombre que está antes de los dos puntos como una etiqueta. Así,

L1:

es lo mismo que

106
L1 LABEL NEAR

Cuando usas una etiqueta en su forma abreviada, puedes poner una instrucción en la misma línea. Por
ejemplo

TESTAX: CMPAX,0

Sin embargo, es mejor práctica de programación dejar toda la línea para la etiqueta

TESTAX:
CMPAX,0

10.2 El registro de Banderas.

En el capítulo 3, menciones que el procesador tiene 14 registros. Nueve de ellos son tratados como
entidades de 16-bits: SI; DI, SP, BP, IP, CS, DS, ES y SS. Cuatro de estos registros pueden ser tratados
como entidades de 16 u 8 bits: AX (AH, AL), BX (BH, BL), CX (CH, CL) y DX (DH, DL).

El registro de banderas es diferente de todos los otros, estos 16 bits son considerados como entidades
separadas de un bit.

De los 16 bits en el registro de banderas, 6 son llamados banderas de estado, 3 son llamados banderas de
control, y 7 no son usados en el modo real.

Una bandera es un bit cuyo valor, 0 o 1, es usado para indicar una condición particular. Las 6 banderas de
estado describen los resultados generados por ciertas instrucciones. Las 3 banderas de control controlan la
operación del procesador.

Cada una de las banderas de estado y de control tienen un nombre y una abreviación. Esto se muestra en
la tabla 10-1.

Tabla 10-1 Las banderas de estado y control


Banderas de Estado
Abreviación Nombre
AF Bandera de Acarreo auxiliar
CF Bandera de Acarreo
OF Bandera de Sobreflujo
PF Bandera de Paridad
SF Bandera de Signo
ZF Bandera de Cero
Banderas de Control
DF Bandera de Dirección
IF Bandera de Interrupción
TF Bandera de Trampa

Si cada bandera es un bit, debe tener siempre un valor 0 ó 1. Cuando una bandera tiene un valor de 1, se
dice que la bandera está ACTIVA; cuando una bandera tiene un valor de 0 se dice que la bandera está
DESACTIVADA.

107
Para referencia, la figura 10-1 muestra la locación de las banderas dentro del registro de banderas. Los
bits más significativos están a la izquierda. Los bits que no se usan son el 15, 5, 3 y 1. Los bits que son
usados sólo en el modo protegido son 14, 13 y 12.

Figura 10-1 La locación de las banderas

10.3 Las banderas de Estado.

La banderas de estado son modificadas por muchas instrucciones, muchas de las cuales afectan a
varias banderas. Hablando generalmente, las banderas de estado son usadas con operaciones aritméticas
en números con signo. Algunos de los valores de las banderas tienen un uso extraño que rara vez
necesitaras. Las más importantes son la siguientes:

Bandera de Cero. La bandera de cero (ZF) se activa para indicar que el resultado de una operación
fue cero.

Bandera de Signo. La bandera de signo (SF) se activa para indicar que el resultado de una
operación
es negativo.

Bandera de Sobreflujo. La bandera de sobreflujo (OF) es usada con números con signo. OF es
activada para indicar que el resultado de una operación es demasiado grande para guardarla en
el operando destino.

Bandera de Acarreo. La bandera de acarreo (CF) es usada con números sin signo. CF es activada
para indicar que el resultado es demasiado grande para ser almacenado en el operando destino.
(El nombre “bandera de acarreo” se refiere al hecho de que si el resultado de una adición es muy
grande el bit más significativo del destino queda fuera).

Bandera de Acarreo auxiliar. La bandera de acarreo auxiliar (AF) es usada con aritmética decimal.
AF esta activa después de una adición o substracción si es necesario un ajuste.

Bandera de Paridad. La bandera de paridad (PF) está activa si el resultado de una instrucción
contiene un número par de unos. Si tiene un número impar de 1´s la bandera está desactiva.

La bandera de acarreo tiene otro uso importante que debes comprender. Los programadores usan en sus
procedimientos la bandera CF para indicar una terminación exitosa o errónea. Si un procedimiento
termina sin errores CF se desactiva a 0 antes de regresar; si hay un error, el procedimiento activa la
bandera CF.

108
De esta manera el procedimiento que llama puede examinar CF para saber si hubo un error.

10.4 Instrucciones que modifican las banderas de estado: STC, CLC y CMC.

Muchas de la veces no es necesario que modifiques los estados de las banderas, ya que son solo para
examinarse. Sin embargo, hay algunas veces cuando querrás modificar la bandera de acarreo (CF). Por
esta razón, el procesador reconoce tres instrucciones que modifican el valor de esta bandera. Como se
muestra a continuación.

STC Activa la bandera de acarreo Pone CF a 1


CLC Limpia la bandera de acarreo Pone CF a 0
CMC Complementa la bandera de acarreo Invierte el valor de CF

Estas instrucciones tiene dos usos. Primero, puedes usar CF para indicar una condición de error en un
procedimiento. Segundo, ciertas instrucciones de rotación RCL y RCR (que se explicarán en el capítulo
16)
usan CF como parte de sus operaciones.

Con respecto a las otras banderas de estado, no hay forma de cambiar su valor.

10.5 La banderas de Control.

Las banderas de estado modifican el comportamiento de las instrucciones que no han sido
ejecutadas.

Bandera de Dirección. La bandera de dirección (DF) es usada por las instrucciones que trabajan
con
strings. Con las instrucciones de strings, puedes especificar las direcciones de inicio y la longitud
del string a ser procesado. Si DF esta en 0, el string es procesado hacia adelante; si DF está puesta
a 1, la cadena procesa hacia atrás.

Bandera de Interrupción. En casos inusuales, un programa puede controlar su ambiente, la


bandera
de interrupción (IF) es usada para activar o desactivar las interrupciones. Si la bandera IF está
puesta, el procesador reconoce las interrupciones; si la bandera IF esta desactivada el procesador
ignora las interrupciones que viene de dispositivos externos (hasta que IF se activa).
Las interrupciones son explicadas al detalle en el capítulo 17.

Bandera de Trampa. La bandera de trampa (TF) permite a un programa poner al procesador en un


estado en el cual un procedimiento especial es llamado después de que cada instrucción es
ejecutada.
Esto es usado por los debuggers para ejecutar un programa una instrucción a la vez y hacer una
pausa
entre instrucciones.
Si TF está en 1, el procesador llama al procedimiento especial después de la ejecución de cada
instrucción; si TF está en 0 el procesador opera normalmente

109
10.6 Instrucciones que modifican las banderas de Control: STD, CLD, STI y CLI.

Hay dos instrucciones que modifican la bandera de dirección (DF) y dos instrucciones que
modifican la bandera de interrupción (IF):

STD Activa la bandera de dirección Pone DF a 1


CLD Desactiva la bandera de dirección Pone DF a 0
STI Activa la bandera de interrupción Pone IF a 1
CLI Desactiva la bandera de interrupción Pone IF a 0

Necesitas activar y desactivar DF cada vez que uses instrucciones con strings (Ver capítulo 15).

10.7 Instrucciones de Saltos Condicionales.

Existen una multitud de instrucciones que pueden usar la influencia del control de flujo de un
programa. Muchas de estas instrucciones son saltos condicionales.

Normalmente, el procesador ejecuta instrucciones una después de otra. Un salto es una instrucción que le
dice al procesador que continúe la ejecución en la dirección dada por una etiqueta particular. De hecho, el
control es transferido a la primera instrucción después de la etiqueta. Por ejemplo, Digamos que un
programa contiene los estatutos.

L1:
MOV AX,0

Si el procesador ejecuta un salto a la etiqueta L1, el siguiente estatuto a ejecutar es la operación MOV.

Un salto condicional es un salto que es ejecutado sólo si la condición especificada es cierta. Este es el
formato de un salto condicional:

Jxxx etiqueta

xxx es una abreviación de un salto en particular. Si la condición es cierta, el procesador continua la


ejecución con el primer estatuto después de la etiqueta. Si es falso, la ejecución continua con la siguiente
instrucción en la secuencia.

Aquí hay un ejemplo:

; si CX no es igual a 0, entonces le paso un 10


JCXZ L1
MOV CX,10
L1:

La instrucción JCXZ salta a L1 si CX es igual a cero. Si el salto toma lugar la instrucción MOV nunca es
ejecutada. Si CX no es cero, la instrucción MOV se ejecuta.

10.8 Saltos condicionales que prueban banderas y registros.

110
Existen 10 saltos condicionales que dependen del valor de una bandera y un salto que depende del
valor de una registro (CX).

Todas las banderas de estado menos PF tienen dos saltos condicionales; uno que salta si la bandera está
activa y un que salta si la bandera está desactivada. Estas instrucciones están en la tabla 10-2. Las
abreviaciones seguidas de “J” son usadas como siguen:

Abreviación Significado
Z Bandera de Cero
S Bandera de Signo
O Bandera de Sobreflujo
C Bandera de Acarreo
P Bandera de Paridad
N No
Tabla 10-2 Salto condicionales con banderas
Opcode Significado
JZ Salta si ZF está puesta a 1
JNZ Salta si ZF está puesta a 0
JS Salta si SF está puesta a 1
JNS Salta si SF está puesta a 0
JO Salta si OF está puesta a 1
JNO Salta si OF está puesta a 0
JC Salta si CF está puesta a 1
JNC Salta si CF está puesta a 0
JP (JPE) Salta si PF está puesta a 1
JNP (JPO) Salta si PF está puesta a 0

¿Porque existe una instrucción para probar CX? Muchas instrucciones que repiten una instrucción varias
veces guardan el número de repeticiones en CX. Así es útil saber si el contador se pone a ceros.

10.9 Comparar dos números: CMP.

Los saltos condicionales que viste en la sección anterior examinan las banderas de estado (o CX)
directamente. Sin embargo, muchas de las veces necesitaras saltos condicionales basados en la
comparación de dos números.

Por ejemplo, supón que tienes que poner TOTAL a 0 siempre y cuando TOTAL sea menor que 0. O
comparar AX con MAXIMO, y ejecutar ciertas instrucciones si AX es mayor que MAXIMO.

Implementar saltos basados en dos valores numéricos es un proceso de dos pasos. Primero comparas los
dos valores y después ejecutas el salto incondicional basado en el resultado de la comparación.

Para comparar dos valores, usa la instrucción CMP

CMPvalor1,valor2

Aquí hay descripciones de varios tipos de comparaciones que puedes ejecutar. Cada descripción está
seguida de un ejemplo:

Comparar dos registros:


111
CMPAX,BX

Compara un registro con un campo dato:

CMPAX,TOTAL

Compara un registro con un operando inmediato

CMPAX,0

Compara un campo dato con un registro:

CMPTOTAL,AX
Compara un campo dato con un operando inmediato

CMPTOTAL,0

No puedes comparar dos campos de datos directamente. Si necesitas comparar dos datos tienes que copiar
uno de ellos a algún registro. Existen dos restricciones: Primera, puedes comparar dos números con signo
o sin signo pero no combinaciones. Segundo los operandos deben ser de la misma longitud.

Si quieres compara dos strings usa a instrucciones CMPSB y CMPSW, si quieres comparar patrones de
bits usa la instrucción TEST.

10.10 Saltos condicionales usados después de comparaciones.

El procesador tiene dos conjuntos de saltos condicionales que puedes usar después de una
comparación. Lo conjuntos son similares; la única diferencia es que un conjunto es para números con
signo y el otro para números sin signo.

Después de una instrucción CMP, normalmente usas un salto condicional para probar el resultado de la
comparación. Las tablas 10-5 y 10-6 muestran las instrucciones de saltos condicionales para los dos tipos
de números. Las abreviaciones después de “J” son las siguientes:

Abreviación Significado
N No
E Igual
G Mayor que
L Menor que
B Debajo
A Encima

Tabla 10-5 Saltos condicionales para números con signo


Opcode Significado
JL (JNGE) Salta si menor que (no mayor que o igual)
JG (JNLE) Salta si mayor que (no menor o igual)
JLE (JNL) Salta si menor o igual (no mayor que)
JGE (JNL) Salta si mayor o igual (no menor)
JE Salta si igual

112
JNE Salta si no igual

Tabla 10-6 Saltos condicionales para números sin signo


Opcode Significado
JB (JNAE) Salta si debajo (no arriba o igual)
JA (JNBE) Salta si arriba (no debajo o igual)
JBE (JNA) Salta si debajo o igual (no arriba)
JAE (JNB) Salta si arriba o igual (no debajo)
JE Salta si igual
JNE Salta si no igual

Note que algunas instrucciones tienen opcodes similares. Puedes usar el que quieras.

Hay dos cosas importantes que comprender. Primera, el procesador checa banderas diferentes para
números con signo y sin signo. Así, la instrucción le dice al procesador cual tipo checar. Sin embargo los
dos conjuntos de instrucciones son lógicamente equivalentes.

La segunda cosa importante es que la relación expresada por la instrucción se refiere a los dos operandos
previos en la instrucción CMP: Así, una instrucción JGE asume que la instrucción previa fue la
comparación entre dos números con signo. JGE salta si el primer número que encontró fue mayor o igual
que el segundo.

Aquí hay algunos ejemplos que usan CMP con saltos condicionales. El primer ejemplo brinca algunas
instrucciones si una variable con signo llamada TOTAL es menor que 0:

CMPTOTAL,0
JL L1
--- instrucciones que se salta ---
L1:

Si quieres puedes usar JNGE en lugar de JL:

CMPTOTAL,0
JNGE L1
--- instrucciones que se salta ---
L1:

El siguiente ejemplo salta algunas instrucciones si la variable sin signo llamada SUM es menor que la
valor sin signo en AX:

CMPSUM,AX
JB L2
--- instrucciones que se salta ---
L2:

El tercer ejemplo salta si DI es mayor o igual a VALOR, asumimos que DI tiene un número con signo.

CMPDI,VALOR
JGE L3
--- instrucciones que se salta ---
L3:

113
El último ejemplo salta si AH es mayor o igual a DH. Asumimos que DH y AH tiene números sin signo:

CMPAH,DH
JAE L4
--- instrucciones que se salta ---
L4:

10.11 Una gráfica de Referencia de todos los saltos condicionales.

La tabla 10-7 muestra todos los saltos condicionales, en orden alfabético. A un lado de dada código
están los campos de las banderas y registros que originan el salto. Los programadores comúnmente usan
las siguientes abreviaciones:

Abreviación Significado
CF Bandera de acarreo
CX Contenido del registro CX
OF Bandera de sobreflujo
PF Bandera de paridad
SF Bandera de signo
ZF Bandera de cero

Tabla 10-7 Gráfica de referencia de saltos condicionales


Opcode Campos de los registros bandera
JA (JNBE) CF=0 y ZF=0
JAE (JNB) CF=0
JB (JNAE) CF=1
JBE (JNA) CF=1 o ZF=1
JC CF=1
JCXZ CX=0
JE ZF=1
JG (JNLE) ZF=0 y SF=OF
JGE (JNL) SF=OF
JL (JNGE) SF=1 o =F=1 pero no ambas
JLE (JNG) ZF=1 o (SF=1 o OF=1 pero no ambas)
JNA (JBE) CF=1 o ZF=1
JNAE (JB) CF=1
JNB (JAE) CF=0
JNBE (JA) CF=0 y ZF=0
JNC CF=0
JNE ZF=0
JNG (JLE) ZF=1 o (SF=1 o OF=1 pero no ambas)
JNGE (JL) SF=1 o OF=1 pero no ambas
JNL (JGE) SF=OF
JNLE (JG) ZF=0 y SF=OF
JNO OF=0
JNP PF=0
JNS ZF=0
JNZ ZF=0
JO OF=1
JP PF=1

114
JPE PF=1
JPO PF=0
JS SF=1
JZ ZF=1

10.12 Salto incondicional: JMP.

Un salto incondicional es un salto que siempre se ejecuta; esto es, no depende de ninguna condición
cierta o falsa. El patrón del comando JMP es:

JMP etiqueta

Aquí hay algunos ejemplos

JMP L1
JMP FINISH_DISPLAY
JMP TABLE(SI)

Una instrucción JMP es usada para dos propósitos: Primero, para saltar instrucciones que no deben ser
ejecutadas cuando ciertas condiciones permanecen; segundo, para saltar hacia atrás y formar un ciclo.

10.12 Reglas para usar direcciones en instrucciones de Salto.

Cuando usas un salto incondicional (JMP) puedes especificar cualquier dirección, ya sea dentro del
mismo segmento de código (near) o en otro segmento (far). De preferencia evita saltar a otros segmentos.

La regla para los saltos condicionales es más restrictiva. Un salto condicional sólo puede saltar a una
etiqueta near; lo que es más la etiqueta pude estar a un máximo de distancia de 127 bytes de la
instrucción. El porque de esto, es que el procesador fue diseñado para que haga las traducciones de los
saltos con direcciones de un byte.

10.13 Guías para usar instrucciones de Salto.

Las instrucciones de salto son poderosas. Pueden transferir el control cuando sea y casi a donde sea.
Tal poder puede causar más problemas de los que crees.

Este comportamiento nos obliga a usar instrucciones de salto sólo cuando es necesario y de manera
disciplinada. Existen sólo dos situaciones importantes en las cuales puedes usar saltos.

Primera, usa saltos sólo para implementar una de las construcciones alternativas o repetitivas discutidas
en el siguiente capítulo. Por ejemplo:

; if AX=0 then BX+=5, else CX+=10


CMPAX,0
JE L1
JNE L2
115
L1:
ADD BX,5
JMP L3
L2:
ADD CX,10
L3:

Segundo, cuando un programa detecta condiciones excepcionales, y tienes que hacer un salto inmediato a
un procedimiento especial o a una parte especial del programa actual.

Por ejemplo, un procedimiento que lee y procesa datos debe asegarse que los datos sean validos. Si no es
así el procedimiento podría ir a la sección de manejo de erroresAquí hay un ajemplo que detecta tal
condición de error cuando la bandera de acarreo está puesta:

JC INVALID_DATA

Aquí hay un consejo que te evitará algunos problemas: Nunca saltes hacia atrás condicional o
incondicionalmente con un sola excepción : Los ciclos.

10.14 Repetir una secuencia: LOOP.

Un loop es una secuencia de instrucciones que se ejecutan repetidamente. El procesador tien un


grupo de instrucciones que hacen fácil implementar ciclos.

La primera instrucción es LOOP. Está crea un cilo a ser ejecutado un número especifico de veces. Por
ejemplo, digamos que necesitas una tabla de 100 números, debes hacer un culo que se ejecute 100 veces.

El comando LOOP tiene el siguiente formato:

LOOP etiqueta

Como con los saltos condicionales la etiqueta debe estar entre los 127 bytes de instrucciones. Usualmente
este no es problema.

La instrucción LOOP usa el registro CX como un contador que mantiene el valor del número de
ejecuciones del ciclo. Debes iniciar cargando a CX con el número de veces que quieres ejecutar el ciclo.
Depues defines la etiqueta que identifica el ciclo, los estatutos del ciclo, y la uinstrucción LOOP como se
ve a continuación.

MOV CX,número-de-veces
etiqueta:
-- estatutos dentro del ciclo ---
LOOP etiqueta

El coando LOOP resta 1 de CX. Entonces si CX no es igual a 0, LOOP salta a la etuqueta especificada.

Por ejemplo, aquí hay una rutina de un ciclo que se ejecuta 100 veces:

MOV CX,100
L1:
116
--- estatutos dentro del ciclo ---
LOOP L1

Aquí hay un ejemplo que adiciona 100 números. El número es almacenado como palabras in la tabla
llamada TABLA, la cual está definida como sigue:

TABLE DW 100 DUP(?)

Los valore son dados por el programa. Veamos un cilo que sume 100 números y deje la suma en AX.

; Pone en AX la suma de 100 números que están almacenados como palabra en TABLA
MOV AX,0
MOV SI,0
MOV CX,100
L1:
ADD AX,TABLA[SI]
ADD SI,2
LOOP L1

Este ejemplo usa tres registros AX, SI y CX. El contador trabaja como sigue: CX está inicializado a 100 y
los estatutos dentro del ciclo ejecutan la operación de suma, El LOOP disminuye 1 de CX y examina el
resultado, com CX es igual a 99 (el cual es diferente de cero), LOOP salta a L1. Así hasta que CX valga
0.

El indice trabaja como sigue: SI es inicializada a 0. Cada vez que entramos al ciclo, SI es incrementada en
2. Así, SI va toamndo los valores 0,2,4, etc. El primer ADD usa SI como registro indice. Así el segundo
operando es está instrucción toma el valor de TABLA+0, TABLA+2, TABLA+4, etc.

El acumlador trabaja como sigue: Ax es inicializada a 0. Cada vez que entramos al ciclo, un nuevo valor
es inicializado a AX, al final AX contiene el total de la suma.

10.15 Ciclos que hacen uso de comparaciones: LOOPE y LOOPNE.

Algunas veces es útil usar ciclos que dependam del uso de una comparación. Los comandos LOOPE
y LOOPNE proveen estos servicios. Como la instrucción LOOP, LOOPE y LOOPNE substren 1 de CX y
saltan si CX no es igual a 0. Sin embargo LOOPE y LOOPNE también imponen otra condición. LOOPE
requiere que la comparación previa encuentre que los dos operandos sean iguales; LOOPNE requiere que
los dos operandos sean diferentes.

De hecho, digamos que quieres buscar en una tabla de 100 números y que encuentre el primero que tenga
un valor de -1, supongamos que TABLA es una tabla de 100 palabras que contienen números con signo.
TABLA está definida como:

TABLA DW 100 DUP(?)

El número son puestos por el programa. Veamos como quedaría el ciclo usando LOOPNE:

; Busca en una tabla el primer valor igual a -1


MOV SI,-2
117
MOV CX,100
L1:
ADD SI,2
CMPTABLE[SI],-1
LOOPNE L1

Este ejemplo usa dos registros CX y SI. El contador trabaja como sigue: CX vale 100 al inicio. Entionces
LOOPNE decrementa el valor de CX en 1 dejandolo como 99, (el cual es diferente de 0). Si los dos
operandos no son iguales LOOPNE salta a L1. El ciclo continua hasta que CX vale 0 ó una comparación
de los operandos muestra que son iguales, (esto es que algún valor es igual a -1).

El indice trabaja como sigue: SI está inicializado a -2. Cada vez que entra al ciclo, SI es decrementado en
2. Así va tmando los valores 0, 2, 4, etc.El comando CMP usa a SI como el registro indice. Así el
segundo operando en está instrucción toma los valores TABLA+0, TABLA+2, TABLA+4, etc. Como
TABLA consiste de palabras, estos valores direccionan cada número en turno.

La razón de la inicialización de SI a -2 en lugar de 0, el que el comando ADD debe estar antes del CMP,
si ADD estuviera entre el CMP y el LOOPNE, la suma prodría cambiar el valor de las banderas, es por
eso que la operación CMP tiene que ser la última antes del LOOP.

Aqui hay un ejemplo de un LOOPE que busca en la misma tabla el primer número diferente de -1:

; Busaca en TABLA de 100 palabras el primer valor que sea diferente de -1


MOV SI,-2
MOV CX,100
L1:
ADD SI,2
CMPTABLA[SI],-1
LOOPE L1

Las tres instrucciones de ciclo están sumarizadas en la tabla 10-8. Note que LOOPE y LOOPNE tiene
nombres alternos:

Tabla 10-8 Comandos de ciclo


Opcode Significado Campos del registro de banderas
LOOP Ciclo CX no es cero
LOOPE Ciclo si igual a 0 CX no es cero y ZF=1
LOOPNE Ciclo si no igual a 0 CX no es cero y ZF=0

{_____________o___________}

118
CAPITULO 11 IMPLEMENTAR CONTROLES DE FLUJO

En este capítulo, aprenderás como son usadas las herramientas de ensamblador para hacer control de
flujo: etiquetas, banderas, saltos condicionales e incondicionales y comandos loop. Es esta capítulo
aprenderás a usar esas herramientas para construir los flujos de control que necesitas para un programa..
Este capítulo es especialmente importante debido a que cubre los patrones que usaras para representar la
lógica de los programas.

Nuevos Términos

Secuencia. Una de las tres construcciones fundamentales de control de flujo; la ejecución de un grupo de
instrucciones una después de otra.

Alternación. Una de las tres construcciones fundamentales de control de flujo; la ejecución de uno de
varios grupos de secuencias, dependiendo del valor de una condición especifica.

Repetición. Una de las tres construcciones fundamentales de control de flujo; la ejecución de una
secuencia de instrucciones una y otra vez, hasta ó mientras una condición especifica sea verdad.

11.1 Secuencia, Alternación y Repetición.

En cada procedimiento, el control de flujo es expresado usando los tres constructores


fundamentales: secuencia, alternación y repetición. En un lenguaje de alto nivel estos constructores ya
están definidos y listos para usarse. En lenguaje ensamblador tendrás que construirlos tú mismo a partir
de operaciones básicas.

La secuencia se refiere a ejecutar un número de estatutos uno después de otro. La alternación provee
opciones. Varias secuencias de instrucciones son especificadas, cuando el programa se ejecuta, una
elección es hecha para ejecutar una secuencia de instrucciones, basados en el valor de una o más
condiciones especificas. La repetición significa ejecutar una secuencia de instrucciones una y otra vez
hasta, o mientras, una condición es satisfactoria.

Los constructores cuyo patrón veremos en las siguientes secciones son: case, case-else, if-then-else, if-
then, repeat-until, for-next y while-repeat.

119
11.2 El Constructor CASE.

CASE es un constructor del tipo alternativo. Es usado para seleccionar un curso de acción en una
situación donde varios casos pueden surgir En un lenguaje de alto nivel el patrón del CASE sería:

CASE
cond1: secuencia1,
cond2: secuencia2,
cond3: secuencia3, ...

El constructor CASE es interpretado como sigue: La condición es evaluada en orden, una por una.
Cuando la condición es cierta la secuencia de instrucciones asociada se ejecuta, cada secuencia puede
consistir de más de una instrucción. Si ninguna de las condiciones es cierta, nada se ejecuta.

Suponga que necesitamos implementar los siguiente:

CASE
OPCION=1 : Llamar a DISPLAY;
OPCION=2 : Llamar a GET_INPUT;
OPCION<0 : Adicionar TOTAL a AX

En lenguaje ensamblador, un constructor CASE es traducido como una secuencia de saltos condicionales
e incondicionales:

; case opción=1: llama a DISPLAY,


; opción=2: llama a GET_INPUT
; opción<0: adiciona TOTAL a AX
CMP OPCION,1
JE L1
CMPOPCION,2
JE L2
CMPOPCION,0
JL L3
JMP L4
L1: ; opción=1
CALL DISPLAY
JMP L4
L2: ; opción=2
CALL GET_INPUT
JMP L4
L3: ; opción<0
ADD AX,TOTAL
L4:

Para uso general, el siguiente listado es el patrón del constructor CASE. Este patrón implementa sólo tres
comparaciones, pero puedes extenderlo al número de opciones que desees:

; case cond1: sec1,


; cond2: sec2,
; cond3: sec3
CMP(cond1)
Jcond1 L1
120
CMP(cond2)
Jcond2 L2
CMP(cond3)
Jcond3 L3
JMP L4
L1: ; cond1
--- (sec1) ---
JMP L4
L2: ; cond2
--- (sec2) ---
JMP L4
L3: ; cond3
--- (sec3) ---
L4:

11.3 El constructor CASE-ELSE.

CASE-ELSE es un constructor alternativo que es una variación del case. La única diferencia con
CASE-ELSE es que si ninguna de la alternativas normales del case es seleccionada la alternativa ELSE es
ejecutada.

En un lenguaje de alto nivel, el formato del case-else es:

CASE
cond1: sec1,
cond2: sec2,
cond3: sec3,
.
.
.
ELSE: sec-else

Aquí hay un ejemplo de un constructor CASE-ELSE:

CASE
OPCION=1 : Llamar a DISPLAY;
OPCION=2 : Lamar a GET_INPUT;
OPCION<0 : Adicionar TOTAL a AX;
ELSE : adicionar 1 a BAD_CHOICE

Aquí esta la traducción:

; case opción=1: llama a DISPLAY,


; opción=2: llama a GET_INPUT
; opción<0: adiciona TOTAL a AX
; else: adiciona 1 a BAD_CHOICE
CMP OPCION,1
JE L1
CMPOPCION,2
JE L2
121
CMPOPCION,0
JL L3
JMP L4
L1: ; opción=1
CALL DISPLAY
JMP L5
L2: ; opción=2
CALL GET_INPUT
JMP L5
L3: ; opción<0
ADD AX,TOTAL
JMP L5
L4: ; else
ADD BAD_CHOICE,1
L5:

Para uso general, el siguiente listado es el patrón del constructor CASE. Este patrón implementa sólo tres
comparaciones, pero puedes extenderlo al número de opciones que desees:

; case cond1: sec1,


; cond2: sec2,
; cond3: sec3
; else: else-sec
CMP(cond1)
Jcond1 L1
CMP(cond2)
Jcond2 L2
CMP(cond3)
Jcond3 L3
JMP L4
L1: ; cond1
--- (sec1) ---
JMP L5
L2: ; cond2
--- (sec2) ---
JMP L5
L3: ; cond3
--- (sec3) ---
L4: ; else
--- (else sec) ---

L5:

11.4 El constructor IF-THEN-ELSE.

IF-THEN-ELSE es un constructor que contiene una condición y dos secuencias de instrucciones. Si


la condición es cierta, la primer secuencia se ejecuta; de otra forma la segunda secuencia se ejecuta. en un
lenguaje de alto nivel, el formato del IF-THEN-ELSE sería:

IF cond THEN
122
sec1
ELSE
sec2

Aquí hay un ejemplo de IF-THEN-ELSE

IF OPCION>0 THEN
adiciona 1 a GOOD_CHOICE
ELSE
adiciona 1 a BAD_CHOICE
Aquí está la traducción:

; if OPCION>0 adiciona 1 a GOOD_CHOICE


; else: adiciona 1 a BAD_CHOICE
CMPOPCION,0
JG L1
JMP L2
L1:
ADD GOOD_CHOICE,1
JMP L3
L2:
ADD BAD_CHOICE,1
JMP L3
L3:

Aquí está el patrón para uso general:

; if cond sec1,
; else sec2
CMP(cond)
Jcond L1
JMP L2
L1: ; then
--- (sec1) ---
JMP L3
L2: ; else
--- (sec2) ---
L3:

11.5 El constructor IF-THEN.

El constructor IF-THEN solo tiene una secuencia de instrucciones, y se ejecutan solo si la condición
es cierta, su formato es:

IF cond THEN secuencia

Aquí hay un ejemplo de IF-THEN

IF OPCION<0 THEN
adiciona 1 a GOOD_CHOICE,
123
pon CHOICE a 0

Aquí está la traducción a ensamblador

; if OPCION<0 then
; adiciona 1 a GOOD_CHOICE
; pon CHOICE a 0
CMP OPCION,0
JNL L1
ADD GOOD_CHOICE,1
MOV CHOICE,0
L1:

Para uso general aquí está el patrón:

; if cond then sec


CMP(cond)
JNcond L1
-- (sec) ---
L1:

11.6 El constructor REPEAT-UNTIL.

El REPEAT-UNTIL repite una secuencia de instrucciones hasta que una condición se hace
verdadera. La condición no se prueba hasta que la secuencia ha sido ejecutada por lo menos una vez. En
lenguaje de alto nivel el REPEAT-UNTIL sería:

REPEAT
sec
UNTIL cond

Aquí hay un ejemplo. Un programa necesita leer alguna entrada y asegurarse que es valida. El programa
llama a dos procedimientos: GET_INPUT para leer la entrada y TEST_INPUT para validarla. El
programa debe llamar a estos dos procedimientos hasta que la entrada sea valida

En algunos casos debes hacer una comparación antes del salto condicional. TEST_INPUT dice si la
entrada es valida o invalida poniendo a AX en 0 o en 1, respectivamente.

; repeat
; lee la entrad,
; prueba la entrada; hasta que AX=0
L1:
CALL GET_INPUT
CALL TEST_INPUT
CMPAX,0
JNE L1

Para uso general aquí está el patrón:

; repetir secuencia hasta que la condición sea cierta


124
L1:
--- sec ---
CMPcond
JNcond L1

11.6 El constructor FOR-NEXT usando LOOP

FOR-NEXT ejecuta una secuencia de instrucciones un número fijo de veces. Aquí hay un ejemplo
tomado del capítulo 10 acerca del uso del LOOP. El programa suma 100 números que están almacenados
como palabras en una tabla llamada TABLA, y que está definida como sigue

TABLA DW 100 DUP(?)

Los valores ya están en la tabla. El programa usa tres registros: AX para guardar el resultado, SI como un
índice y CX para controlar el ciclo, la construcción FOR-NEXT sería:

inicializa AX a 0
inicializa SI a 0
inicializa CX a 100
FOR CX=100 a CX=1
suma TABLA[SI] a AX
incrementa SI en 2
NEXT

Aquí está la traducción a ensamblador:

; Tener en AX la suma de los 100 números que están en TABLA


MOV AX,0
MOV SI,0
MOV CX,100
L1:
ADD AX,TABLA[SI]
ADD SI,2
LOOP L1

Para uso general este es el patrón

; repite una secuencia N veces


MOV CX,N
L1:
--- sec ---
LOOP L1

11.7 El constructor WHILE-REPEAT.

WHILE-REPEAT ejecuta una secuencia de instrucciones mientras que la condición sea verdadera.
Si la prueba de la condición originalmente es falsa no se entra ni siquiera una vez a este constructor.. En
lenguaje de alto nivel, el formato del WHILE-REPEAT sería:
125
WHILE cond REPEAT
secuencia

Aquí hay un ejemplo que llama a un procedimiento (PRINT_DATA) para mandar datos a la impresora. El
procedimiento se llama mientras la variable CONT sea mayor que 0.

WHILE CONT>0 REPEAT


llamar a PRINT_DATA
restarle 1 a CONT

Aquí está la traducción:

; while CONT>0 repite


; mandar un dato a la impresora
; incrementar CONT en 1
L1:
CMPCONT,0
JNG L2
CALL PRINT_DATA
SUB CONT,1
JMP L1
L2:

Para uso general:

; while cond repite secuencia


L1:
CMPcond
JNcond L2
--- secuencia ---
JMP L1
L2:

{_____________o___________}

126

También podría gustarte