Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
Objetivo General: Al finalizar el curso el alumno podrá aplicar los conceptos y características sobre
la arquitectura de una computadora para elaborar y depurar programas escritos en lenguaje
ensamblador.
Criterios de Evaluación:
1er. Bimestre 2º. Bimestre Ordinario
Criterio de Evaluación Puntos Criterio de Evaluación Puntos Criterio de Evaluación Puntos
Examen escrito 15 Examen escrito 15 Examen escrito 15
Participación 5 Participación 5 Participación NA
Trabajos y/o proyectos 5 Trabajos y/o proyectos 5 Trabajos y/o proyectos 15
Tareas o ensayos 10 Tareas o ensayos 10 Tareas o ensayos NA
Exposiciones de trabajo NA Exposiciones de trabajo NA Exposiciones de trabajo NA
Otros NA Otros NA Otros NA
Total 35 Total 35 Total 30
Fechas de Evaluaciones
Primer Parcial:
Segundo Parcial:
CONTENIDO
1. FUNDAMENTOS DE LA PROGRAMACIÓN EN ENSAMBLADOR
El alumno comprenderá y aplicará los conceptos relacionados con la estructura de las
computadoras y el Lenguaje Ensamblador.
1.1. La familia de computadoras IBM
1.2. Macroensamblador
1.3. Ventajas que se obtienen al aprender un Lenguaje Ensamblador
1.4. Sistemas Numéricos
1.4.1.Numeración binaria
1.4.2.Bits, nibbles, Bytes y Words
1.4.3.Representación de enteros
1.4.3.1.Magnitud con signo
1.4.3.2.Complemento a uno
1.4.3.3.Complemento a dos
1.4.3.4.Exceso 2n-1
1.4.4.Representación de punto flotante
1.4.4.1.Representación de números de punto flotante en la PDP-11 e IBM
1.4.4.2.Bit Escondido
1.5. El Debug
Centro Escolar “Felipe Carrillo Puerto”
3.10.Imprimiendo en Decimal
3.11.Segmentos y Desplazamientos
3.12.Mapa de la memoria RAM
3.13.Segmentos
3.13.1.Seccionando la memoria del Microprocesador
3.13.2.La Pila
3.13.3.El priefijo del Segmento de Programa (PSP o Scratch área)
3.13.4.La directiva DOSSEG
3.13.5.Llamadas (NEAR y FAR)
3.13.6.Vector de Interrupciones
3.14.Vaciando la memoria
3.14.1.Instrucción group
3.14.2.Rutinas varias
Centro Escolar “Felipe Carrillo Puerto”
3.15.Diseño de software
3.16.Instrucciones de control
3.16.1.Instrucciones de salto
3.16.2.Instrucciones de comparación
3.17.Optimización del diseño
3.17.1.Programación modular
3.17.2.Diseño descendente
3.18.Diagramas de flujo y pseudocódigo
3.19.Enfoque a la programación estructurada
3.20.Estilo y forma
3.21.Instrucciones de uso más frecuentes
3.21.1.Instrucciones aritméticas
3.21.2.Instrucciones de transferencia
3.21.3.Instrucciones de carga
3.21.4.Instrucciones Loop
3.21.5.Instrucciones de Stack
3.21.6.Instrucciones de conteo
3.21.7.Otras instrucciones
3.21.8.Instrucciones de corrimiento
3.21.9.Instrucciones de rotación
3.21.10.Instrucciones de almacenamiento
3.21.11.Instrucciones de manejo de cadenas
3.21.12.Instrucciones de conversión
3.21.13.Instrucciones de procedimiento y control
3.21.14.instrucciones ASCII
3.21.15.instrucciones de aritmética decimal
3.21.16.Instrucciones de I/O
3.21.17.Instrucciones diversas
3.22.Ejemplos y ejercicios de programación.
Bibliografía
Introducción
¿Qué es el Lenguaje Ensamblador?
Es un lenguaje de bajo nivel que traduce instrucciones en lenguaje máquina1. Este utiliza
nemotécnicos (abreviaturas) que representan operaciones, nombres simbólicos, operadores y símbolos
especiales.
Entonces, ¿Por que estudiar el Lenguaje Ensamblador si existen Lenguajes de Alto Nivel?
La importancia del Lenguaje ensamblador es que trabaja directamente con el microprocesador.
Los programadores que emplean lenguajes de alto nivel para desarrollar aplicaciones donde el
tiempo no es un factor crítico o que hacen uso de dispositivos estándar de entrada/salida (I/O), rara vez
necesitan llamar rutinas que no formen parte de la librería del compilador. En otras palabras, las
necesidades de programar en lenguaje ensamblador en este tipo de aplicaciones, son mínimas.
Sin embargo, alguien debe escribir las rutinas de librería que estos programadores emplean, con
el fin de obtener la interfaz estándar. Estas rutinas forman la parte no transportable del lenguaje que
utilizan y están escritas en lenguaje ensamblador.
1
El Lenguaje de Máquina son aquellas instrucciones que son directamente entendidas por lo computadora y no necesitan
traducción posterior para que la CPU pueda comprender y ejecutar el programa. Dichas instrucciones se expresan con bits.
1
Los microprocesadores 8088 y 8086 se programan con un conjunto básico de instrucciones que
son la base de la versión 1.0 de macroensamblador (que emplea un modelo de memoria pequeña). El
80286 se programa en uno de dos modos posibles: el modo de direcciones reales o el modo de
direcciones virtuales (o modo protegido). Todo el código desarrollado para el 8088 y 8086 se ejecuta
en el primero modo. De manera similar todo el código, salvo para unas instrucciones específicas
desarrollado para el 80286 en el modo de direcciones reales será ejecutado sin ningún problema, tanto
en el 8088 como en el 8086, La versión 2.0 del macroensamblador incluye también instrucciones
específicas del 80286, 8087 y 80287.
En modo protegido el 80286 es muy diferente del 8088 y 80286. En este modo existen
instrucciones orientadas a soportes de sistemas que no están disponibles en las versiones 1.0 y 2.0 del
macroensamblador. Además, estas instrucciones no están diseñadas para que el programador de
aplicaciones haga uso de ellas ya que proporcionan funciones para el manejo de memoria y multitarea.
El 80386 tiene muchas instrucciones que son iguales al 80286 tanto en direcciones reales como en
modo protegido. Además, el 80386 tiene un modo virtual para el 8086 que permite ejecutar los
programas desarrollados para este microprocesador en el ambiente multitarea.
2
Registros del INTEL 80386/80486
Registros Descripción
EAX (AH, AL) Registro del acumulador (32 bits)
EBX (BH, BL) Registro de base (32 bits)
ECX (CH, CL) Registro para conteo (32 bits)
EDX (DH, DL) Registro de datos (32 bits)
ESP Registro apuntador de pila (32 bits)
EBP Registro apuntador de base (32 bits)
ESI Registro de índice de fuente (32 bits)
EDI Registro de índice de destino (32 bits)
ECS Registro de segmento de código (16 bits)
EDS Registro de segmento de datos (16 bits)
ESS Registro de segmento de pila (16 bits)
EES Registro de segmento extra (16 bits)
EIP Apuntador de instrucciones (16 bits)
EFLAGS Registro de estado de banderas (16 bits)
Ejemplos de Ensamblador:
MACRO (PDP-11)
COMPASS (CYBER)
ZILOGZ-80 (RADIO SHACK)
MACROENSAMBLADOR (INTEL)
IBM MACRO ASSEMBLER
MICROSOFT MACROASSEMBLER, TURBO, EDITASAM ASSEMBLER
Las ligas a continuación, muestra la forma en que se programaba en ensamblador usando una PDP-11:
• Parte 1 (http://www.youtube.com/watch?v=XV-7J5y1TQc&feature=related)
• Parte 2 (http://www.youtube.com/watch?v=7zaaD_xP6nU&feature=related)
• Parte 3 (http://www.youtube.com/watch?v=xiE2QldpQRQ&feature=related)
• Parte 4 (http://www.youtube.com/watch?v=NUSn59iY8U8&feature=related)
3
En el macroensamblador de IBM las instrucciones no ejecutables que se emplean para
estructurar el código fuente toma la forma de pseudoperaciones, el ensamblador permite que un
programa que se ejecute en una CPU 8086, 8088 u 80286 haga uso de 4 tipos de segmentos: el de
código, de datos, de pila y uno mas de datos o segmento extra de código.
El 80386 y el 80486 tiene dos segmentos adicionales de datos que fueron añadidos para la
congestión de registro ES y para mejorar la coincidencia de los registros índice y base disponibles en el
conjunto general de registros, estos segmentos son FS y GS.
Los programas con extensión .EXE pueden ser colocados en cualquier parte de la memoria
RAM o el sistema operativo. El programa LINK únicamente añade cabeceras al programa.
Ejemplo:
Etiqeta1: MOV AX, 01
CMP AX, BX
JNZ Etiqueta2
SUB BX, 10
JMP Etiqueta1
Etiqueta2: ADD BX, AX
HLT
END
Tabla de símbolos
Nombre Valor Longitud Reubicable Acceso Ext.
Etiqueta1 0 10 -- 1
Etiqueta2 10 6 -- 0
4
Tabla de códigos
OP Code Cod. Hex Long Int Tipo de inst
MOV 7B 2 R/M
CMP 3D 4 R/R
... ... ... ...
R = registro y M = memoria
Tabla de literales
Valor Localidad
01 500H
10 502H
Sistemas Numéricos
Numeración binaria
Un bit representa un dígito que tiene uno de dos valores posibles: uno o cero. El dígito
representa uno de dos estados y se define como aritmética binaria o de base-2.
N-bits pueden representarse de la siguiente manera, siendo el bit más significativo el que se
encuentra mas a la izquierda.
En la memoria de la computadora, los números positivos se representan como enteros sin signo.
En general, con palabras de 16 bits se puede representar cualquier entero positivo en el intervalo de
0-65,535 (216 - 1). Si el bit 16 se emplea para indicar el signo del número, entonces el mayor entero que
puede representarse con los 15 bits restantes está en el intervalo de (-32,767, +32,767).
2
El coprocesador es el que lleva a cabo operaciones de punto flotante a muy alta velocidad.
5
Bits, nibbles, Bytes, Words
Bit = 1 ó 0
Nibble = 4 bits
Byte = 2 Nibbles = 8 bits
Word = 2 Bytes = 4 Nibbles = 16 bits
Representación de enteros
Magnitud con signo
En éste método de numeración binaria los valores positivos empiezan con cero y los negativos
con uno. El bit más significativo representa el signo.
Ejemplo: Escribir con 3 bits los valores posibles
0|00 = 0
0|01 = 1
0|10 = 2
0|11 = 3
1|00 = -0
1|01 = -1
1|10 = -2
1|11 = -3
Complemento a uno
Los positivos comienzan con cero (magnitud con signo) y para escribir los negativos se escribe
el valor positivo y se complementa lógicamente (es decir, los 0 se convierten en 1 y viceversa)
Ejemplo: Escribir con 3 bits todos los valores positivos y negativos posibles
0|00 = 0
0|01 = 1
0|10 = 2
0|11 = 3
1|00 = -3
1|01 = -2
1|10 = -1
1|11 = -0
Complemento a dos
Los positivos se escriben igual que en complemento a uno y magnitud con signo. Los negativos
provienen de sumarle 1 al valor del complemento a uno.
Ejemplo: Escribir ±131 en complemento a dos con 9 bits
131 = 0|10000011 => además es m.c.s., complemento a uno y complemento a dos
1|01111100 => -131 en complemento a uno
+ 1
_______________
1|01111101 => -131 en complemento a dos
Exceso 2n-1
Para escribir cualquier cantidad positiva o negativa bastará con sumarle 2 n-1, donde n=número
de bits empleados.
Ejemplo: Escribir ±36 en exceso 2n-1 con 7 bits.
36 + 27-1= 36 + 26 = 36 + 64 = 100 => 1100100
-36 + 27-1= -36 + 26 = -36 + 64 = 28 => 0011100
6
Representación de punto flotante
Los números de punto flotante se representan en la forma a·be donde:
a=es la mantiza normalizada
b=es la base del sistema de numeración
e=es el exponente de la base
Ejemplo:
Representar 1943 en punto flotante en base decimal
.1943 x 104 donde a=.1943, b=10 y e=4
La familia PDP-11 de DEC representan sus números de punto flotante con 32 bits:
1 8 23
1 7 24
0.5x2=1.0
0.0x2=0.0
Bit escondido
La mantiza se supondrá normalizada (siempre empezará con .1) y el primer 1 después del punto
binario se omitirá suponiendo que esté presente (escondido).
7
9 + 28-1(8 por el número de bits del exponente)=9 + 27=9 + 128=137 (en exceso 2n-1)
137=10001001 (exponente)
1 10001001 .00001111000000000000000
0 1001001 .110110110010001111010111
El Debug
El Debug es una herramienta de edición y comprobación de programas el cual proporciona un
control de pruebas en un ambiente binario y de archivos ejecutables. Se puede ejecutar de dos maneras:
Introduciendo DEBUG desde la línea de comandos (C:\>Debug <↵>)
Para depurar un archivo ejecutable (C:\>Debug ejemplo.EXE <↵>)
DEBUG [[unidad:][ruta]archivo [parámetros_de_test]]
Donde [unidad:][ruta]archivo Especifica el archivo que se desea comprobar.
parámetros_de_test Especifica la información de línea de comandos que precisa el archivo que
se desea comprobar.
Si decide trabajar desde la línea de comandos del debug, al momento de presionar el enter
aparecerá un nuevo prompt esperando instrucciones. La línea de comandos del debug consiste en una
sola letra con uno a más parámetros. Después de iniciar Debug, escriba ? para visualizar una lista de los
comandos de depuración. Si un error de sintaxis ocurriera se indicará mediante un ^Error
Lista de comandos
Comando Función
A [address] Assemble
C range address Compare Intervalo de direcciones
D [range] Dump
E address [list] Enter
F range list Fill
G [=address [address …]] Go
H value value Hex
I value Input
L [addres [drive record record]] Load
M range address Move
N file descriptor [file descriptor] Name
O value byte Output
P [=dirección] [número] Proceed
Q Quit
R [register-name] Register
S range list Search
T [=address] [value] Trace
U [range] Unassemble
W [address [drive record record]] Write
XA [N.páginas] allocate expanded memory
8
Comando Función
XD [identificador] deallocate expanded memory
XM [páginaL] [páginaP] [identificador] map expanded memory pages
XS display expanded memory status
Trace (T) ejecuta una o más instrucciones comenzando con la dirección actual del apuntador de
instrucciones (CS:IP)
Dump (D) Muestra el contenido de la memoria comenzando con una determinada localidad
(indicada ésta como segmento:desplazamiento)
9
INTRODUCCIÓN A LA PROGRAMACIÓN EN LENGUAJE ENSAMBLADOR
El alumno conocerá cómo representa la computadora de manera interna los datos e instrucciones que
procesa mediante el debug.
EL DEBUG y su Uso
El debug permite colocar en memoria y ejecutar un grupo de instrucciones en lenguaje de máquina una a
una en un tiempo, permitiendo observar cómo el programa trabaja. El debug utiliza números hexadecimales,
puesto que en términos de longitud es más fácil que con números binarios.
El nombre hexadecimal proviene de hexa (6) y deca (10), lo cual combinado representa 16 dígitos (0 – 9,
A - F). Con dos números hexadecimales se pueden representar 256 diferentes números con dos dígitos.
Para ejecutar el Debug deberá estar en el símbolo del sistema (DOS) e introducir:
- H 3D5C 2A10
676C 134C
- H 3A7 1ED
0594 01BA
-H91
000A 0008
-H96
000F 0003
3
<↵> Significa Enter
10
¿Qué pasa si nosotros utilizamos números de 5 dígitos?
H 5CF00 4BC6
^ERROR
El 8088/8088/80286/80386/80486 puede usar números con signo o sin signo. En la forma binaria para
números positivos el bit 5 es siempre 0, para los negativos 1. Si nosotros usamos instrucciones para números sin
signo en nuestro programa, el procesador ignora el bit de signo, de tal manera que nosotros podemos usarlo a
nuestra conveniencia. Los números negativos son conocidos como complemento a dos del número positivo.
11
ARITMÉTICA DEL 8086/8088/80286 MEDIANTE EL DEBUG
Conociendo algo del Debug y la aritmética binaria del procesador, nosotros podemos aprender cómo el
procesador trabaja y puede ejecutar órdenes internas llamadas instrucciones.
Nosotros le podemos solicitar al Debug que despliegue la información contenida en los registros con el
comando R (Register). Probablemente vea diferentes números en las líneas dos y tres del desplegado en la
pantalla, éstos números reflejan la cantidad de memoria de su computadora.
Por ahora, el Debug nos ha proporcionado mucha información. Nos concentraremos en los registros AX,
BX, CX y DX, los cuales deben ser iguales a 0000. Los números de cuatro dígitos siguientes para cada registro
están en notación hexadecimal. Una palabra está compuesta por cuatro dígitos hexadecimales. Cada uno de estos
registros son de 16 bits, esto explica porque 8086/8088/80286 son conocidas como máquinas de 16 bits.
Los otros registros, también son conocidos como de propósito especial: SP, BP, SI, DI, DS, ES, SS, CS,
e IP.
El comando R hace más que desplegar los registros, también nos permite cambiarlos. Por ejemplo,
nosotros podemos cambiar el valor de registro AX.
- R AX <↵>
AX 0000
: 3A7 <↵>
R (Para comprobar)
Desde este momento utilizaremos el Debug como un intérprete, así que nosotros podremos trabajar
directamente con el procesador.
Ahora, colocaremos un número en BX y otro en AX y, le pediremos al procesador que los sume y deje el
resultado en AX. Coloque AX=3A7h y en BX=92A con el comando R. Verifíquelo.
La memoria está dividida en piezas de hasta 64Kb llamados segmentos. Nosotros colocaremos la
instrucción en algún lugar de un segmento y luego le diremos dónde está y que la ejecute sin saber dónde inicia
dicho segmento.
Todos los bytes de la memoria RAM están etiquetados con números iniciando con 0000h. Pero recuerda
que la limitación de los números hexadecimales es de 4 dígitos. De esta manera, el número más alto que puede
ser usado como etiqueta de memoria es de 65,535, lo cual, implica la longitud máxima de los segmentos.
12
Sin embargo, el procesador puede llamar más de los 64 Kb de memoria. ¿Cómo puede ser esto?. Se usan
dos números, uno para cada segmento de 64 Kb. y otro para el desplazamiento dentro del segmento. De tal
manera, que los segmentos están traslapados pudiendo el procesador utilizar más de un millón de bytes en
memoria.
Todas las etiquetas de dirección serán usadas como resultado del principio de un segmento. Por ejemplo:
3756:0100 significará que nosotros estamos en la dirección 0100h del segmento 3756.
Por ahora, confiaremos del debug para cuidar el segmento por nosotros. Así, que nosotros trabajaremos
sin prestar atención a los números de segmento. Ahora, cada dirección se refiere a un byte de un segmento y las
direcciones son consecutivas.
Colocaremos la instrucción de adición ADD AX, BX en la posición 0100h y 0101h del segmento. El
código de la instrucción es 01D8h.
El Comando del Debug para examinar y cambiar los datos en la memoria es E (de Enter). Use éste
comando para colocar la instrucción.
- E 100
3756:100 B4.01<↵>
- E 101
3756: 0101 85.D8<↵>
El número de segmento que observa, probablemente sea diferente pero eso no afecta la operación.
ESTILOS DE ADICIÓN
Si damos R, verificaremos que está cargada la instrucción correcta. Los bytes 01h y D8h tal vez no
significan nada para nosotros, pero para la máquina sí; es el código para el nemotécnico ADD AX, BX.
Ahora, debemos decir al procesador dónde encontrar la instrucción (el segmento y el desplazamiento), lo
cual lo encuentra a partir de los registros CS e IP. Al desplegar nuevamente los registros veremos el valor del
segmento en CS, la segunda parte de la dirección se almacena en IP. Colocaremos IP= 0100, inicialmente,
siempre apunta a 0100h.
Ahora le diremos al debug que utilice la instrucción mediante el comando T (Trace), el cual ejecuta la
instrucción en un tiempo. Después de ejecutar una instrucción el IP se incrementa y apunta a la siguiente
dirección, es decir, en 0102h (nosotros no hemos colocado ninguna instrucción en esa dirección, pero el
procesador sí). AX contiene ahora el resultado CD1h. Repita la instrucción, con los valores que conservan los
registros, el resultado en AX=15FBh y en BX=092Ah.
ESTILOS DE RESTA
Ahora, vamos a escribir una instrucción para restar BX a AX, con los datos que conservan. Así que, el
código para la resta es 29h y D8h. Cárguelo a partir de la dirección IP=0100h, ahora ejecute la instrucción T, el
Resultado en AX es 0CD1h, repita nuevamente, ahora AX es 03A7h.
NÚMEROS NEGATIVOS
El procesador usa el complemento a dos para los números negativos. Ahora, trabajaremos con la
instrucción SUB para calcular números negativos. Le haremos una pequeña prueba al procesador para obtener el
resultado FFFFh alias -1. Nosotros le restaremos un 1 a 0 colocando AX=0000 y BX=0001. Repitamos la
instrucción SUB en la dirección 0100. ¿Cuál es el resultado?. Haga la prueba con otros datos.
13
BYTES
Todos los datos hasta ahora han sido representados en palabras. ¿El procesador sabe cómo representar la
aritmética con Byte?. La respuesta es sí.
Los registros de propósito general están divididos en dos bytes, conocidos como alto (High) y bajo
(low). Ahora ejecutaremos la instrucción ADD AH, AL, lo cual es una adición de dos bytes del registro AX y el
resultado quedará en AH. El código para esta instrucción es 00h y C4h.
Ahora, suponga que deseamos sumar 01h y 03h. ¿Podemos colocar 01h en AL?, la respuesta es no.
Tendrá que colocar 0301 porque el debug sólo nos permite cambiar la palabra completa.
Ejecute nuevamente la instrucción, el resultado es 0401h, la suma de 03h + 01h está ahora en AH.
0100h * 7C4B. Los tres dígitos del 100 tienen el mismo efecto que en haxadecimal. El resultado es
7C4B00h, es decir, se suman dos ceros a la derecha DX=007Ch y AX=4B00h. Multiplicar dos palabras juntas
nunca pueden ser más de 2 palabras.
Cuando nosotros dividimos dos números, el procesador da como resultado el cociente y el resto. El
código para la división es F7h y F3h. Colóquelo en la dirección 0100h y 010h. Como la instrucción MUL, DIV
también usa DX:AX. Si desplegamos con R, veremos la instrucción DIV BX.
Ahora, dividamos el resultado de MUL que está en DX:AX, es decir, 007Ch:4B00h. Hagamos la
división 7C4B00h/0100h, por lo tanto pongamos 0100h en BX. El resultado, el cociente en AX=7C4Bh y el
resto en DX=0000h.
14
IMPRIMIENDO CARACTERES
Vamos a iniciar con las interrupciones del DOS para enviar un carácter a la pantalla. Construiremos un
pequeño programa y aprenderemos otra manera de poner datos en los registros. Ahora, veamos si podemos
hablar con el DOS.
Entraremos al Debug y haremos AX= 0200h y DX=0041h. El código en hexadecimal para la instrucción
INT 21h es CD21h, es una instrucción de dos bytes que iniciará en la dirección 100h, use R para confirmar que
IP=100h.
No podemos usar el comando T para ejecutar esta instrucción (puesto que ejecuta una instrucción en un
tiempo), pero la instrucción INT llama (invoca) a un programa largo “subroutine” del DOS, porque trazaríamos
a través de una instrucción en un tiempo. Nosotros queremos ejecutar nuestra línea de programa, pero detenernos
antes de ejecutar la instrucción de la localidad 102h. Lo anterior, podemos hacerlo con el comando G (GO),
indicando la dirección en la cual queremos detenernos.
- G 102 <↵>
DOS imprimirá el carácter A y retornará el control a nuestro programa. En cierto sentido, nuestra línea
de instrucción es en realidad dos instrucciones, la segunda instrucción está en 102h.
INT 21
MOV SP BP
El registro 02h en AH le dijo al DOS que imprima un carácter. Otro número en AH, le dirá al DOS que realice
una función diferente. El DOS usa el número en DL como el código ASCII para el carácter a imprimir. El código
de A=41h.
Son muchas las operaciones que realiza el DOS para imprimir un carácter simple.
Coloque la instruscción INT 20h iniciando en la localidad 100h, verifique con el comando R. Ahora,
ejecute la instrucción con el comando
- G 102 <↵>
Program terminated normally (El programa ha finalizado con normalidad)
15
Para listar varias instrucciones (es decir, ver los nemónicos), necesitamos el comando U (Unassembler).
Al proporcionar
- U 100 <↵>
- G 104 <↵>
(X) es la letra que se deseó imprimir en DX
Program terminated normally
PROGRAMAS ENTEROS
Hasta ahora, nosotros hemos cargado nuestras instrucciones con números (códigos de máquina). El
comando A (Assembler) nos permite cargar nemotécnicos directamente en memoria.
- A 100 <↵>
3970:0100 INT 21
3970:0102 INT 20
3970:0104 <↵>
Aquí, el comando A le dijo al Debug que deseamos colocar instrucciones en forma nomotécnica a partir
de la dirección 0100h.
Para grabar el programa en disco, use primero el comando N (Name), el cual asigna un nombre a un
archivo antes de grabarse.
- N imprime.com
Antes de grabar, debemos proporcionar el número de bytes a grabar a partir de la dirección contenida en
IP. Para hacer esto, se calcula: 4 instrucciones * 2 bytes = 8 bytes de longitud. Otra manera, es observar la
dirección final del programa y hacer
- H 208 200 <↵>
16
El debug utiliza los registros BX:CX para almacenar la longitud de un archivo a grabar, por lo tanto,
colocaremos en CX el 08h y en BX el 00h. Por último, para grabar el programa, usaremos el comando W (Write)
- W <↵>
Writing 0008 bytes
La string termina con el número 24h, que indica fin de la string. Lo anterior, explica porqué el DOS
nunca utiliza el $, es decir, no puede imprimirlo.
Vemos 16 bytes hexadecimales, seguidos de los mismos pero en ASCII. Donde vea un puntoen la
ventana de ASCII, representa un carácter especial, por ejemplo la letra griega beta o theta. El comando D
despliega 96 caracteres de los 256 caracteres usados en la computadora, es decir, que 160 caracteres son
representados por un punto (.).
MOV AH, 09 ;Función imprime string
MOV DX, 0200 ;Dirección donde inicia la string
INT 21
INT 20
Al ejecutarlo,
HELLO, DOS HERE.
El programa ha finalizado con normalidad
Ahora, asigna un nombre al programa y grábalo en disco, puedes usar el comando H para auxiliarte a
calcular la longitud del programa.
17
Registro de banderas
Usos comunes de los registros internos del 8088
Registro Descripción
Datos
AX Acumulador: usado para almacenamiento de programación en general, también para
algunas instrucciones como multiplicación, división, I/O, manejo de cadena de caracteres.
BX Base: Cuando se accesa la memoria, con frecuencia se utiliza este registro para contener
valores de direcciones. Al hacer uso de rutinas de servicios de interrupción, este registro
debe contener un valor que se usa para selección de opciones
CX Contador: durante la ejecución de un loop, este registro contiene el valor de un índice de
conteo
DX Datos: usado para almacenamiento general y también para operaciones de multiplicación y
división
Segmento
CS Registro de segmento de código: éste registro apunta al inicio del segmento donde el
programa en ejecución se encuentra situado
DS Registro de segmento de datos: Señala el inicio del segmento de datos
SS Registro de segmento de pila: Señala el inicio del segmento de pila
ES Registro de segmento extra: Señala el inicio del segmento de extra
Apuntador
SP Apuntador de pila: para algunas instrucciones este registro contiene valores de
desplazamiento para el stack
BP Apuntador base: Similar a SP. Algunas instrucciones hacen uso de el con el fin de guardar
el valor de un desplazamiento
Índice
SI Índice fuente: para ciertas instrucciones, este registro contiene la dirección fuente con
frecuencia las instrucciones que hacen uso de este recurso no requieren de operandos
DI Índice destino: Contraparte con SI y contiene la dirección destino para algunas
instrucciones.
Bits
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
X X X X OF DF IF TF SF ZF X AF X PF X CF
SF
18
Bandera Debug
No. Bit Designación Descripción S O
0 CF Bandera de acarreo: el valor de este bit es 1 si el resultado de una operación de adición CY NC
o sustracción genera un acarreo o préstamo.
1 No usado -------------------------------------------------------------------------------------------------------- ---- ----
-
2 PF Bandera de paridad: es un 1 si el resultado de una operación de datos tiene un número PE PO
par de bits iguales a 1
3 No usado -------------------------------------------------------------------------------------------------------- ---- ----
-
4 AF Bandera de auxiliar de acarreo: indica la presencia de un acarreo generado del cuarto AC NA
bit de 1 byte. Su mayor uso es durante operaciones aritméticas con números decimales
codificados en binario
5 No usado -------------------------------------------------------------------------------------------------------- ---- ----
-
6 ZF Bandera de cero: es activada si el resultado de una operación es cero ZR NZ
7 SF Bandera de signo: se activa si el resultado de una operación con números signados es NG PL
negativo.
8 TF Bandera de trampa: cuando este bit es activado, el 8088 ejecuta una instrucción a la ---- ----
vez (No se considera en el debug)
9 IF Bandera de habilitación de interrupción: el 8088 atenderá a las interrupciones solo EI DI
cuando este bit sea activado
10 DF Bandera de dirección: Cuando es activada, causa que el contenido de los registros DN UP
índice se decremente después de cada operación de una cadena de caracteres
11 OF Bandera de sobreflujo: es activada cuando el resultado de una operación es mayor que OV VN
el máximo valor que es posible representar con el número de bits del operando destino
12-15 No usado -------------------------------------------------------------------------------------------------------- ---- ----
-
19
IMPRIMIENDO NÚMEROS BINARIOS
Si hacemos AX = FFFFh y BX = 01h y ADD AX, BX. Al final de la segunda línea del debug al
dar R, verá 8 pares de letras. Podrá leer NC o CY (acarreo). El resultado de la operación, resultará un
overflow de 1, entonces, la bandera estará en CY (carry). El acarreo es de 1, es decir está activada.
Para comprobar que se ha almacenado el bit 17 (o el noveno para una operación de 8 bits),
adicione 1 a 0 en AX, con IP = 0100h y repitiendo la instrucción de nuevo. La bandera estará afectada
por cada instrucción ADD, y en este momento no hubo acarreo, por lo que se desactivará, lo que indica
con NC (no carry) al dar R.
Al imprimir un número binario, la información del acarreo nos es útil. Imprimiremos solamente
un caracter a un tiempo y sacaremos los bits de nuestro número uno por uno, desde la izquierda a la
derecha. Por ejemplo, el primer caracter del número 1000 0000b es el uno.
Nosotros podemos mover un byte un lugar hacia la izquierda, almacenando el uno en la bandera
de acarreo y adicionando un 0 a la izquierda, repitiendo el proceso para cada dígito sucesivo. Para
hacer esto, usamos la instrucción RCL (rotación de acarreo hacia la izquierda). Ejemplo:
Esto lo hace a través de la bandera de acarreo. La instrucción es llamada rotación porque RCL
mueve el bit de la izquierda a la bandera de acarreo. En este proceso, todos los demás bits son movidos
o rotados a la izquierda. Después de cierto número de rotaciones suficientes (17 para una palabra, 9
para un byte), los bits serán movidos a la posición original y se regresará al número original.
20
Ahora veremos cómo convertir el bit en la bandera de acarreo en un carácter 0 ó 1.
Ahora, nosotros necesitamos un loop para ejecutar RCL, ADC e INT 21h 8 veces, una para cada
bit del byte.
LOOPING
El loop es como un for – next, pero no tan general. Loop decrementa CX y finaliza cuando
CX=0.
Para ejecutarlo, puede hacerse paso a paso con T o con G 010A (sin ejecutar INT 20 puesto que
inicializa los registros). Podemos comprobar que CX = 0 y que BX = C551 o BX = C5D1, dependiendo
del valor inicial de la bandera de acarreo.
Ejecútese con G después de INT 20, BL contiene el número impreso en binario. Recuerde que
no se puede ejecutar con T la instrucción INT 21 e INT 20.
21
IMPRIMIENDO EN HEXADECIMAL
Si la bandera del signo (SF) es cero, entonces, la bandera será PL (Plus o positivo), en caso
cotrario, al activarse será NG (Negative).
Si la bandera del overflow (OF) es cero, entonces, la bandera será VN (No overflow), en caso
contrario, al activarse será OV (Overflow).
Ejemplo:
396F:0100 MOV AL,05
396F:0102 SUB AL,01
396F:0104 JNZ 0102
396F:0106 INT 20
CMP (Compare), permite realizar comparaciones sin almacenar el resultado, es decir, puede
activar únicamente el registro de banderas. Ejemplo:
CMP AX, BX
Si el resultado es cero se activa la bandera del cero en ZR = 1, pero los datos en los registros se
conservan.
If BL < 0Ah
Then BL = BL +30h
Else BL = BL + 37h
22
Otra manera
BL = BL +30h
If BL >= 3A
Then BL = BL +07h
CMP compara DL con 3Ah y activa banderas pero no cambia DL. JL (Jump if less than, Salta si
DL < 3Ah).
Mediante una operación lógica podemos aislar los 4 bits más bajos que representan el segundo
dígito hexadecimal. Para rotaciones de más de un bit utilizamos el registro CL para llevar la cuenta. CL
se usa para indicar el número de veces que va a rotar el byte o palabra.
MOV CL, 04
MOV DL, 5D
SHR DL, CL
MOV AH, 02
MOV DL, BL ;Se inicializa con el número hexadecimal a imprimir
MOV CL, 04
SHR DL, CL
ADD DL, 30
CMP DL, 3A
JL BRINCO ; Aquí debe ir la dirección donde se encentra la INT 21h
ADD DL, 07
BRINCO: INT 21
INT 20
Para aislar e imprimir el segundo dígito, dejando DL = a los 4 bits inferiores, se activan los 4
bits superiores en cero con la función lógica AND, herramienta de lógica formal.
23
Por ejemplo, AND BL, CL
BL 1011 0101
CL 0111 0110
---------------
AND 0011 0100
MOV AH, 02
MOV DL, BL
AND DL, 0F
ADD DL, 30
CMP DL, 3A
JL BRINCO
ADD DL, 07
BRINCO: INT 21
INT 20
24
LEYENDO CARACTERES
Ahora realizaremos el proceso inverso, leeremos dos caracteres en hexadecimal del teclado y lo
convertiremos a un byte.
LEYENDO UN CARACTER
La interrupción 21h puede ser utilizada con la función 01h para leer un caracter (con eco) del
teclado. Al ejecutarse, el cursor se detendrá parpadeando en espera de que presionemos una tecla. Al
hacerlo, el DOS colocará el código ASCII del caracter leído en AL.
JLE (Jump if Less Than or Equal), salta si es menor o igual. Ocasiona que la ejecución de un
programa se ramifique hacia la dirección del operando si la bandera de signo no es igual a la de
sobreflujo o si la bandera de cero está activada. Esta instrucción es funcionalmente igual que JNG
(Jump in Not Greater Than), salta si no es mayor que.
Nota: trabaja correctamente con números en hexadecimal válidos (o sea las letras en
mayúsculas).
25
MOV AH, 01h
INT 21
MOV DL, AL
SUB DL, 30h
CMP DL, 09h
JLE Salto1
SUB DL, 07h
Salto1: MOV CL, 04h
SHL DL, CL
INT 21
SUB AL, 30
CMP AL, 09h
JLE Salto2
SUB AL, 07h
Salto2: ADD DL, AL
INT 20
26
PROCEDIMIENTOS Y PILAS
PROCEDIMIENTOS
Un procedimiento es una lista de instrucciones que podemos ejecutar desde varios lugares de un
programa, en vez de tener que repetirlos cada vez que las necesitemos.
Programa Principal
------ Procedimiento1
------ ------
------ ------
CALL PROCEDIMIENTO1 ------
------ ------
------ RET
------
CALL PROCEDIMIENTO1
------
------
------
End
INC DL, es una nueva instrucción que incrementa en uno el registro DL. RET, regresa al
programa principal situándose en la primera instrucción ejecutable después de su llamada en este caso
LOOP 0105.
27
Ejecute el programa para ver el resultado mediante el comando G.
Dirección Pila
0098:
SP:0100 0100: 0203
0102: 0103
0104:
En una Pila el último que entra será el primero en salir a esto es conocido como LIFO (Last In,
First Out), esta secuencia es precisamente lo que necesitamos para revertir el regreso de direcciones
después de que hacemos llamadas anidadas como en el presente ejemplo.
Aquí la instrucción en la dirección 0100h llama a uno en la dirección 0200h, la cual llama a otra
en la dirección 0300h la cual llama a otra en la dirección 0400 donde finalmente vemos una instrucción
de retorno (RET). Este RET, regresa a la instrucción siguiente previa a la instrucción CALL de la
dirección 0300h o sea el microprocesador ejecuta la instrucción 0303h. pero encontrará otra instrucción
RET en 303h la cual será empujada por la siguiente dirección (la 203h) dejándola fuera de la pila. De
este modo el microprocesador retomará la instrucción ejecutando 2003h al inicio. Cada RET saca en
que se encuentra al inicio regresando la dirección fuera de la pila de este modo cada RET siguiente el
mismo camino volverá a las llamadas hechas atrás.
Dirección Pila
0098: 0303
SP:0098 0100: 0203
0102: 0103
28
0104:
Los registros están compuestos por varios niveles de procedimientos, conduce al siguiente nivel
de abajo. Por salvar los registros al inicio de un procedimiento y restaurarlo al final de este, no
necesitamos remover instrucciones este procedimiento de los diferentes niveles, haciendo nuestra
programación muy sencilla.
Ejemplo.
0200 PUSH CX
0201 PUSH DX
0202 MOV CX,0008
0205 CALL 0300
0208 INC DL
020A LOOP 0205
020C POP DX
020D POP CX
020E RET
Note que los POP’s están en orden inverso que los PUSH’s esto es porque un POP remueve una
palabra situada más recientemente en la pila y el último valor de DX está sobre el último valor de CX.
Salvando y restaurando CX y DX nos permite cambiar los registros dentro del procedimiento
iniciado en 0200h pero sin cambiar los valores usados por el procedimiento llamado y teniendo salvado
CX y DX podremos usar estos registros como variables locales.
0200 PUSH DX
0201 MOV AH,08
0203 INT 21
0205 CMP AL,30
0207 JB 0203
0209 CMP AL,46
020B JA 0203
020D CMP AL,39
020F JA 21B
0211 MOV AH,02
0213 MOV DL,AL
0215 INT 21
0217 SUB AL,30
0219 POP DX
021A RET
021B CMP AL,41
021D JB 0203
021F MOV AH,02
0221 MOV DL,AL
0223 INT 21
0225 SUB AL,37
0227 POP DX
0228 RET
El procedimiento lee un carácter en AL (con la INT 21 de 203h) y verifica que sea válido con
las comparaciones (CMP) y los saltos condicionales. Si el carácter leído no es válido la instrucción es
de salto condicional enviando al microprocesador atrás en la dirección 0203 donde la INT 21 lee otro
caracter (JA, salta si está arriba; JB, Salta si está debajo; ambos tratos son para números sin signo hay
instrucciones JL que usaremos para tratar a números con signo).
En la línea 211h sabremos si tenemos un dígito válido entre 0 y 9 por lo tanto sustraemos el
código para colocar 0 y regresamos el resultado en el registro AL recordando sacar el registro DX
cuando lo salvemos iniciando el procedimiento. El proceso del dígito hexadecimal de A - F es similar.
Observe que tendremos 2 instrucciones RET en este procedimiento; tendremos varios o solamente uno.
30
Como hemos hecho antes, use el comando G, con un punto de interrupción, o use el comando P.
Ejecute la instrucción CALL 200h sin ejecutar la instrucción INT 20h, para que vea los registros antes
de finalizar el programa y que se restablezcan los registros.
Usted verá el cursor al lado izquierdo de la pantalla, esperando un carácter. Teclee k que no es
un carácter válido. Nada debe pasar. Ahora, teclee cualquiera de los caracteres del hexadecimal
mayúsculos. Usted debe ver el valor del hexadecimal del carácter en AL y el propio carácter hechos
eco de en la pantalla. Pruebe este procedimiento con las condiciones del límite: '\ ' (el carácter antes del
cero), 0, 9, ': ' (el carácter sólo después de 9), y así sucesivamente.
Ahora que nosotros tenemos este procedimiento, el programa para leer un número hexadecimal
de dos dígitos, con el tratamiento de errores, bastante aceptable:
0100 CALL 0200
0103 MOV DL, AL
0105 MOV CL, 04
0107 SHL DL, CL
0109 CALL 0200
010C ADD DL, AL
010E MOV AH, 02
0110 INT 21
0112 INT 20
Podemos ejecutar este programa en el DOS, desde que lee en un número hexadecimal de dos
dígitos y entonces muestra el carácter ASCII correspondiente al número tecleado. Aparte del
procedimiento, el programa principal es mas simple que la versión escrita anteriormente, sin tener que
duplicar las instrucciones para leer los caracteres. Nosotros agregamos tratamiento de errores, sin
embargo, y aun cuando complicó nuestro procedimiento, también asegura que el programa acepta sólo
entradas válidas.
A partir de ahora, evitaremos las interacciones complicadas entre los procedimientos, nosotros
seremos muy estrictos sobre guardar cualquier registro usado por un procedimiento.
31
PROGRAMACIÓN EN LENGUAJE ENSAMBLADOR
El alumno programará en Lenguaje Ensamblador para trabajar a bajo nivel en la
computadora y desarrollará diversas aplicaciones.
Proceso de ensamble
1. Editar el programa con un editor de texto simple y que se guarde con extensión .ASM
2. MASM <nombre del archivo>.ASM;
3. LINK <nombre del archivo>;
4. EXE2BIN <nombre del archivo>.EXE <nombre del archivo>.COM
Instrucciones de 3 + 1 direcciones:
Dirección
Código de Dirección Dirección Dirección de la
operación OP1 OP2 de destino siguiente
instrucción
Ejemplos:
ADD A,B,C,D
MUL K,R,PZ
Instrucciones de 3 direcciones:
Eliminando la dirección de la siguiente instrucción surgió la máquina con 3 direcciones. Se
ejecutaría la instrucción siguiente en orden físico:
32
Dirección
Código de del
OP1 OP2
operación resultado
destino
OP1 OP2
Código de Dirección Dirección
operación fuente de destino
Ejemplo:
MUL R1,R2 ; Multiplica R1 a R2 y guarda el resultado en R2
Operando
Código de
fuente /
operación
destino
Ejemplo:
SUB AL, AL ;SUB AL se resta a AL
ADD AH, BH ; ADD BH se suma a AH
DIV AX ; Se divide a AX el valor de AX
DIV AX, CX ;DIV CX se divide AX entre CX
Máquinas de cero instrucciones: Manejando una pila se puede hablar de una máquina con cero
instrucciones (aunque en verdad no son cero instrucciones).
Ésta máquina usa dos instrucciones PUSH (empuja un dato) POP (Jala un dato) para llevar a la
pila y sacar de ella un dato.
Las operaciones se efectúan con los elementos superiores de la pila y el resultado se queda en
ella. Por ejemplo Calcular C=(a+b)/(d-e). La pila es una estructura de datos en memoria en la cual el
primer elemento que entra es el último en salir.
.
.
.
Apuntador de
base (BP) Apuntador
indica la de pila
dirección base (SP)
de la pila
33
B
E A A A+B
D D D-E D-E D-E D-E C
Push D Push E SUB Push A Push B ADD DIV Pop C
Instrucciones en Macroensamblador
[etiqueta] mnemónico de instrucción [operando] [;comentario]
Estructura de un programa
En el Macroensamblador es posible procesar hasta cuatro tipos de segmentos. El programa
mínimo requiere de dos segmentos: el de código y el de pila (CS, SS), el ensamblador busca la
definición del segmento stack y genera un error, si el usuario olvida incluirlo. El pseudo-operador
SEGMENT del macroensamblador sirve para definir un segmento.
Los pseudo-operadores son el medio por el que el programador le indica al ensamblador lo que
debe hacer para preparar y estructurar el código en lenguaje de máquina. Este código de máquina está
basado en los datos e interrupciones contenidos en el programa fuente. Los pseudo-operadores no
generan código máquina. Existen cuatro tipos de pseudo-operadores: de datos (SEGMENT es incluido
en este tipo), de ramificación condicional, macros y listados.
34
Ejemplo de un programa en MASM
PAGE 50, 132
TITLE PRUEBA
COMMENT *
ELABORADO POR: Sexto semestre de L. C. C. del Centro Escolar Felipe Carrillo Puerto
FECHA: 1 de marzo de 2005
DESCRIPCIÓN: Este módulo limpia el contenido del registro AX, dejando ceros. Después coloca en
AX el valor de 18. Todas las operaciones pueden ser observadas en el debug. *
;
;
STACK SEGMENT PARA STACK ‘STACK’
;
;Se inicializará con cero el segmento del stack y se cargará con una cadena de caracteres: 64
;valores de ‘stack ’
;
DB 64 DUP (‘STACK ’)
STACK ENDS
CSEG SEGMENT PARA PUBLIC ‘CODE’
ASUME CS:CSEG, SS:STACK
;
SUB AX, AX
MOV AX, 18
SUB AX, 18
;
CSEG ENDS
END
PAGE (pseudo-operador de listado): sirve para definir las dimensiones de la página y solo
tiene efecto cuando se pone en lista el archivo general durante el ensamble.
; y COMMENT: El ‘;’ sirve para indicar un comentario de una línea y COMMENT para
indicar comentarios multilínea. Para COMMENT el primer carácter diferente de un espacio se usa
como delimitador; todos los demás caracteres que se encuentren entre los delimitadores son
considerados comentarios y por lo tanto ignorados por el ensamblador.
donde:
nombre-seg: indica el nombre del segmento
tipo-alineamiento: señala al ensamblador la manera en la que comenzarán los segmentos en la
memoria. Existen 4 tipos:
PARA: es el predeterminado e indica que el segmento comienza en los límites de un
párrafo (dirección divisible por 16)
BYTE: el segmento puede comenzar en localidad de memoria
WORD: el segmento debe iniciar en el límite de una palabra (donde la dirección sea par)
PAGE: el segmento debe iniciar en una página (los últimos 8 bits de la dirección son
cero)
tipo-combinación: indica la manera en la que los segmentos serán combinados o cargados
cuando se ejecute el encadenador (linker). Existen 5 formas:
35
PUBLIC: todos los segmentos con el mismo nombre y con atributo PUBLIC serán
encadenados juntos.
COMMON: todos los segmentos con el mismo nombre empezarán en la misma
dirección y se traslaparán en memoria.
AT(exp): se utiliza para definir variables con un desplazamiento fijo de memoria. El
segmento se coloca en el párrafo indicado por el resultado obtenido después de
evaluar “exp”.
STACK: Sirve Para indicar que el segmento es parte del STACK.
MEMORY: todos los segmentos de este tipo se colocarán en direcciones de número
mayor que cualesquiera otros segmentos.
‘clase’: se emplea para hacer referencia a una colección de segmentos. Los segmentos con el
mismo nombre de clase se colocan en memoria secuencial, siguiendo el orden en que los encontró el
encadenador.
DB(pseudo-operador de datos): sirve para definir una variable o para inicializar un área de
memoria (DB = Define Byte). Tiene la siguiente forma:
[nombre-variable] DB expresion
DUP (duplica), en el ejemplo DB 64 DUP (‘stack ’) está inicializando un área de memoria con
64 duplicaciones de ‘stack ’
36
PROGRAMANDO CON EL MASM
Ya estamos en condiciones de utilizar el ensamblador, este es un programa del DOS que hará
nuestra programación más fácil. De aquí en adelante, escribiremos las instrucciones mnemónicas,
directamente usando el ensamblador, para convertir nuestros programas en código máquina.
Use el “edit”4 para ingresar las siguientes líneas de código el archivo se llamará
WRITESTR.ASM (la extensión .ASM quiere decir éste es un archivo de origen ensamblador). Así
como con el Debug, puede escribir el código con minúscula o con mayúscula sin embargo nosotros
usaremos las mayúsculas para evitar la confusión entre el número 1 (uno) y la letra minúscula l (ele):
.MODEL SMALL
.CODE
MOV AH, 02h
MOV DL,2Ah
INT 21h
INT 20h
END
Ignore por ahora las tres nuevas líneas en nuestro archivo de origen, note que hay un ‘h’
después de cada número hexadecimal. Esta h le indica al ensamblador que los números son en
hexadecimal. DEBUG supone que todos los números son en hexadecimal, en el ensamblador supone
todos los números decimales. Nosotros le indicaremos que será un número hexadecimal poniendo una
h después de cualquier número.
Nota: El ensamblador puede confundirse por los números, por ejemplo, ACh que se parece un nombre
o una instrucción. Para evitar esto, siempre teclee un cero antes, para diferenciar el número
hexadecimal que empieza con una letra. Por ejemplo, no teclee ACh.
Veamos lo que pasa cuando Ensamblemos un programa con ACh, en lugar de 0ACh. Aquí está
el programa:
.MODEL SMALL
.CODE
MOV DL,ACh
INT 20h
END
4
Sin embargo para facilitar nuestro trabajo usaremos el Programmer’s File Editor, ya que nos proporciona la ventaja de
agregar números de línea que nos ayudará a localizar el número de línea que marque error al momento de compilar.
37
Aquí está la salida
A> MASM TEST;
Microsoft (R) Macro Assembler Version 6.11
Copyright (C) Microsoft Corp 1981, 1998. All rights reserved.
Pero cambiando el ACh a 0ACh se soluciona el ensamblador. También note el espacio de los
comandos en nuestro programa ensamblador. Usaremos los tabuladores para alinear y hacer el texto
más legible. Compare el programa en que usted entró con esta versión:
Ahora regresemos a las 3 líneas nuevas del archivo. Las tres nuevas líneas son todas las
directivas (también llamadas pseudo-ops, o pseudo-operadores). Son llamadas directivas porque, en
lugar de generar instrucciones, proporcionan información y direcciones al ensamblador. El pseudo-op
END marca el fin del archivo, para que el ensamblador sepa qué hacer cuando encuentra un END.
Después, veremos que este END es útil de otras maneras, también. Por ahora, apartemos cualquier
discusión extensa de él o las otras dos directivas y vea cómo usar el ensamblador.
Antes de que probemos el archivo WRITESTR.ASM, asegúrese que todavía es ASCII. Haga
Type del DOS:
A>TYPE WRITESTR.ASM
Debe ver el mismo texto que ingresó. Si ve caracteres extraños en su programa, tendrá que usar
un editor diferente para escribir los programas. Ahora, empecemos a ensamblar Writestr; teclee lo
siguiente:
A>MASM WRITESTR;
Microsoft (R) Macro Assembler
Copyright (C) Microsoft Corp 1981, 1988. A11 rights reserved.
0 Harning Errors
0 Severe Errars
A>
38
Linking
Ahora queremos que nuestro LINK tome nuestro .OBJ y cree uno .EXE de él. Enlace
WRITESTR.OBJ tecleando:
A>LINK WRITESTR;
Microsoft (R) Overlay Linker
Copyright (C) Microsoft Corp 1963-1988.
All rights reserved. LINK: warning L4021: no stack segment
A>
Aunque el LINK nos advierte que no hay ningún segmento de la pila, nosotros no necesitamos
uno ahora mismo. Después de que nosotros aprendemos a agregar más instrucciones, veremos porqué
nosotros podríamos querer un segmento de la pila.
Ahora nosotros guardemos nuestro .EXE, pero este no es el último paso. Nosotros tenemos un
archivo .COM tal y como lo creamos con el DEBUG. De nuevo, se verá después porqué nosotros
necesitamos todos estos pasos. Para ahora, creemos un archivo .COM de Writestr.
La contestación no nos dijo mucho. Para ver si Exe2bin trabajó, listemos todo el Writestr
guardados hasta ahora: con DIR WRITESTR.*
Regresando al DEBUG
Leamos nuestro archivo .COM (o .EXE) en el DEBUG y desensamble para ver cómo el
DEBUG reconstruye nuestro programa en código-máquina de WRITESTR.COM
A>DEBUG WRITESTR.COM
_U
397F:0100 8402 MOV AH, 02
397F:0102 822A MOV DL, 2A
397F:0104 CD21 INT 21
397F:0106 CD20 INT 20
Los comentarios
En los programas del lenguaje ensamblador, nosotros ponemos los comentarios después de un
punto y coma El ensamblador ignora algo en la línea después de un punto y coma, para que nosotros
podemos agregar algo que nosotros queremos.
.MODEL SMALL
.CODE
MOV AH, 2h ;Selecciona la función 2 de DOS para sacar un caracter
MOV DL, 2Ah ;Carga el código ASCII a ser impreso
INT 21h ;Imprime con la NT 21h
INT 20h ;y Sale del DOS
END
Las etiquetas
Cuando quisimos bifurcar (saltar) en una parte del programa a otro con uno de los comandos de
la bifurcación (JNZ, JLE, etc), nosotros teníamos que saber la dirección específica nosotros estábamos
saltando a. Cada vez que programábamos, insertábamos nuevas instrucciones obligando a que
39
cambiemos las direcciones en las instrucciones de salto. El ensamblador cuida de este problema con
nombres de etiqueta que nosotros damos a las direcciones de cualquier instrucción o situaciones de la
memoria. Una etiqueta tiene lugar. En cuanto el ensamblador vea una etiqueta, reemplaza la etiqueta
con la dirección correcta antes de enviarlo delante del microprocesador.
Las etiquetas pueden ser de hasta 31 caracteres y puede contener letras, números, y algunos de
los siguientes símbolos: un signo de interrogación (?), un punto (.), una arroba ( @ ) un guión bajo ( _ )
o un signo de peso ( $ ). Sin embargo no puede empezar con un dígito (0-9) y el punto puede ser usado
solamente como el primer carácter.
Ejemplo:
0111
010C JLE DIGIT1
010E SUB DL
DIGIT1: 0111 MOV CL
0113 SHL DL,1
Tomemos como ejemplo práctico el código empleado para leer 2 dígitos hexadecimales.
0100 MOV AH, 01h
0102 INT 21
0104 MOV DL, AL
0106 SUB DL, 30h
0109 CMP DL, 09h
010C JLE Salto1
010E SUB DL, 07h
0111 MOV CL, 04h
0113 SHL DL, CL
0115 INT 21
0117 SUB AL, 30
0119 CMP AL, 09h
011B JLE Salto2
011D SUB AL, 07h
011F ADD DL, AL
0121 INT 20
No será obvio lo que este programa hace, y si no está fresco en su mente, usted puede tener que
trabajar un poco para entender el programa de nuevo. Agreguemos etiquetas y comentarios para
clarificar su función:
.MODEL SMALL
.CODE
MOV AH, 01h ;Seleccione la función 1 del DOS, para introducir un carcater
INT 21 ;Lee un caracter, y retorna el código ASCII en el registro AL
MOV DL, AL ;Mueve el código ASCII dentro de DL
SUB DL, 30h ;Resta 30H para convertirlo en digito de 0 a 9
CMP DL, 09h ;Está el dígito entre 0 y 9?
JLE Salto1 ;Si, entonces tenemos el primer dígito
SUB DL, 07h ;No, Resta 7H para convertirlo en letra de la A a la F
Salto1: MOV CL, 04h ;Prepara para multiplicar por 16
SHL DL, CL ;Realiza el corrimiento hacia la izquierda
INT 21 ;Solicita el siguiente caracter
SUB AL, 30 ;Repite la operación
CMP AL, 09h ;Es un dígito de 0-9?
JLE Salto2 ;Si, tenemos el segundo dígito
40
SUB AL, 07h ;No, restamos 7H
Salto2: ADD DL, AL ;Agregamos el segundo dígito
INT 20 ;Finalizamos
END
Las etiquetas, SALTO1 y SALTO2, son de un tipo conocido como Etiquetas Cercanas (NEAR),
porque los dos puntos (:) aparecen después de las etiquetas cuando ellos están definidos. El término
NEAR tiene que ver con segmentos que nosotros hablaremos más adelante junto con las directivas
.MODEL, y .CODE. Si ensambla el programa y lo desensambla con el DEBUG, verá que SALTO1 se
reemplazó por 0111h y SALTO2 se reemplazó por 011Fh.
41
Modos de direccionamiento
Se refiere a la manera en la que el procesador puede accesar un operando para realizar una
operación los cuales existen 7 modos básicos:
Direccionamiento de registro: El valor del operando fuente está almacenado en algún registro de
propósito general. Se interpreta la longitud del operando con el nombre del registro.
SUBAX, AX
MOV AX, BX
MOV DS, AX
Tomando como ejemplo la primera instrucción la unidad de ejecución (EU) toma el operando
del registro AX, determina como destino del propio registro AX y la AUL lleva a cabo la operación.
Los dos modos de direccionamiento anteriores; tienen los ciclos de ejecución más pequeños y se
evitan tiempos de acceso a la memoria. Solamente hay que tener en cuenta que al momento de hacer
una operación las dos instrucciones sean del mismo número de bytes
MOV EBX, AX ;EBX es un registro del microprocesador 80386 y son registros extendidos
;AX es menor número de bytes que EBX ya que EBX es de 32 bits y AX de 16 por lo
;tanto AX si se le puedeasignar a EBX pero no al revés
42
Se usa generalmente como matrices y/o vectores. Otra manera
LEA BX, DATA ;LEA = LOADEFECTIVE ADDRESS
MOV AX, [BX]
43
Ejemplo de los diferentes modos de direccionamiento
PAGE 40, 132
TITLE DIRECCIONAMIENTOS
COMMENT*
ELABORADOPOR: Sexto semestre de L. C. C. del Centro Escolar Felipe Carrillo Puerto
FECHA: 15de marzo de 2005
DESCRIPCIÓN: Esta rutina muestra los diferentes modosde direccionamiento disponibles en el Macro
ensamblador. *
Cuando DOS coloca el programa del usuario en memoria para su ejecución, guarda
automáticamente, en el registro del segmento de datos DS, la dirección del segmento prefijo del
programa PSP. Esta característica será importante cuando se estudie la manera de regresar el control a
DOS una vez terminado el programa. El primer programa de ejemplo no cuenta con esta instrucción y
su ejecución traerá como consecuencia que el sistema se pierda, sin embargo en este programa al
finalizar regresa el control a DOS.
44
El programador debe asegurarse de que al término del programa el registro CS contenga la
dirección del PSP (Program Segment Prefix) y que IP sea igual a 0000H.
Para tener acceso al segmento de datos (DS) del programa del usuario, debe cargarse en DS la
dirección de inicio del segmento de datos de éste. Las siguientes instrucciones
MOV AX, SEGDATA
MOV DS, AX
ubicadas en el segmento de código, se utilizan con este fin. El operador SEG determina la dirección del
segmento DATA (la dirección de inicio del segmento de datos). La primera instrucción coloca esta
dirección en AX. La segunda, coloca esta dirección en DS. Si estas instrucciones no se ejecutan
entonces DS continuará con el contenido de la dirección de PSP y, por lo tanto todas las referencias que
se hagan a las variables contenidas en el segmento de datos del usuario no serán válidas ya que sus
direcciones serán incorrectas. Antes de hacer cualquier cosa, sin embargo, la dirección del PSP
(segmento y desplazamiento) se deban guardar en el stack para que cuando el procedimiento STAR
lleve a cabo un regreso FAR, al ejecutar la instrucción RET, pueda obtener del stack la dirección del
PSP.
Los procedimientos son muy útiles para organizar y reducir el código de un programa. El
pseudo-operador que permite definir un procedimiento es PROC es de tipo de datos y tiene la siguiente
forma:
Nombre del procedimiento PROC NEAR
(o
Nombre del procedimiento PROC FAR)
…
RET
…
nombre del procedimiento ENDP
El atributo NEAR es opcional. Por cada salida en el procedimiento, debe incluirse una
instrucción de retorno RET. El procedimiento debe finalizar con ENDP. En el ejemplo el
procedimiento START contiene todas las proposiciones ejecutables en el programa y es un
procedimiento FAR, debido a que cuando termine cederá el control a DOS.
45
LOS PROCEDIMIENTOS Y EL ENSAMBLADOR
En las clases anteriores construimos en el DEBUG un programa con una llamada (CALL). El
programa no hizo nada más que la impresión de las letras de la A a la J, y se parecía a esto:
Convirtamos este código en un programa para el ensamblador. Será difícil leerlo sin no le
agregamos tabuladores y comentarios, por lo que se lo agregaremos para hacer nuestro programa más
legible:
46
.MODEL SMALL
.CODE
IMPAJ PROC
MOV DL, ‘A’ ;Inicializa con el carácter A
MOV CX, 10 ;Imprime 10 caracteres, inicializando con A
SALTO: CALL IMP_CAR ;imprime caracter
INC DL ;mueve el siguiente carácter del alfabeto
LOOP SALTO ;continua con los 10 caracteres
MOV AH, 4CH ;Retorna el control del DOS
INT 21H
IMPAJ ENDP
IMP_CAR PROC
MOV AH, 2 ;Función para imprimir un caracter
INT 21H ;imprime el carácter contenido en el registro DL
RET ;Regresa de este procedimiento
IMP_CAR ENDP
END IMPAJ
Hay dos nuevas directivas: PROC, y ENDP. PROC y ENDP son las directivas que definen los
procedimientos. Como puede ver, el programa principal y el procedimiento en 200h son encerrados por
el par de directivas PROC y ENDP.
PROC define el principio de un procedimiento; ENDP define el fin. La etiqueta delante de cada
uno, es el nombre que nosotros damos al procedimiento que ellos definen. Así, en el procedimiento
principal, IMPAJ, podemos reemplazar la instrucción CALL 200H con la llamada mas legible de
CALL IMP_CAR. Simplemente inserte el nombre del procedimiento y el ensamblador asigna las
direcciones.
Desde que nosotros tenemos dos procedimientos, necesitamos decirle al ensamblador cuál debe
usar como procedimiento principal. La directiva END da indicación de este detalle. Escribiendo END
IMPAJ, le dijimos al ensamblador que IMPAJ es el procedimiento principal. Mas adelante veremos que
el procedimiento principal puede estar en cualquier parte. Ejecute una prueba a IMPAJ.
Nota: Si encuentra algún mensaje del error que no reconozca, verifique que ha tecleado correctamente
en el programa. Si eso falla, verifique la lista algunos errores comunes.
47
Cuando esté satisfecho del resultado del programa, use el Debug para desensamblar el programa
y vea cómo el ensamblador encaja los dos procedimientos juntos. La llamada para poder leer un
archivo particular en el DEBUG teclee su nombre con. Por ejemplo, teclear DEBUG IMPAJ.EXE, y
una vez hecho, veremos:
c:\MASM611\BIN>DEBUG IMPAJ.EXE
-U
0D73:0000 B241 MOV DL,41
0D73:0002 B90A00 MOV CX,000A
0D73:0005 E80800 CALL 0010
0D73:0008 FEC2 INC DL
0D73:000A E2F9 LOOP 0005
0D73:000C B44C MOV AH,4C
0D73:000E CD21 INT 21
0D73:0010 B402 MOV AH,02
0D73:0012 CD21 INT 21
0D73:0014 C3 RET
-
Nuestro programa es bueno y cómodo, sin el hueco entre los dos procedimientos.
48
PRUEBA_IMP_HEX ENDP
PUBLIC IMP_HEX
;------------------------------------------------------------------------------------------------------------------;
; Este procedimiento convierte el byte del registro DL a hexadecimal y escribe ;
; dos dígitos hexadecimales en la posición actual del cursor ;
; La entrada: DL el byte lo convertirá en hexadecimal ;
; Use: IMP_DIGIT_HEX ;
;------------------------------------------------------------------------------------------------------------------;
IMP_HEX PROC ;Punto de entrada
PUSH CX ;Salva los registros usados en este procedimiento
PUSH DX
MOV DH, DL ;Hace una copia del byte
MOV CX,4 ;Da el nibble superior en DL
SHR DL, CL
CALL IMP_DIGIT_HEX ;Despliega el primer dígito hexadecima
MOV DL, DH ;Da un nibble inferior dentro de DL
AND DL, 0Fh ;Remueve el nibble superior
CALL IMP_DIGIT_HEX ;Despliega el segundo dígito hexadecimal
POP DX
POP CX
RET
IMP_HEX ENDP
PUBLIC IMP_DIGIT_HEX
;------------------------------------------------------------------------------------------------------------------;
; Este procedimiento convierte los 4 bits bajos de DL a un dígito hexadecimal y lo ;
; escribe en la pantalla ;
; La entrada: DL Los 4 bits bajos contiene un número para ser impreso en hexadecimal ;
; Uses: IMP_CAR ;
;------------------------------------------------------------------------------------------------------------------;
IMP_DIGIT_HEX PROC
PUSH DX ;Salva los registros usados
CMP DL,10 ;Este nibble es <10?
JAE LET_HEX ;No, convierte a letra
ADD DL,“0” ;Si, convierte a digito
JMP Short IMP_DIGIT ;Ahora escribe este carácter
LET_HEX: ADD DL, “A”-10 ;Convierte a letra hexadecimal
IMP_DIGIT: CALL IMP_CAR ;Despliega la letra en la pantalla
POP DX ;Restaura los valores antiguos de DX
RET
IMP_DIGIT_HEX ENDP
PUBLIC IMP_CAR
;------------------------------------------------------------------------------------------------------------------;
; Este procedimiento imprime un carácter en la pantalla usando la función de llamada del DOS ;
; La entrada: DL Byte a imprimir en la pantalla. ;
;------------------------------------------------------------------------------------------------------------------;
IMP_CAR PROC
PUSH AX
MOV AH,2 ;Función para imprimir un caracter
INT 21h ;Saca el carácter que se encuentra en el registro DL
POP AX ;Restaura los valores antiguos de AX
RET ;Y regresa
IMP_CAR ENDP
END PRUEBA_IMP_HEX
49
La función del DOS para imprimir los carácteres trata algunos carácteres especialmente. Por
ejemplo, usando la función del DOS a la salida 07 resultados en un pitido, sin imprimir el carácter para
07 que son un diamante pequeño. Nosotros veremos más adelante una nueva versión de IMP_CAR que
imprimirá un diamante, dónde aprenderemos sobre las rutinas de ROM BIOS dentro de su PC. Por
ahora, sin embargo, usaremos la función del DOS para imprimir los carácteres.
La nueva directiva PUBLIC está aquí para el uso futuro: Nosotros lo usaremos, cuando
aprendemos sobre el diseño modular. PUBLIC simplemente le dice al ensamblador que genere un poco
más de información para el LINK. El LINK nos permite juntar partes separadas de nuestro programa,
ensamblándolos desde archivos fuente diferentes, uniéndolos en un solo programa. Y el PUBLIC
indica al ensamblador que el procedimiento nombrado después de la directiva PUBLIC debe hacerse
público o disponible a los procedimientos de otros archivos.
Ahora, PIMHEX contiene los tres procedimientos para escribir un byte como un número
hexadecimal, y un programa principal corto para probar estos procedimientos. Nosotros estaremos
agregando muchos procedimientos al archivo cuando desarrollamos Dskpatch, y al final,
PIMHEX.ASM se llenará de muchos procedimientos de uso general.
El procedimiento PRUEBA_IMP_HEX que hemos incluido hace lo que dice: Está aquí para
probar la impresión de un hexadecimal que, a su vez, usa IMP_HEX_DIGIT e IMP_CAR. En cuanto
nosotros hayamos verificado que estos tres procedimientos son correctos, nosotros borraremos
PRUEBA_IMP_HEX de PIMHEX.ASM.
Cree la versión del COM de Video_io, y use Depure para probar completamente ESCRIBA--
IEX. Cambie los 3Fh al lOlh de situación de memoria a cada uno de las condiciones del límite que
nosotros probamos en Capítulo 5, entonces use G para ejecutar TEST_WRITE--IEX.
Usaremos muchos programas de prueba simple para probar los nuevos procedimientos que
hemos escrito. De esta manera, podremos construir un programa pieza por pieza, en lugar de intenta
construir y depurar solo uno. Este método incremental es muy más rápido y fácil, desde que podemos
confinar los errores a sólo el nuevo código.
50
LOS PRINCIPIOS DE DISEÑO MODULAR
Note el encabezado de cada procedimiento en PIMHEX que hemos incluido, este es un bloque
de comentarios que describen la función de cada procedimiento brevemente. Es importante, estos
comentarios ya que dicen qué registros del procedimiento usa para pasar la información de un lado a
otro, así como qué otros procedimientos usa. Como característica de nuestro diseño modular, el bloque
del comentario nos permite usar cualquier procedimiento mirando la descripción. No hay necesidad de
entender cómo el procedimiento hace su trabajo. Esto también lo hace bastante fácil de reescribir un
procedimiento sin tener que reescribir cualquiera de los procedimientos que lo llaman.
También hemos usado las instrucciones PUSH y POP para guardar y restaurar cualquier registro
que nosotros usemos dentro de cada procedimiento. Nosotros haremos esto para cada procedimiento
que escribimos, salvo nuestros procedimientos de prueba. Este diseño, también, es la parte del estilo
modular que nosotros usaremos.
Cada llamada guardaremos y restauraremos cualquier registro usado para que no tengamos que
preocuparnos por las interacciones complejas entre procedimientos que intentan luchar sobre el número
pequeño de registros en los microprocesadores. Cada procedimiento es libre usar tantos registros como
desee, siempre y cuando los restaura antes de la instrucción de RET. Es un precio pequeño para pagar
por la simplicidad agregada. Además, cuando guardamos y restauramos los registros, la tarea de
reescribir los procedimientos estaría mentalmente rasgada. Seguramente perdería mucho pelo en el
proceso.
Nosotros también intentamos usar muchos procedimientos pequeños, en lugar de uno grande.
Esto, también, hace nuestra tarea de programación más simple, aunque a veces escribiremos
procedimientos largos cuando el diseño se complique particularmente.
Estas ideas y métodos se usarán a partir de ahora. En la siguiente clase, por ejemplo,
agregaremos otro procedimiento a PIMHEX: un procedimiento para tomar una palabra del registro DX
e imprimir el número decimal en la pantalla.
Nosotros agregaremos nuevo directivas a este esqueleto del programa más adelante, pero por el
momento puede usar, lo aquí mostrado, como punto de partida para los nuevos programas que
escribirá.
52
IMPRIMIENDO EN DECIMAL
Prometimos escribir un procedimiento que tome una palabra (word) y lo imprima en notación
decimal. IMP_DECIMAL usa algunos trucos nuevos -la manera de guardar aquí un byte, en unos
microsegundos allí. Quizás el truco pareciera no merecer la pena de ver el esfuerzo. Pero si usted los
memoriza, encontrará que puede usarlos para acortar y acelerar los programas. A través de los trucos,
aprenderemos también aproximadamente dos nuevos tipos de operaciones lógicas adicionales a la
instrucción AND. Primero, repasemos el proceso por convertir una palabra a dígitos decimales.
LLAMANDO A LA CONVERSIÓN
La División es la clave para convertir una palabra en dígitos decimales. La llamada a la
instrucción DIV calcula la respuesta del entero y su resto. Así que, calculando 12345/10 devuelve 1234
como la respuesta del entero, y 5 como el resto. En este ejemplo, 5 es simplemente el dígito más a la
derecha. Y si nosotros dividimos de nuevo por 10, obtendremos el siguiente dígito a la izquierda. La
división repetida por 10 saca los dígitos de derecha a izquierda, cada vez poniéndolos en el resto.
Claro, los dígitos salen en orden inverso, pero programando el lenguaje ensamblador, nosotros
tenemos que solucionarlo. ¿Recuerda la pila? Simplemente está como una pila de bandejas del
almuerzo: El primero en salir de la cima es la última que entró. Si nosotros sustituimos los dígitos
como las bandejas y ponemos los dígitos uno encima del otro cuando se obtenga cada resto cuando los
53
saquemos de la pila obtendremos el número correcto. Nosotros podemos iniciar con los dígitos en el
orden correcto.
El dígito de la cima es el primer dígito en nuestro número, y los otros dígitos estarán debajo de
él. Así, si nosotros empujamos a la pila los restos cuando los calculamos y los imprimimos cuando los
saquemos de la pila, los dígitos estarán en el orden correcto. El siguiente programa es el procedimiento
completo para imprimir un número en la notación decimal. Como habrá notado, hay trucos ocultos en
este procedimiento. Nosotros pronto conseguiremos bastante de ellos, pero primero probemos
IMP_DEC para ver si funciona antes de que nos preocupemos sobre cómo funciona.
IMP_DEC
;------------------------------------------------------------------------------------------------------------------;
; Este procedimiento imprime 16-bit, un número sin signo en notación decimal ;
; La Entrada: DX N: 16-bit, número sin signo. ;
; Usa: IMP_DIGIT_HEX ;
;------------------------------------------------------------------------------------------------------------------;
IMP_DEC PROC NEAR
PUSH AX ;Guarda los registros usados aquí
PUSH CX
PUSH DX
PUSH SI
MOV AX, DX
MOV SI, 10 ;para dividir por 10 usando SI
XOR CX, CX ;cuenta los dígitos localizados en la pila
NO_CERO: XOR DX, DX ; Poner la palabra superior de N a 0
DIV SI ;Calcula N/10 y (N mod 10)
PUSH DX ;Empuja un dígito dentro de la pila
INC CX ;Un dígito mas agregado
OR AX, AX ;N = 0 todavía?
JNE NO_CERO ;Nope, continue
CICLO_IMP_DIGIT: POP DX ;Da el dígito en orden inverso
CALL IMP_DIGIT_HEX
LOOP CICLO_IMP_DIGIT
END_DECIMAL: POP SI
POP DX
POP CX
POP AX
RET
54
IMP_DEC ENDP
Observe que hemos incluido un nuevo registro, el registro SI (el Índice Fuente). Luego veremos
porqué se le ha dado ese nombre, y nos encontraremos a su hermano, el registro DI, o Índice Destino.
Ambos registros tienen usos especiales, pero también pueden usarse como fueran registros de uso
general. Desde que IMP_DEC necesite cuatro registros de uso general, nosotros usamos SI, aunque
nosotros pudiéramos usar BX, simplemente para mostrar que este SI (y DI) puede servir registros de
uso general si fuera necesario.
Antes de que probemos nuestro nuevo procedimiento, necesitamos hacer otros dos cambios a
VIDEO_IO.ASM. Primero, debemos borrar el PRUEBA_IMP_HEX del procedimiento y debemos
insertar este procedimiento de prueba en su lugar (o mejor aun mantenerlo como comentario mediante
la cláusula COMMENT):
PRUEBA_IMP_DEC PROC
MOV DX,12345
CALL IMP_DEC
MOV AH, 4CH
INT 21h ;Regresa el control al DOS
PRUEBA_IMP_DEC ENDP
El procedimiento prueba IMP_DEC con el número 12345 (qué el ensamblador convierte a una
palabra 3039h).
Haga estos cambios y déle una vuelta rápida a VIDEO_IO. Conviértalo a la versión .EXE y vea
si funciona. Si no lo hace, verifique su archivo fuente para los errores (y mire los errores comunes). Si
quiere aventurarse, intente encontrar su error con el Debug. Después de todos, para eso es el Debug.
ALGUNOS TRUCOS
El primero es una instrucción eficaz para establecer un registro para poner a cero. No es mucho
más eficaz que MOV AX, 0, y quizás no merece la pena el esfuerzo, pero es la clase de truco que
encontrará en las personas que lo usa, aquí. La instrucción:
OR AX, AX
55
poner a cero los conjuntos el registro AX. ¿Cómo? Para entender, necesitamos aprender sobre la
operación lógica llamada OR Exclusivo, con el nombre de XOR.
es verdadero si sólo uno de sus bits es verdadero, no son verdaderos si ambos son verdaderos o
falsos. Así, si nosotros hacemos a sí mismo un OR exclusivo al mismo número, conseguimos un cero:
1 0 1 1 0 1 0 1
XOR 1 0 1 1 0 1 0 1
0 0 0 0 0 0 0 0
Éste es el truco. Nosotros no encontraremos otros usos para la instrucción de XOR más
adelante, pero este uso es de bastante interés.
Por otro lado, usted encontrará a muchas personas que usan otro truco rápido para establecer un
registro a cero. En lugar de usar la instrucción XOR, nosotros podríamos usar:
SUB AX, AX
Ahora para el otro truco. Es casi tan desviado como nuestro esquema XOR de borre un registro,
y usar al primo del OR Exclusivo -la función OR.
Verifiquemos el registro AX para ver si es cero. Para hacer esto, usaremos la instrucción CMP
AX, 0. Pero no usaremos un truco más bien usaremos una acción más eficaz. Así que, escribamos OR
AX, AX y seguimos esta instrucción la bifurcación condicional JNE (Salta si no es igual). También
podríamos usar el JNZ (Salta si no es cero).
56
La instrucción OR, es similar a una ecuación matemática, establece banderas, incluyendo la
bandera del cero. AND y OR son parecidos a un concepto lógico. Pero aquí, un resultado OR es verdad
si uno de los bits es verdad:
OR 0 1
0 0 1
1 1 1
La instrucción OR también es útil para establecer simplemente un bit en un byte. Por ejemplo:
1 0 1 1 0 1 0 1
OR 1 0 1 1 1 1 0 1
1 0 1 1 1 1 0 1
Primero, el registro CX se usa para contar cuántos dígitos hemos empujado hacia la pila, para
que nosotros sepamos cuántos debemos sacar. El registro CX es una opción particularmente
conveniente, porque nosotros podemos construir un bucle (ciclo) con la instrucción del LOOP y
podemos usar el registro CX para repita la cuenta para almacenar. Nuestra opción hace el ciclo de
dígito de salida (CICLO_IMP_DIGIT) sea trivial, porque la instrucción LOOP usa el registro CX
directamente. Usaremos CX muy a menudo cuando tengamos que contar un almacenaje.
Luego, tenga cuidado de verificar el límite condicional. La condición del límite con el 0 no es
un problema, como podrás verificar. La otra condición del límite es 65535, o FFFFh con el que se
puede verificar fácilmente con el Debug. Carga VIDEO_IO.EXE en el Debug tecleando DEBUG
VIDEO_IO.EXE y cambie los 12345 (3039h) por 101h a 65535 (FFFFh). IMP_DEC funciona con
números sin signo. Vea si puede escribir una versión para escribir números con signo.
57
Habrá notado un punto importante aquí, mientras teniendo que hacer con los 8088, no nuestro
programa. Depure los trabajos principalmente con los bytes (por lo menos el comando de E hace) pero
nosotros queremos cambiar una palabra. Nosotros debemos tener el cuidado, desde las 8088 tiendas los
bytes en un orden diferente. Aquí es un unassemble para la instrucción de MOV:
Usted puede decir de la parte de BA3930 de esta pantalla que el byte a las 101h es 39h, y el uno a las
102h es 30h (BA es la instrucción de MOV). Los dos bytes son los dos bytes de 3039h, pero
aparentemente en orden inverso. ¿Confundiendo? Realmente, el orden es lógico, después de una
explicación corta.
Una palabra consiste en dos partes, el más bajo byte y el byte superior. El más bajo byte es el byte
significante (39h - en 3039h), mientras el byte superior es la otra parte (30h). tiene sentido, entonces,
para poner el más bajo byte a la más bajo dirección en la memoria. (Muchas otras arquitecturas de
computación, como la Motorola,
68000 en el Apple Macintosh, realmente invierta estos dos bytes, y éste puede ser un bit que confunde
si usted está escribiendo los programas en varios tipos diferentes de computadoras. )
Pruebe los números diferentes para el palabra arranque a las 101h, y usted verá cómo este
almacenamiento trabaja. Use TEST_WRITE-DECIMAL ver si usted le hiciera derecho, o unassemble
la primera instrucción.
58
Segmentos y desplazamientos
Para direccionar la memoria, las computadoras utilizan 20 bits, sin embargo la CPU procesa
palabras de 16 bits en sus registros de direcciones.
CS: Contiene la dirección de inicio del segmento donde residen las instrucciones del programa en
ejecución.
DS: Retiene (señala hacia) la dirección donde inicia el segmento en el que se definen las variables
SS: Señala hacia el segmento donde se encuentra el Stack (pila). Esta es una estructura de datos de
memoria donde pueden colocarse byte o palabras una después de otra y que, posteriormente se
puede recuperar (PUSH, POP) (PUSH = mete o empuja, POP = jala o saca)
ES: Apunta a un segmento definido por el usuario y que regularmente contiene datos adicionales.
Dado que los segmentos pueden ser de hasta 64Kb, se necesita especificar otro parámetro (una
dirección de 16 bits) para accesar las localidades de memoria dentro del segmento.
Ejemplo
0001 0000 1010 1111 (0000) (dirección del segmento)
1111 0000 1111 1111 (dirección del desplazamiento)
---------------------------------------------
0001 1111 1011 1110 1111 (dirección de 20 bits)
59
En hexadecimal 0100
0AFF 0101 FF Notación
0AFF:0100
0102 0A
Segmento:desplazamiento
0103
0104
Por lo tanto estamos en la
dirección 0100 del segmento
0AFF
Mapa de la memoria RAM
La presente tabla muestra a grandes razgos la manera en que se encuentra distribuida la
memoria en la PC para una IBM PC y XT.
Dirección de 20 bits
Inicio Fin Descripción
00000 0FFFF Esta área contiene los primeros 64K de memoria de acceso aleatorio
(64K) (RAM), en los primeros sistemas fabricados por IBM, esta cantidad es la
máxima que puede ser instalada sobre la tarjeta del sistema
10000 3FFFF 192K adicionales de memoria puede ocupar esta región. En versiones más
(256K) nuevas de la PC, esta memoria puede estar también instalada sobre la
tarjeta del sistema
40000 9FFFF En esta región el usuario puede instalar hasta 384K de memoria RAM,
(640K) adicionales lo que permite tener un total de 640K de memoria RAM para
el usuario en versiones PC
A0000 A3FFF Área de 16K reservada por IBM
(656K)
A4000 BFFFF Este espacio es un buffer de 112K para gráficas y visualización de vídeo
(768K)
C0000 C7FFF Área de 32K para expansión de memoria ROM
(800K)
C8000 C9FFF 8K de memoria ROM que contiene el programa controlador del disco duro
(808K)
CA000 F3FFF Área de 168K reservada para el ROM de diversas tarjetas adaptadoras para
(976K) soporte de aplicaciones
F4000 F5FFF Área de 8K reservada para memoria ROM del usuario se relaciona con un
(984K) socket de repuesto
F6000 FDFFF Espacio de 32K para cassette BASIC
(1016K)
FE000 FFFFF Área de 8K reservada para el sistema entrada/salida (I/O) de BASIC
(1024K)
60
SEGMENTOS
En las clases anteriores, nos encontramos varias veces con la directiva que se trata a los
segmentos. Ahora vamos a prestarle atención a los segmentos y cómo el microprocesador maneja una
dirección completa en un megabyte (1,048,576 bytes) de memoria. Para esto, empezaremos a entender
porqué los segmentos necesitan su propia directivas en el ensamblador, más adelante empezaremos a
usar los diferentes segmentos (como los lejos).
El problema, en este caso, inicia con los direccionamientos mayores de 64K de memoria-el
límite con una palabra, la cual el 65535 es el número más grande que una sola palabra puede sostener.
A partir de los microprocesadores 8088 de Intel, se usaron los segmentos y registros del segmento para
“solucionar” este problema.
Hasta ahora, no hemos tenido relación con este problema. Nosotros hemos estado usando el
registro IP para tener la dirección de la próxima instrucción que el microprocesador ejecutará. Nosotros
hemos dicho que dirección real se forma de los registros CS y del IP. Recordemos.
61
Como podrás observar en la siguiente Figura, los microprocesadores dividen la memoria en
muchos segmentos superpuestos; con un nuevo segmento de inicio cada 16 bytes.
62
Ahora, esto puede parecer una forma extraña de direccional más de 64K de memoria, pero
funcionan. Pronto, veremos lo bien que trabaja.
Antes de continuar, observemos un programa corto, diferente a cualquiera que hemos visto
anteriormente, eso usan dos segmentos diferentes. Capture este programa en el archivo llamado
P_SEG.ASM:
DOSSEG
.MODEL SMALL
.STACK ;Localiza 1K en la pila.
.CODE
P_SEGMENT PROC
MOV AH,4Ch ;Función de pregunta para la salida-al-dos
INT 21h ;Retorno a DOS
P_SEGMENT ENDP
END P_SEGMENT
Ensamble y vincule P_SEG, pero no genere un archivo .COM. El resultado será P_SEG.EXE el
cual es ligeramente diferente a un archivo .COM
Nota: Nosotros tenemos que usar un método diferente que INT 20h para finalizar los
archivos .EXE. Para los archivos .COM, INT 20h trabajas perfectamente bien, pero no funciona en
absoluto para los archivos .EXE porque la organización de segmentos es muy diferente, por lo que
veremos más adelante; más de estas diferencia después. De aquí en adelante usaremos la INT 21h,
función 4Ch para finalizar nuestros programas.
63
Cuando usemos Debug en un archivo .COM, Debug establece todo el registro de segmento al
mismo número, con el programa de inicio a un desplazamiento de 100h para el inicio de este segmento.
Los primeros 256 bytes (100h) son usados para almacenar varias partes de información que no estamos
realmente interesados, pero que estaremos espiando la parte de esta área un poco.
Ahora, pruebe P_SEG.EXE en el Debug, para ver lo que pasa con los segmentos en un
archivo .EXE:
C:>DEBUG P_SEG.EXE
_R
AX=0000 BX=0000 CX=0004 DX=0000 SP=0400 BP=0000 SI=0000 DI=0000
DS=3985 ES=3985 SS=3995 CS=3995 IP=0000 NV UP DI PL NZ NA PO NC
3995:0000 B44C MOV AH, 4C
LA PILA
En nuestro programa, definimos dos segmentos. El segmento STACK donde nosotros ponemos
la pila (.STACK), y el segmento de código (.CODE) que es en donde todas nuestras instrucciones se
64
almacenan. La directiva STACK le dice al ensamblador que crea una pila de 1024 bytes. (Nosotros
podríamos crear una pila más grande o más pequeña poniendo un número después de .STACK. Por
ejemplo, .STACK 128 crearían una pila 128 bytes de largo.)
La dirección para la cima de la pila está dada por SS:SP. SP es el apuntador de la Pila,
semejante al IP y CS para el código, y es un desplazamiento dentro del Segmento de Pila actual.
Realmente, “cima de la pila” es un nombre equivocado, porque la pila crece de la memoria alta
hacia la memoria baja. Así, la cima de la pila realmente está en el fondo de la pila en la memoria, y se
ponen nuevas entradas a la pila progresivamente como la memoria baje. Aquí, SP es 400h que es igual
a 1024 en decimal (4*162) porque nosotros definimos una área de la pila de 1024 bytes de largo.
Nosotros no hemos puesto nada en la pila, para que la cima-de-pila está en la cima de la memoria
nosotros establecimos al lado para la pila: 400h.
Acerca de donde estaba la pila, si usted mira la pantalla del registro (R) para WRITESTR.COM,
usted verá que la pila está en el mismo fin del segmento (SP=FFEE):
_R
AX=0000 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=3995 ES 3995 SS=3995 CS=3995 IP=0100 NV UP EI PL NZ NA PO NC
3995:0100 B402 MOV AH 02
El DOS siempre pone el puntero de la pila al mismo fin del segmento cuando carga un
archivo .COM en la memoria. Por esta razón, nosotros no necesitamos declarar un segmento de pila
(con . STACK) para los archivos .COM.
65
A>DEBUG P_SEG.EXE
_R
AX=0000 BX=0000 CX=0004 DX=0000 SP=0000 BP=0000 SI=0000 DI=0000
DS=3985 ES=3985 SS=3995 CS=3995 IP=0000 NV UP EI PL NZ NA PO NC
3D90:0000 B44C MOV AH ,4C
La pila está ahora en 3995:0 que es el inicio de nuestro programa (CS:0). Ésta es una mala
noticia. Nosotros no queremos a la pila en cualquier parte cercana al código de nuestro programa.
También, desde que el puntero de la pila esté en SS:0, no tiene ningún cuarto para crecer (desde que la
pila crece en la parte baja de la memoria). Por estas razones, nosotros debemos declarar un segmento
de pila en los programas .EXE.
Nota: usted debe declarar siempre un segmento de pila con .STACK en los programas .EXE.
66
Nota: Esta “SCRATCH AREA” (área improvisada) se actualmente llama PSP (el Prefijo del
Segmento de Programa) y contiene la información para el uso por del DOS. En otras palabras, no debe
suponer que podrá hacer uso de esta área.
Entre otras cosas, estos 256-bytes del PSP al inicio del programa contiene los caracteres que
tecleamos después del nombre de nuestro programa. Por ejemplo:
c:\MASM611\BIN>debug p_seg.exe Ahora veremos algunos caracteres en el vertedero de memoria
-d ds:80
0D69:0080 3C 20 41 68 6F 72 61 20-76 65 72 65 6D 6F 73 20 < Ahora veremos
0D69:0090 61 6C 67 75 6E 6F 73 20-63 61 72 61 63 74 65 72 algunos caracter
0D69:00A0 65 73 20 65 6E 20 65 6C-20 76 65 72 74 65 64 65 es en el vertede
0D69:00B0 72 6F 20 64 65 20 6D 65-6D 6F 72 69 61 0D 65 20 ro de memoria.e
0D69:00C0 6D 65 6D 6F 72 69 61 0D-65 63 69 66 69 71 75 65 memoria.ecifique
0D69:00D0 20 75 6E 61 20 64 69 72-65 63 63 69 A2 6E 20 64 una direcci.n d
0D69:00E0 65 0D 20 32 2E 30 2E 0D-00 00 00 00 00 00 00 00 e. 2.0..........
0D69:00F0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
El primer byte nos dice que tecleamos 3Ch (o 60) caracteres, incluso el primer espacio después
de P_SEG.EXE.
El PSP también contiene información que el DOS usa cuando terminamos la ejecución de un
programa, con las instrucciones INT 20h o INT 21h, función 4Ch,. Pero por razones que están del todo
claras, la instrucción INT 20h espera el registro CS el punto de inicio de este PSP cuando es un
programa .COM, pero no para un programa de .EXE. Ésta es una cuestión histórica. Y, en efecto, la
función de salida (INT 21h, función 4Ch) se agregó al DOS con la introducción a la versión 2.00 para
hacer más fácil la salida de los programas .EXE; la función 4Ch no espera el registro CS el punto de
inicio del PSP. Usaremos la INT 21h, función 4Ch de aquí en adelante para terminar nuestros
programas.
El código para los archivos .COM siempre deben iniciar en un desplazamiento de 100h en el
segmento de código dejando espacio para estos 256-byte del PSP al inicio. Esto es diferente al
archivo .EXE ya que este código inicia en IP = 0000, porque el segmento de código inició 100h bytes
después de iniciado del área en la memoria.
Antiguamente, la mayoría de los programas se escribían como programas .COM porque eran
más simples escribir. Pero Actualmente, la mayoría de los programas son escritos como .EXE. Por lo
que de aquí en adelante estaremos trabajando enteramente con los programas .EXE.
67
La Directiva DOSSEG
Si observa de nuevo P_SEG.EXE, notará que el segmento de pila está en la memoria alta del el
segmento de código. En nuestro archivo fuente definimos la pila (.STACK) antes de cualquiera código
(.CODE). ¿Y porqué la pila está en la memoria alta que el código?
Observe el código de máquina a la izquierda que la instrucción CALL, ocupa sólo tres bytes
(E8F800). El primer byte (E8h) es la instrucción CALL, y el segundo, dos bytes, forman un
desplazamiento. Los microprocesadores calculan la dirección de la rutina llamándola agregándole este
desplazamiento 00F8h a la dirección de la próxima instrucción (108h en nuestro programa). En este
caso, entonces, tenemos F8h + 108h = 200h. Precisamente lo esperado.
68
En efecto esta instrucción usa una sola palabra para el desplazamiento indicando que las
Llamadas (CALL) están limitadas a un solo segmento de 64K bytes de largo. ¿Así, cómo es que
nosotros podemos escribir un programa que es más grande que 64K? Nosotros hacer uso de llamadas
FAR (lejanas) y NEAR cercanas.
Una llamada NEAR, como hemos visto, se limita a un solo segmento. En otras palabras, ellos
cambian el registro del IP sin afectar el registro de CS. Por esta razón estos son conocidos como
llamadas intrasegmentos.
Pero también podemos tener llamadas FAR que cambian los registros CS e IP. Estas llamadas
son conocidas llamadas intersegmentos porque llaman a los procedimientos en otros segmentos.
Las dos versiones de la instrucción CALL tienen dos versiones de la instrucción RET.
La Llamada NEAR, metemos una sola palabra en la pila para su dirección del retorno. La
correspondiente instrucción RET hace sacar esta palabra fuera de la pila y dentro del registro IP.
En el caso de las llamadas FAR y su retorno, una palabra no es suficiente, porque estamos
tratándonos con otro segmento. En otros palabras, necesitamos guardar una dirección de retorno de dos
palabras en la pila: una palabra para el apuntador de instrucción (IP) y otro para el segmento de código
(CS). El retorno FAR, entonces, saca dos palabras fuera de la pila -uno para el registro CS y el otro
para el IP.
¿Cómo sabe el ensamblador cuál de estas dos llamadas y retornos usar? ¿Cuándo debe usar la
llamada FAR, y cuándo debe usar la llamada NEAR? Respuesta: poniendo una directiva NEAR o un
FAR después de la directiva PROC.
69
Por ejemplo, mire el siguiente programa:
PROC_UNO PROC FAR
.
.
.
RET
PROC_UNO ENDP
Para la instrucción RET, por otro lado, el ensamblador mira la definición del procedimiento que
contiene la instrucción de RET. En nuestro programa, la instrucción de RET para PROC_UNO será un
70
RET FAR, porque PROC_UNO se declara como un procedimiento FAR. De la misma manera, el RET
en PROC_DOS es un RET NEAR.
¿Qué pasa cuándo nosotros no ponemos una directiva NEAR o FAR después de PROC? El usa
la información de la directiva .MODEL para determinar si los procedimientos están NEAR o FAR, si
usted no declara un procedimiento explícitamente como NEAR o FAR. Nosotros estamos usando la
directiva .MODEL SMALL que le dice al ensamblador que nosotros sólo tenemos un segmento de
código, para que todos los procedimientos sean procedimientos cercanos (NEAR). Hay otras
directivas .MODEL (como el MEDIUM) esto le dice al ensamblador que haga los procedimientos
lejanos (FAR) si estos no se declaran explícitamente como cercanos (NEAR).
Cuando los microprocesadores reciben una interrupción, necesita almacenar más información
sobre la pila que simplemente las dos palabras para la dirección de retorno. Tiene que almacenar los
valores del estado de banderas -la bandera de acarreo, la bandera del cero, y así sucesivamente. Estos
valores se almacenan en una palabra conocido como el Registro de Banderas, y el microprocesador
mete esta información en la pila antes de la dirección del retorno.
Ahora, observe como una interrupción ocurre entre estas dos instrucciones del programa:
CMP AH,2
JNE NOT_2
71
Supongamos AH=2, para que la bandera del cero se establecerá después de la instrucción CMP
lo que quiere decir que la instrucción de JNE no se bifurcará a NOT_2.
Ahora, imagine que el reloj interrumpe al microprocesador entre estas dos instrucciones. Eso
significa que el microprocesador detiene lo que está ejecutando para llevara cabo el procedimiento de
la interrupción antes de que verifique la bandera del cero (con la instrucción JNE). Si el
microprocesador no guarda y restaura la bandera del registro, la instrucción JNE usaría banderas
establecidos por el procedimiento de la interrupción, no de nuestra instrucción de CMP. Para prevenir
tales desastres, los microprocesadores guardan y restauran siempre el registro de banderas para las
interrupciones. Una interrupción guarda las banderas, y una instrucción IRET (Interrupt Return
-Retorno de la Interrupción) restaura las banderas al final del procedimiento de la interrupción.
Lo mismo es verdad para una instrucción INT. Así, después de ejecutar la instrucción:
INT 21
la pila del microprocesador se parecerá:
cima de la pila anterior IP (retorno de direcciones parte I)
anterior CS (retorno de direcciones parte II)
anterior registro de bandera
(La pila crece en la parte bajo de la memoria, para que el Registro de banderas anterior esté
realmente en la parte más alta de la memoria).
Debug usos una bandera especial en el registro de bandera llamada bandera de Captura (Trap
Flag). Esta bandera pone al microprocesador en un modo especial conocido como modo del paso
simple el cual el Debug lo utiliza para analizar los programas una instrucción a la vez. Cuando la
bandera de captura es fija, el microprocesador arroja una INT 1 después de ejecutar cualquier
instrucción.
72
La INT 1 también borra la bandera de captura, para que el microprocesador no esté en el modo
del paso simple mientras estemos Depurando el procedimiento INT 1. Pero desde que INT 1 guarda las
banderas a la pila, arroja un IRET para devolver al programa lo que estábamos depurando restaurando
la bandera de captura. Entonces, nosotros recibiremos otra interrupción INT 1 después de la próxima
instrucción en nuestro programa. Éste es simplemente un ejemplo de cuanto es útil guardar los registros
de bandera. Pero, cuando veamos luego, esta característica de restaurar la bandera no siempre es
adecuada.
Estos vectores son sumamente útiles para agregar las características al DOS, porque ellos nos
permiten que interceptemos las llamadas para interrumpir los procedimientos cambiando las
direcciones en la tabla del vector. Todas estas ideas y métodos deben ponerse más claros cuando vemos
más ejemplos.
73
Vaciando la memoria
Instrucción group
Rutinas varias
74
PROGRAMACIÓN EN LENGUAJE ENSAMBLADOR
El alumno programará en Lenguaje Ensamblador para trabajar a bajo nivel en la computadora y
desarrollará diversas aplicaciones.
Diseño de software
Instrucciones de control
Instrucciones de salto
Instrucciones de comparación
Estilo y forma
75