Vous êtes sur la page 1sur 44

INTRODUCCION AL ASM: ARQUITECTURA

=================================
En el interior de un ordenador, el microprocesador (abreviadamente uP) es
un chip ms interconectado a los chips de memoria, etc... La diferencia es que
es un chip que, adems de ser capaz de responder a las seales que le llegan
del exterior, es capaz de generar seales para otros chips y recoger la
respuesta de stos.
Supondremos en principio que tenemos un ordenador con un uP y un nico
chip de memoria. Supondremos que el chip de memoria es de 64 kilobytes.
La memoria es igual que una serie de celdillas, en la que cada celdilla puede
contener un nmero entre 0 y 255d (0 y 0FFh), es decir, que est compuesta
de ocho bits. Las celdillas se numeran comenzando en la 0 y acabando en la
65535d (o 0FFFFh), y a este nmero o ndice se le denomina 'direccin' de la
posicin de memoria o celdilla. Por tanto, con un nmero entre 0 y 65535d se
identifica una posicin de memoria en concreto. Una memoria soporta dos
operaciones distintas: LECTURA y ESCRITURA. Veremos cmo se realizan estas
dos operaciones a nivel de circuito.
De los pines o patillas del chip de memoria, los que ms nos interesan son
los siguientes: CE o Chip Enable, RD/WR o Read/Write, las 16 patillas A0-A15,
y las ocho D0-D7. Estas patillas pueden estar a dos niveles, 0V o +5V. Para las
patillas de control se adopta una convencin: si se dice que una patilla est
'activa a nivel alto' indica que cuando se quiere activar la funcin de una
patilla hay que ponerla a +5; y cuando se dice que es 'activa a nivel bajo'
indica que hay que ponerla a 0, y el nombre de la patilla se suele escribir
con una rayita por encima. Aqu, tomaremos la convencin (para simplificar),
de que todas las patillas son activas a nivel alto. La patilla RD/WR indica
si la operacin es lectura o escritura, y asumiremos que a nivel alto significa
lectura.
Cuando el circuito externo a la memoria quiere leer el dato de una determinada posicin de memoria, tiene que hacer varias cosas:
-Poner CE a nivel alto (activa el chip)
-Poner RD/WR a nivel alto (indica lectura)
-Poner en las lineas A0-A15 el nmero en binario de la direccin de mem.
de la que se quiere leer el dato. Para saber la seal que hay que
meter a partir de la direccin, tomamos un ejemplo:
Posicin 23345d (decimal) -> 5B31h (hex.)->
0101101100110001b (binario)
Los bits o dgitos de un nmero se numeran empezando por la derecha con
el 0, 1,... hasta el 15 que es el de la izquierda en este caso.
Por tanto, para leer de esta posicin la lnea A0 debera estar a 5V

(representa un 1), las A1,A2 y A3 a 0v (representa un 0),...


-Esperar algunos ciclos de reloj (depende de la velocidad del chip) y
recoger el dato de las lneas D0-D7, que son los 8 dgitos binarios o
bits del nmero almacenado.
Cuando el circuito externo lo que quiere es escribir, debe hacer lo mismo
excepto que deber poner RD/WR a nivel bajo y en lugar de leer el dato de D0-D7
deber poner l mismo los bits del dato a escribir, a menudo algunos ciclos
despus de poner CE activo y RD/WR a nivel bajo para dar tiempo al chip a
reaccionar.
El propio uP tiene las lneas A0-A15 entre sus pines, que van conectadas a
las A0-A15 de la memoria, y tiene tambin D0-D7 conectadas a las de la memoria.
Tambin tiene una patilla RD/WR, que pone al valor correspondiente cuando
quiere acceder a la memoria. Y tambin tiene una patilla, que en el Z80 (el uP
que llevaban los Spectrum, los Amstrad CPC y los MSX) se llama MREQ (Memory
Request), que pone a nivel alto cuando quiere acceder a memoria (lectura o
escritura). Esta patilla, como habris imaginado, va conectada al CE de la
memoria. A las lneas que interconectan el uP con otros dispositivos se les
denomina genricamente 'bus', clasificndose en tres grupos funcionales: el
'bus de direcciones', formado por las lneas A0-A15, el 'bus de datos', formado
por las D0-D7, y el 'bus de control' que incluye todas las dems lneas. El
nmero de lneas para el bus de direcciones se denomina 'anchura del bus de
direcciones', y anlogamente para el bus de datos. El modelo idealizado que
hemos visto tendra una anchura del bus de direcciones de 16 bits, y una
anchura del bus de datos de 8 bits. Este modelo es muy similar a la
arquitectura real de un Spectrum, por ejemplo.
Un uP determinado se disea de manera que pueda reconocer ciertos nmeros
como instrucciones que le indican hacer algo determinado. Estas instrucciones
que el uP es capaz de reconocer forman el 'conjunto de instrucciones' del uP,
y los nmeros que le corresponden a cada una son los 'codigos de operacin' de
las instrucciones. Algunas instrucciones sencillas son slo un byte, otras
estn formadas por 2 o ms bytes. Una ristra de estas instrucciones del uP
forman un programa, y a este 'lenguaje', formado nicamente por nmeros y que
entiende el uP directamente, se le llama cdigo mquina. Al principio, los
ordenadores se programaban introduciendo directamente los cdigos de operacin
en la memoria, pero evidentemente adems de tedioso resultaba de lo ms
proclive a errores. Adems, revisar un listado de nmeros presenta una gran
dificultad para el programador. Por tanto, se define para cada uno de las
instrucciones del uP una palabra representativa de su funcin, a la que se
llama 'mnemnico', a la que eventualmente habr que aadir los operandos para
poder generar el cdigo de operacin ('opcode') completo, ya que el cdigo de
operacin lleva informacin tanto de la instruccin a ejecutar como de los
operandos que sta manejar. El programa que traduce un texto con instrucciones en este nuevo lenguaje se denomina 'assembler' o 'ensamblador', y este

lenguaje de programacin se denomina 'assembly language' o 'lenguaje ensamblador'. Algunos ensambladores conocidos para la familia 80x86 son el MASM o
Macro ASseMbler de Microsoft y el TASM o Turbo ASseMbler de Borland. El
programa DEBUG que viene con el MS-DOS o el SID que viene con el DR-DOS tambin
son capaces de traducir instrucciones ASM en sus correspondientes cdigos de
operacin, pero a un nivel mucho ms primitivo que el MASM o el TASM.
Ahora que ya sabemos cmo el uP puede acceder a la memoria, y cmo son los
programas que ejecuta ste veamos cul es la tarea del micro y que es lo que
hace constantemente.
Para que el uP ejecute un programa, se deben situar los cdigos de operacin
del programa a partir de una direccin determinada. A esta direccin a partir
de la que est el programa la denominamos 'direccin de comienzo' por razones
evidentes. El uP internamente tiene algo de memoria, muy poca, estructurada
en lo que se denomina 'registros'. Cada registro est formado por una serie de
bits, 16 para ser precisos en el 8086, de forma que puede contener un nmero
entero en un rango determinado (con 16 bits podremos almacenar nmeros entre 0
y 65535d o 0FFFFh). Los registros dependen de cada uP en concreto, pero veremos
dos que existen en todo uP: el llamado 'Instruction Pointer' o 'IP' en el 8086,
y el registro de instruccin. Para comprender el funcionamiento de ambos,
veremos un modelo idealizado de un ordenador con un uP, un chip de memoria de
64K, un bus de direcciones de 16 bits, uno de datos de 8 bits y un conjunto de
instrucciones en el que todas tienen un cdigo de operacin de un slo byte.
El uP tiene una patilla llamada CK habitualmente ('clock' o reloj), por el
que recibe una seal cuadrada producida por un cristal de cuarzo y algo ms de
circuitera (seal cuadrada es una seal que peridica y alternativamente toma
dos valores, en el caso de los PCs 0V y +5V. La velocidad de un ordenador en
megahercios o megaciclos por segundo no es ms que la frecuencia de la seal
de reloj). El uP realiza varias tareas, al ritmo del reloj que se le conecta.
Estas tareas requieren uno o varios ciclos de reloj, pero son siempre las
mismas y se repiten constantemente:
-Primero, el uP genera una lectura de memoria para leer el byte
contenido en la direccin de memoria indicada por IP. El valor
recibido se almacena en el registro de instruccin. Es decir, se
activan MREQ y RD/WR se pone a RD, poniendo el valor contenido en
IP en las lneas A0-A15. El valor que se lee de D0-D7 se introduce
en el registro de instruccin.
-El uP incrementa el registro IP en uno, para luego leer la siguiente
instruccin de la serie.
-El uP decodifica la instruccin del registro de instr., es decir, ve
qu significa, y la ejecuta. Esta ejecucin puede implicar ms accesos
a memoria para lectura/escritura, puede cambiar el IP directamente
para saltar a otra direccin (a modo del GOTO del basic), etc...

-Vuelve al primer paso, es decir, lee la siguiente instruccin.


Y as, el uP ejecuta su trabajo 'ad infinitum'. Esta secuencia se repite
incansablemente, a no ser que el micro reciba una seal que lo interrumpa, un
reset, o algn otro suceso poco habitual (las interrupciones son relativamente
poco habituales).
Antes de ver el funcionamiento del 8086 en concreto, que es el uP que la
arquitectura IBM PC define como estndar mnimo, revisaremos cmo se comunica
el uP con los perifricos, ya que hasta ahora slo hemos visto la comunicacin
con la memoria.
En un ordenador, adems de la memoria existen otros dispositivos que
requieren que el uP interacte con ellos. Estos dispositivos pueden ser el
mdem, la tarjeta grfica, el teclado,... entre muchos otros. Estos dispositivos se comunican con el micro envindole datos y recibiendo datos de ste.
Algunos son slo de lectura (el uP slo puede recibir datos de ellos), otros
slo de escritura (el uP puede enviarles datos, pero ellos no envan nada a
ste), y muchos otros son tanto de lectura como de escritura.
Una de las maneras posibles sera hacer que el uP tuviese una patilla para
cada dispositivo, al igual que la MREQ, de forma que cuando quisiera acceder
a uno en concreto pusiese su patilla correspondiente a nivel activo y escribiese o leyese el dato de o en el bus de datos. Pero ya que los dispositivos
son muchos y los uP se disean de forma general para distintos circuitos, lo
que se hace es asignar a cada dispositivo un nmero o direccin, de forma
parecida a la que cada direccin de memoria identifica una celdilla, y poner
una patilla en el uP (llamada IORQ - Input Output ReQuest - en el Z80, en el
8086 tendr un nombre parecido) que se pone a nivel activo cuando se quiere
acceder a cualquier dispositivo. Cada perifrico (llamando perifrico a todo
lo que no sea la memoria o el uP) debe responder slo cuando IORQ est activa
y lea en el bus de direcciones el nmero que le corresponde. Cada uno de estos
valores que se sitan en el bus de direcciones se llama 'puerto de entrada/
salida'. Los puertos que le corresponden a cada dispositivo es una de las
cosas que identifican un estndar, en nuestro caso el estndar PC. Algunos
dispositivos, como la tarjeta grfica, necesitan intercambiar muchos datos con
el uP y por tanto requieren varios puertos. Por cierto, cada tarjeta grfica
tiene sus puertos estndar, por ejemplo los puertos de una Hrcules monocromo
no coinciden en absoluto con los de una VGA color, y por tanto se pueden
montar ambas en el mismo ordenador.
En el siguiente captulo saldremos de este ordenador idealizado para entrar
de lleno en la arquitectura segmentada de los compatibles PC, y veremos los
registros del 8086.
Salut!!!

INTRODUCCION AL ASM: EL 8086 Y LA ARQUITECTURA PC


=================================================
En este segundo captulo del curso de ASM, veremos cmo difiere el 8086 de
la arquitectura ideal que vimos en el primer captulo. Despus, comenzaremos
a estudiar los registros del 8086.
El 8086, que es el uP que llevan los compatibles PC (todos los uP de la
serie 80x86 de Intel, los NEC V20 y V30, el Cyrix486, los AMD... son
totalmente compatibles con el 8086, es decir, reconocen el mismo juego de
cdigos de operacin, aunque incluyan algunos - o muchos - ms), difiere
bastante del esquema que vimos en el captulo anterior.
Lo primero es que los buses de direcciones y de datos tienen otra anchura:
20 bits para el bus de direcciones y 16 para el de datos. Cuando el uP genera
una lectura de memoria de la direccin x, se leen los bytes de la direccin (x)
y de la direccin (x+1), componindose con estos la palabra de 16 bits que se
enva al uP. En el 8086 y todos los uP Intel (y el Z80, y muchos otros), el
byte de la dir. (x) se usa para los 8 bits desde D0 hasta D7, y el de la dir.
(x+1) para los bits desde D8 hasta D15. Este ordenamiento de bytes se suele
denominar 'ordenamiento Intel' (adivinad por qu...), mientras que otros uP
como el Motorola MC68000 y los de su serie (usados en los Mac) usan el
'ordenamiento Motorola', en el que los bytes de una palabra (con una palabra
nos referimos al conjunto de dos bytes) se almacenan al reves en memoria.
El 8088 se diferenciaba del 8086 bsicamente en que el bus de datos del uP
era de 8 bits, por lo que las lecturas de 16 bits deban realizarse en dos
pasos. A nivel de programacin esto no se percibe, ya que una instruccin que
produzca una lectura de 16 bits se ejecuta igual pero tarda el doble, mientras
que la circuitera externa debe ser algo ms complicada. Por lo dems, el 8088
lo consideraremos idntico al 8086 y hablaremos nicamente de ste ltimo. Las
VGA, por ejemplo, son dispositivos que aaden memoria a la propia del ordenador, de manera que decir que una VGA es de 8 bits o de 16 indica si los accesos
a memoria de 16 bits se hacen en dos pasos, como en los ordenadores con 8088, o
en uno. Queda claro por qu el mismo programa corre mucho ms rpido en una
VGA de 16 bits que en una de 8, siempre que el programa haga accesos a memoria
de palabra en palabra.
El problema que se present a los diseadores del 8086 fue que los registros
internos del uP (el IP y muchos ms que estudiaremos detalladamente ms adelante) deban ser de 16 bits, probablemente por limitaciones de diseo, mientras
que las direcciones que se envan por el bus de direcciones (y valga la redundancia) deban ser de 20 bits. Para componer una direccin de memoria completa
son necesarios otros 4 bits adems de los 16 de cada registro, ya que si estos
cuatro bits se ponen siempre al mismo valor slo tendremos acceso a 64K de
memoria. La solucin que se adopto ha venido limitando la accesibilidad de

memoria de los PCs desde entonces, y es la razn de que ahora mismo existan
cosas como la memoria extendida, la expandida, y otras peculiaridades que
suponen un gran handicap para los programas que corren en PCs frente a otros
ordenadores como los Mac.
La solucin pasaba por aadir en el interior del micro varios registros de
16 bits llamados 'registros de segmento'. Son cuatro en el 8086 y el 286, y 2
ms en el 386 y superiores. Aqu nos limitaremos a usar los del 8086, pero el
uso de los otros es idntico en los 386+. La direccin completa de 20 bits se
forma a partir de un registro de segmento, que se elige en cada momento, y un
registro de 16 bits que se denomina 'desplazamiento' u 'offset'. Una de las
posibilidades hubiese sido tomar los 4 bits bajos del registro de segmento y
aadirles detrs los 16 del offset. As compondramos una direccin completa:
SEGMENTO | OFFSET
0000 0000 0000 1010 0101 1111 0000 1010
\
dir. completa /
1010 0101 1111 0000 1010 (20 bits)
De esta forma, los 4 bits bajos del segmento determinaran uno de los 16
bancos de 64K contenidos en un megabyte de memoria (el espacio direccionable
con 20 bits), y el offset determinara la direccin en concreto a partir de la
direccin base de este segmento. Si el segmento fuera 0000h accederamos a los
64K ms bajos del megabyte, si fuera 0001h accederamos a los 64K siguientes, y
as hasta un valor de segmento de 000Fh con el que accederamos a los ltimos
64K. As, haciendo un smil en el que una carta de una baraja represente el
espacio de memoria accesible para cada valor de segmento, nuestra baraja (el
conjunto de todos los segmentos) est formada por 16 cartas, y est dispuesta
con cada carta junto a la otra sin superponerse, de manera que con todas las
cartas podemos acceder al mega completo de memoria. Con esta disposicin a cada
posicin de memoria slo puede acceder por medio de una carta, y por tanto el
par segmento:offset que determina una direccin de memoria concreta es nico.
Pero ya que los registros de segmento se hicieron de 16 bits, se eligi otra
forma de hacerlo de manera que se aprovechan totalmente los 16 bits del
segmento: lo que se hace para generar la direccin completa es desplazar el
segmento cuatro bits hacia la izquierda, rellenando los 4 bits de la derecha
con ceros, y sumarle al valor de 20 bits resultante el offset. El desplazamiento a la izquierda equivale a multiplicar por 16 o aadir un cero a la
derecha en hexadecimal:
/ valor de seg. \
16d x SEGMENTO 0000 0000 0000 1010 0000 <- 4 bits 'extra'
OFFSET
0101 1111 0000 1010
--------------------------------------DIRECCION REAL 0000 0101 1111 1010 1010 (20 bits)

As, el valor de segmento determina una direccin base (la formada por su
valor con los cuatro bits a cero), a partir de la cual el offset especifica
cunto hay que moverse para hallar la direccin en concreto. Si meditamos un
poco sobre esto, podemos apreciar que cada segmento de 64K (que es el espacio
direccionable variando el offset y dejando el segmento fijo) se superpone con
otros segmentos. Ahora, volviendo al smil de la baraja, tenemos 64K (64*1024)
cartas, correspondientes a los 64K posibles diferentes valores de segmento.
Tambin ahora podemos acceder al mega completo, pero las cartas estn dispuestas no una al lado de la otra, sino que cada carta est dispuesta 16 posiciones
ms a la derecha (o ms arriba) que la anterior, de manera que se superpone a
la anterior casi en toda su extensin. Pero, como ya hemos dicho, la anchura
total de las cartas (la memoria total accesible) sigue siendo de un megabyte.
As, una misma direccin real se puede generar a partir de diferentes valores
de segmento y offset. Por ejemplo, veamos la posicin de memoria absoluta 17d
o 00011h. Esta direccin se puede especificar como valor de segmento 0 y offset
17d (0000:0011h), pero podemos apreciar que el par 0001:0001 tambin representa
la misma direccin absoluta. Para especificar una direccin por medio del segmento y el offset se suele poner el valor del segmento (sin los cuatro bits que
se aaden a la derecha), dos puntos, y el valor de offset. Como segundo ejemplo, las direcciones 0040h:0000 y 0000:0400h son totalmente equivalentes y
ambas referencian la misma direccin absoluta de memoria, la 00400h o 1024d.
Para ir entrando un poco en cmo es el 8086 en concreto, recordaremos un
registro que ya mencionamos: el IP o 'Instruction Pointer', que contiene la
direccin de la siguiente instruccin a ejecutar. Este registro es de 16 bits,
por lo que necesitamos un registro de segmento para componer la direccin
completa de la siguiente instruccin. El segmento 'CS' o 'Code Segment' es el
registro con el que el uP compone la direccin completa de la siguiente
instruccin a recoger y ejecutar.
En un libro de programacin en ASM tradicional, ahora veramos el conjunto
completo de registros del 8086, la funcin de cada uno, etc... Estos libros,
adems de para el aprendizaje, estn pensados como referencia para el programador que ya conoce el micro o que ya conoce ASM de algn otro micro. Pero ya
que esto es un curso pensado para gente que nunca antes haya tenido contacto
con el lenguaje ensamblador, se seguir otro orden: veremos primero algunos de
los registros, cuya funcin se puede comprender con los conocimientos adquiridos hasta ahora, y a medida que se vayan introduciendo conceptos como la pila,
los saltos condicionales, etc... veremos el resto de los registros.
Todos los micros, incluyendo el menos potente, suelen tener un registro que
se utiliza de forma preferente para las operaciones aritmticas y lgicas.
Algunos micros, como el Z80, tienen algunas instrucciones que slo pueden
ejecutarse con este registro. Este registro se suele denominar 'acumulador',
y en el 8086 toma el nombre simblico de 'AX'. El 8086, adems del AX,

incorpora otros tres registros denominados 'de propsito general', con los que
tambin se pueden hacer algunas operaciones aritmticas. Pero, por ejemplo, las
multiplicaciones o divisiones slo se pueden hacer con el AX. Los otros
registros tambin presentan peculiaridades, instrucciones que operan sobre un
registro determinado, pero las iremos viendo sobre la marcha. Estos otros tres
registros se representan mediante los nombres 'BX', 'CX' y 'DX', y son todos
de 16 bits.
Para componer direcciones de memoria, adems del CS que se usa principalmente para complementar al IP, existen otros registros de segmento. Por el
momento describiremos dos de los otros tres que existen, el 'DS' (Data
Segment) y el 'ES' (Extra Segment). Cuando leemos datos de la memoria, dando
la direccin de la que leer por medio de un registro, por defecto se construye
la direccin completa usando el DS, aunque se puede especificar en la instruccin si se quiere usar el ES o el CS en su lugar.
En el siguiente captulo veremos la primera instruccin de ASM, la ms
usada, la instruccin 'MOV'. Y comentaremos algunas cosillas de cmo empezar
a experimentar con el ASM sin ms que el DEBUG del MS-DOS.
Salut!!! :-)
Jon

INTRODUCCION AL ASM: LA INSTRUCCION MOV (I)


===========================================
La operacin ms bsica que el uP es capaz de realizar es el movimiento de
datos (nmeros) de un lugar a otro. Puede moverlos de un registro a otro, de
un registro a la memoria o de la memoria a un registro.
La instruccin en lenguaje ensamblador que representa esta operacin se
denomina 'MOV', del ingls 'move', y puede traducirse en muchos cdigos de
operacin distintos en funcin del origen y el destino del dato. La instruccin
MOV tiene dos operandos, que van despus del 'mov' dejando uno o varios separadores entre el 'MOV' y el primer operando (diciendo separadores nos referiremos siempre a los espacios - carcter 32d o 20h - y a los tabuladores - carcter 09) y separados por una coma y/o varios separadores. La sintxis es la
siguiente:
MOV destino,origen
En esta instruccin 'origen' significa el lugar de donde el uP recoge el
dato a mover y 'destino' el lugar donde lo escribe despus. Estos operandos
pueden ser de muchas formas, y en funcin de la forma de estos se habla del
'modo de direccionamiento', que es el modo en como se accede al destino o al
origen. Podramos dar una larga y tediosa lista de los nombres de todos los
modos de direccionamiento, pero lo que haremos ser ir viendo ejemplos fcilmente asimilables, de forma que luego quien quiera pueda aprenderse los nombres
de los distintos modos de direccionamiento.
Uno de los usos ms sencillos es el de mover un dato de un registro a otro.
Por ejemplo, para mover el dato del registro BX al registro AX escribiramos:
MOV AX,BX
El contenido anterior del registro AX se pierde, de manera que el valor del
BX 'pisa' lo que hubiera en el AX. Despus de ejecutarse esta instruccin, los
registros BX y AX contendrn el mismo valor, el contenido anterior del BX.
De la misma forma, podemos comprender las siguientes instrucciones:
MOV AX,DS
MOV ES,AX
MOV DX,AX
...
Hacemos aqu un pequeo inciso para explicar cmo se aaden los comentarios
a los listados en ASM: cuando el ensamblador encuentra un punto y coma (carcter ';'), ignora todos los caracteres hasta que encuentra el final de la lnea

actual. Por tanto, las instrucciones anteriores se pueden comentar:


MOV AX,DS ; Carga AX con el valor del DS
MOV ES,AX ; ES = AX
MOV DX,BX ; DX <- BX
Esta convencin para los comentarios la aceptan los ensambladores como el
MASM o el TASM, pero tambin lo acepta el DEBUG del MS-DOS (ms adelante
veremos cmo usarlo).
Retomemos los usos de la instruccin MOV. Otra de las posibilidades es
cargar en un registro directamente un valor. Por ejemplo, podemos inicializar
los registros AX, BX, CX y DX a 1,2,3 y 4 respectivamente con las siguientes
instrucciones:
MOV AX,1
MOV BX,2
MOV CX,3
MOV DX,4
Un punto que me gustara destacar respecto al uso 'MOV reg,valor' es que
los registros de segmento no se pueden cargar directamente. Es decir, no
existe cdigo de operacin para la instruccin
MOV DS,0040h
Sino que hay que hacerlo necesariamente por medio de otro registro:
MOV AX,0040h
MOV DS,AX
Otro aspecto que querra comentar respecto a la instruccin 'MOV': el
cdigo de operacin de las instrucciones de transferencia entre registros incorpora algunos campos que especifican de qu registro a qu registro se mueve
el dato. Pero en estas ltimas instrucciones, el dato que se quiere cargar en
el registro va con el propio cdigo de operacin. Es decir, el 8086 espera
encontrar el valor a guardar inmediatamente despus de lo que constituye el
verdadero 'opcode' (o cdigo de operacin), que es el que identifica a la instruccin y no depende del valor a guardar. Por eso, las primeras instrucciones
(MOV AX,BX; etc...) slo ocupan dos bytes (ambos forman el cdigo de operacin
de la instruccin), mientras que las otras (MOV AX,1; etc...) ocupan dos o tres
bytes. A qu se debe esta diferencia del tamao? Se debe a una caracterstica
del 8086 de la que todava no hemos hablado:
Cuando se dise el 8086, se penso (hicieron bien) que en algunos casos
interesara acceder por separado al byte alto y byte bajo que componen los

registros, de manera que podamos operar con valores del tamao de un byte. Por
tanto, se incluyeron instrucciones que en lugar de operar sobre los registros
de 16 bits completos operaban sobre los bytes que los componen. Esta subdivisin slo se hizo para los cuatro registros de propsito general, y en ASM se
identifican con los siguientes nombres:
AX
BX
CX
DX

->
->
->
->

AH (byte alto) y AL (byte bajo)


BH ( " " ) y BL ( " " )
CH ( " " ) y CL ( " " )
DH ( " " ) y DL ( " " )

As, las siguientes instrucciones tambin tienen su correspondiente


cdigo de operacin. Espero que su funcin resulte obvia:
MOV AL,BL
MOV CH,CL
MOV DL,5
MOV BH,2
Es necesario dejar claro que los registros AL y AH, por ejemplo, son parte
del propio AX y por tanto no son independientes de ste. Por tanto, tras
ejecutar las siguientes instrucciones:
MOV AX,0FFFFh
MOV AL,00
MOV AH,00
El registro AX valdr 0000, ya que tanto su byte alto como su byte bajo se
han puesto a cero.
El tamao de la instruccin 'MOV reg,valor', volviendo a donde lo dejamos,
depende del tamao del registro que se modifique. Si se modifica un registro de
16 bits, el dato que viene a continuacin debe ser de ste tamao, por lo que
adems del byte con el opcode necesitamos otros dos con el valor, por lo que el
total es de tres bytes. En cambio, en una instruccin como 'MOV AL,00' slo es
necesario un byte con el valor despus del opcode, por lo que el tamao total
de la instruccin es de dos bytes.
Otro de los posibles usos de la instruccin MOV es el de cargar un registro
con el contenido de una posicin de memoria. El operando destino indicar el
registro en el que se debe almacenar el valo, mientras que el operando origen
ser la direccin de la que se debe cargar el dato. Para indicar que lo que se
quiere cargar es el valor contenido en la posicin de memoria dada, se encierra
la direccin entre corchetes:
MOV AL,[0000]

; Carga el valor de la posicin cero en AL,

; a diferencia de la siguiente instruccin,


MOV AH,00 ; que carga el VALOR 0 en AH
Ya que con los 16 bits que damos como direccin de memoria (no se pueden
dar los 20 bits completos) no se puede generar la direccin completa, se usa
el registro DS para formarla como vimos en el captulo anterior. Ms adelante
veremos cmo se puede especificar que se use otro registro.
Esto se denomina 'direccionamiento indirecto', en el que el operando es una
posicin de memoria de la que cargar el dato en lugar del valor a cargar. A
proposito, el dar directamente el valor se denomina 'direccionamiento
inmediato'.
Si el registro indicado es de 16 bits, se carga el byte bajo de la direccin
dada y el byte alto de la siguiente (ordenamiento Intel, recordis?).
MOV AX,[0000]
; Carga el valor de la posicin 0 en AL
; y el de la posicin 1 en AH
Al igual que se puede cargar un registro con el contenido de una posicin
de memoria, se puede almacenar el contenido de un registro en una posicin de
memoria. La instruccin, como habris adivinado, queda as:
MOV [0000],AX
MOV [0505h],AL

; Almacena AL en la pos. 0 y AH en la pos. 1


; Almacena AL en la pos 0505h

Aunque as podemos acceder a posiciones de memoria determinadas, a menudo


interesa acceder a la posicin de memoria indicada por un registro. En el caso
del 8086, es posible acceder a la posicin de memoria apuntada por BX, tanto
para lectura como para escritura, de esta forma:
MOV AL,[BX]; Carga en AL el valor almacenado en la
; posicin de memoria dada por BX
MOV [BX],DX
; Almacena DL en la posicin dada por BX,
; y DH en la posicin siguiente
Para darnos ms posibilidades, los cdigos de operacin del 8086 incluyen
un acceso a memoria a una posicin nn bytes ms adelante de la dada por BX,
todo en una sola instruccin:
MOV AX,[BX+15d] ; Carga los dos bytes de la posicin apuntada
; por BX ms 15 y de la siguiente
MOV [BX-12d],DL ; Almacena DL en la direccin dada por BX
; menos 12
Este valor que se suma a BX antes de generar el acceso a memoria viene, de

la misma manera que el valor en el direccionamiento inmediato, inmediatamente


despus del cdigo de operacin, y forma parte de la instruccin. Si el valor
del desplazamiento est entre -128d y +127d, es decir, 'cabe' en un byte
(ya que el desplazamiento se interpreta como entero con signo), se genera una
instruccin un byte ms corta que si el valor necesita una palabra entera. De
todas formas, el desplazamiento (el valor que se suma a BX) nunca puede salirse
de una palabra.
Hemos visto que el acceso a memoria por medio de un registro lo hacemos
siempre con el BX. De hecho, no existen cdigos de operacin para acceder con
el AX, el CX y el DX. Por tanto las siguientes instrucciones no las acceptar
el ensamblador:
MOV BX,[AX]
MOV CL,[DX]

; MAL
; MAL

Y ahora, viendo esta limitacin de registros para apuntar a memoria, llega


el momento de introducir otros dos registros: el SI ('Source Index') y el DI
('Destination Index'). Estos registros admiten todos los modos de direccionamiento que hemos visto, y adems algunos que todava no conocemos. La nica
diferencia es que no se puede acceder por separado a los bytes alto y bajo, por
lo que siempre usaremos los registros 'enteros'.
Por ejemplo, podemos hacer cosas como:
MOV AX,0040h
MOV DS,AX ; Segmento de datos a partir de 00400h absoluta
MOV SI,2
MOV AX,[SI] ; Carga el byte almacenado en 00402h absoluta
; en AL, y el de 00403h en AH
Bueno, en el siguiente captulo seguiremos con ms usos de la instruccin
MOV (ms modos de direccionamiento) y veremos cmo experimentar con el DEBUG.
Pensaba explicar todo esto en un slo captulo, pero se me alarga demasi.
Salut!!! :-)
Jon

INTRODUCCION AL ASM: LA INSTRUCCION MOV (II)


============================================
Continuamos con los modos de direccionamiento que admite el 8086. Para
poder dar una tabla completa con todos los modos de direccionamiento que
faltan, introduzco dos registros que todava no habamos visto: el primero es
el registro 'SS' (de 'Stack Segment', 'segmento de pila'), que es otro registro
que se usa como el CS, el DS y el ES para construir direcciones completas. La
instruccin 'MOV' se puede usar con este registro de la misma manera que con
los otros tres registros de segmento. El otro registro es el 'BP' (de 'Base
Pointer', 'puntero base'). Este registro se puede utilizar de la misma forma
que los registros de propsito general, aunque no se puede acceder por separado
a los bytes alto y bajo. Adems veremos como presenta algunos modos de direccionamiento peculiares.
Para no alargarme demasiado, presento una tabla con una instruccin de cada
modo de direccionamiento. Ya que el valor que indica la posicin de memoria es
siempre un valor de 16 bits, se complementa con un registro de segmento para
formar la direccin absoluta. En la tabla aparece tambin el segmento que el uP
toma por defecto para completar la direccin. Ms adelante veremos cmo podemos
hacer que una instruccin use el registro de segmento que nos interese en lugar
del registro por defecto.
INSTRUCCION
REGISTRO POR DEFECTO
-------------------- -------------------------MOV AX,[BX+SI+xx]
DS
MOV AX,[BX+DI+xx]
DS
MOV AX,[BP+SI+xx]
SS <- !!
MOV AX,[BP+DI+xx]
SS <- !!
MOV AX,[SI+xx]
DS
MOV AX,[DI+xx]
DS
MOV AX,[BP+xx]
DS
MOV AX,[BX+xx]
DS
Estos modos de acceso a memoria se aaden a los ya vistos en el captulo
anterior, que aparecen en la siguiente tabla:
INSTRUCCION
DESCRIPCION
--------------------------------------------------------------------MOV AX,BX
Transferencia de un registro a otro
MOV AX,55AAh
Carga de un registro directamente con un valor
MOV AX,[55AAh]
Transferencia de una direccin de memoria dada
directamente a un registro
MOV AX,[BX]
Acceso a memoria por medio del BX
MOV AX,[SI]
Acceso a memoria por medio del registro ndice SI
MOV AX,[DI]
Acceso a memoria por medio del registro ndice DI

MOV AX,[BP]

Acceso a memoria por medio del registro BP

Estas instrucciones, que ya vimos, generan accesos a memoria completando la


direccin, por defecto, con el DS.
Tambin son posibles las instrucciones generadas invirtiendo los operandos
de las instrucciones de ambas tablas. Es decir, todos estos modos de direccionamiento pueden usarse tanto para lectura (en las tablas) como para escritura.
Y para acabar con los modos de direccionamiento, tenemos uno bastante peculiar que describimos dando un ejemplo:
MOV [55AAh], 3322h
Esta instruccin almacena en la direccin 55AAh el valor 3322h, sin acceder
en toda la operacin a ningn registro.
Pero las instruccines que almacenan directamente un valor en memoria presentan un problema para el ensamblador: cuando uno de los dos operandos (el
destino o el origen) es un registro, con se operando se fija el tamao del
dato a mover. Pero en este caso, es necesario indicar al ensamblador el tamao
del dato a mover, de forma que pueda generar el cdigo de operacin correspondiente. Esto se puede hacer aadiendo a cualquiera de los operandos unas palabras que indican si se refiere a un operando de tipo byte o de tipo palabra.
En los siguientes ejemplos se ve cmo se hace esto:
MOV WORD PTR [55AAh],3322h ; almacena los dos bytes
MOV BYTE PTR [55AAh],20h
; almacena un solo byte
MOV WORD PTR [BX],0
; almacena dos bytes
Estos 'typecasts', que es como se denominan formalmente, pueden aadirse en
todos los casos, pero no son imprescindibles. A modo de ejemplo, las instrucciones siguientes son aceptadas por el ensamblador:
MOV AX,WORD PTR [BX]
MOV BYTE PTR [BP+4],CL
Ahora que ya conocemos algunas de las instrucciones que podemos emplear en
ensamblador, veremos cmo podemos experimentar con ellas y ver cmo se modifican los registros. Para ello usaremos el DEBUG del MS-DOS, pero fcilmente podemos hacer lo mismo con el SID del DR-DOS o con el SYMDEB, que es un DEBUG
bastante mejorado, tambin de Microsoft.
Lo primero es cargar el DEBUG sin ningn parmetro desde la lnea de comandos del DOS. Entonces aparece un mensaje de copyright en la pantalla, tras lo
que tendremos un guin ('-') que es prompt con el que DEBUG nos invita a que

introduzcamos un comando. Si escribimos 'R' y pulsamos RETURN, el programa


nos muestra el contenidos actual de los registros del 8086 y la instruccin
apuntada por el par CS:IP, que es la siguiente instruccin que debe ejecutar
el uP. El comando 'T' ejecuta la siguiente instruccin y vuelve a mostrar los
registros. El registro IP habr variado, de manera que apuntar a la siguiente
instruccin.
Uno de los comandos que ms usaremos para experimentar con el DEBUG ser
el comando 'A', que nos permite introducir cdigo fuente en ASM (instrucciones
en lenguaje ensamblador) y lo ensamblar en la direccin apuntada por CS:IP.
Cuando queramos volver al prompt del DEBUG, pulsaremos RETURN al principio de
una lnea. Para desensamblar el cdigo a partir de la direccin actual usaremos el comando 'U' de 'unassemble' ('desensamblar'). Tanto el comando 'A' como
el comando 'U' aceptan opcionalmente una direccin de memoria (en hexadecimal,
ya que a DEBUG hay que darle todos los nmeros en hex.), que puede ser una direccin compuesta por segmento y offset (con ':' en medio) o slo por un
'offset' (y se asume el CS actual).
Por ejemplo, podemos probar las siguientes indicaciones:
-Cargar DEBUG, escribir 'A' y pulsar RETURN.
-Aparecer la direccin de memoria actual (xxxx:100, donde xxxx puede
variar), seguida de un cursor. Podemos escribir instrucciones ASM.
Escribimos 'MOV AX,2233' y pulsamos RETURN.
-Escribimos 'MOV AH,0' y pulsamos RETURN.
-Pulsamos RETURN, con lo que volveremos al prompt de DEBUG.
-Escribimos 'T' y pulsamos RETURN, con lo que se ejecuta la primera
instruccin e IP avanza a 102h. Podemos observar que AX ahora vale
2233, debido al 'MOV AX,2233' que acabamos de ejecutar.
-Escribimos de nuevo 'T' y pulsamos RETURN, con lo que se ejecuta la
siguiente instruccin. Podemos observar que el contenido de AX es
ahora 0033, ya que hemos almacenado el valor 00 en AH, que es el byte
alto de AX.
Os recomiendo que experimentis con los distintos modos de direccionamiento.
Con el comando 'D' podis ver el contenido de una zona de memoria, dando como
parmetro la direccin a partir de la que mostrar el contenido. Esta direccin
puede ser de la forma SEG:OFF, o puede ser un offset, tomndose en tal caso el
valor de DS para completar la direccin.
Ahora introduciremos las instrucciones aritmticas del 8086, al menos las
bsicas, de forma que podris experimentar con ellas en el DEBUG. Se presenta
una tabla con la descripcin de cada una, ya que su comprensin no ofrece gran
dificultad. Despus de la tabla se comentan con ms detalle las instrucciones:
INSTRUCCIONES ARITMETICAS BASICAS

----------------------------------Instruccin
Descripcin
.................... ....................................................
ADD destino,origen Suma los operandos origen y destino, y almacena el
resultado en destino.
SUB destino,origen Resta los operandos origen y destino, y almacena el
resultado en destino.
INC destino
Suma 1 (incrementa) al operando destino.
DEC destino
Resta 1 (decrementa) al operando destino.
NEG destino
Asigna al operando destino el complemento a dos del
contenido anterior del operando destino. Esto representa cambiar el signo de un nmero binario.
Las instrucciones ADD y SUB aceptan exactamente los mismos modos de direccionamiento que la instruccin MOV, por lo que todos los ejemplo dados antes
sirven para estas instrucciones sin ms que cambiar el MOV por el ADD o el SUB.
Exceptuando que slo se pueden aplicar estas operaciones a los registros AX,
BX, CX, DX, SI, DI, BP y a los registros de un byte (AL, AH,...), pero no a los
registros de segmento.
Las instrucciones INC, DEC y NEG aceptan como operando un registro (que no
sea de segmento) o una referencia a memoria, usando cualquiera de los modos de
direccionamiento descritos para la instruccin MOV.
En el siguiente captulo introduciremos la pila de la mquina, y puede que
las interrupciones.
Despus, con todos estos conceptos comenzaremos a describir el uso de los
ensambladores como el MASM o el TASM, veremos el famoso 'Hello, world' en ASM
y seguiremos con el juego de instrucciones del 8086: saltos condicionales (y
veremos los flags), comparaciones, etc... En muy poco tiempo estaris todos
programando en ASM. Animad a la gente a que se apunte!!!
Salut! :-)
Jon

INTRODUCCION AL ASM: LA PILA


============================
Como comentbamos al final del cuarto captulo, en este quinto captulo
vamos a estudiar uno de los elementos de mayor utilidad del ASM: la pila.
Al estudiarla veremos dos nuevos registros, el SS y el SP, y dos nuevas
instrucciones, PUSH y POP.
Primero explicar lo que se entiende por una pila en general y despus veremos la pila del 8086 en concreto. Por eso, si alguien sabe lo que es una pila,
puede saltarse algunas lneas.
Un ordenador siempre guarda los datos en la memoria, exceptuando los registros de la CPU. Estos datos pueden estar dispuestos de diferentes maneras, y se
puede acceder a ellos de diversas formas. Una particular organizacin y modo de
acceso a los datos se suele denominar 'estructura de datos', de las que existen
muchas y muy diferentes, cada una adecuada para un propsito. Son estructuras
de datos los arrays, las listas encadenadas, los rboles binarios, etc...
Las estructuras que nos interesan ahora aqu son las estructuras lineales,
en que cada elemento (cada dato) va despus del anterior. Es decir, cada elemento tiene un predecesor y un sucesor, excepto el primero y el ltimo. Son
estructuras de este tipo los arrays y las listas encadenadas, mientras que no
lo son los rboles binarios (no os preocupis los que no sepis que son estas
cosillas). Las estructuras lineales se pueden clasificar principalmente en dos
grupos: las llamadas FIFO (First In First Out, el primero en entrar es el primero en salir) y las llamadas LIFO (Last In First Out, el ltimo en entrar es
el primero en salir). Aqu van dos ejemplos de cada una para que quede claro:
Una estructura FIFO es similar a la cola del autobs: los elementos se ordenan segn llegan (los pasajeros se ponen a la cola cada uno detrs del anterior
en llegar) y se sacan empezando por el primero que lleg y acabando por el ltimo (el primero en llegar a la parada es el primero en montar). Esta estructura se suele denominar 'cola' o 'queue' ('cola' en ingls).
En cambio, una estructura LIFO es similar al montn de papeles que suelen
estar pinchados todos juntos en una tintorera: los elementos se van ordenando
segn llegan (cada papel se pincha sobre el anterior), pero al sacarse del
montn se sacan empezando por el ltimo que lleg (se empiezan a sacar por el
ltimo que se pinch, justo en orden inverso al que se pincharon). Esta estructura se suele denominar 'pila' o 'stack' ('pila' en ingls).
Estas estructuras se pueden implementar de muchas maneras diferentes por un
programa (todava no estamos viendo la pila del 8086 en concreto, sta viene ya
implementada por el hard del uP). Una manera sencilla de hacerlo es un array
del tipo de dato que queremos cada elemento: si queremos una cola de enteros

ser un array de enteros, si queremos una pila de nmeros reales ser un array
de nmeros en coma flotante, etc... Necesitaremos tambin una variable entera
que llevar la cuenta de los elementos insertados hasta el momento, que llamaremos 'cuenta'. Veamos cmo se implementara una pila con este esquema:
inicialmente, la variable 'cuenta' valdra -1 (suponemos que los ndices de los
arrays van como en C, comenzando por el cero). Para insertar un elemento, se
incrementara 'cuenta' y se guardara el elemento en la posicin nmero 'cuenta' del array. De esta manera, siempre tendramos accesible el ltimo valor
empujado en 'pila[cuenta]'. Una jugada inteligente sera meter en este punto
una comprobacin de que 'cuenta' no ha sobrepasado el lmite superior de la
pila. En caso de que as fuera, emitiramos un mensaje de error. Este error se
suele denominar 'stack overflow' o 'desbordamiento de la pila', y aunque en la
pila del 8086 no se comprueba internamente, los compiladores de lenguajes de
alto nivel suelen incluir cdigo para comprobarlo. A esta operacin de introducir un valor en la pila se le suele llamar 'empujar' un valor en la pila (en
ingls 'push').
La otra operacin que nos queda ver es la operacin contraria, la de sacar
un valor de la pila. Ya que queremos acceder a la estructura en la forma LIFO,
cada vez que se extrae un valor debe ser del extremo superior de la pila. Por
ejemplo, si 'cuenta' vale 4 y queremos extraer un valor, ste deber tomarse de
la posicin 4 del array, que ser el ltimo elemento empujado. Adems, hay que
decrementar la variable 'cuenta' para apuntar al nuevo 'tope' de la pila. Es
importante tener en cuenta que no hace falta sobreescribir la posicin 4 del
array, esto ocurrir la prxima vez que se empuje un valor, sino que basta con
decrementar la variable 'cuenta'. A esta otra operacin se le llama 'pop' en
ingls, en espaol se suele decir simplemente 'sacar' o 'extraer' un valor de
la pila.
La pila del 8086 viene implementada por el hard del uP, pero los datos residen en memoria. El uP tiene dos registros para manejar la pila: el SS ('Stack
Segment', 'segmento de pila'), que es el cuarto registro de segmento que junto
con CS, DS y ES se utilizan para formar direcciones de 20 bits, y el SP ('Stack
Pointer', 'puntero de pila'), que es un registro de 16 bits que cumple la
funcin de nuestra variable 'cuenta' y que se une al SP para formar la direccin de memoria completa. Cada programa suele destinar una zona de la memoria
para la pila, y es esta zona la que alberga los valores introducidos en sta.
Todos los elementos de la pila son valores enteros de 16 bits, por lo que se
utiliza una palabra de la memoria para cada valor.
Un aspecto que en un principio puede llamar la atencin es que la pila del
8086, al igual que la de todos los micros que conozco, crece hacia abajo en
lugar de hacia arriba. El funcionamiento de sta es, por lo dems, idntico al
de nuestra pila imaginaria: al principio 'cuenta' apuntara al ltimo elemento
del array ms uno, al empujar un valor se se decrementara cuenta y metera en
'pila[cuenta]' , y al sacarlo se incrementara cuenta y se devolvera

'pila[cuenta]'. Ms adelante se vern las ventajas de que la pila crezca hacia


abajo.
Los registros SS y SP forman la direccin absoluta donde reside el ltimo
valor empujado. Al empujar un valor, se resta dos a SP (porque cada elemento
son dos bytes, es decir, dos posiciones de memoria) y el nuevo valor se guarda
en la dir SS:SP (y el byte de mayor peso en SS:SP+1). Al sacar un valor, se
toma de las posiciones SS:SP y SS:SP+1 y se suma dos a SP.
Las instrucciones PUSH y POP nos permiten empujar un valor de 16 bits y
recoger el ltimo valor empujado. El uP se encarga de actualizar SP.
Vamos a ver un ejemplo un poco 'incivilizado' de cmo pondramos a punto la
pila en una direccin determinada. Digo 'incivilizado' porque lo hacemos en una
direccin arbitraria, donde en un PC podra haber un driver o cualquier cosa.
De esto normalmente se encarga la rutina del DOS que carga un programa y lo
ejecuta, por lo que slo es un ejemplo para comprender el funcionamiento.
Como sagazmente habris deducido (y por si acaso no, os lo cuento), ya que
la gestin de la pila se hace modificando slo el SP al hacer PUSH y POP, la
pila no puede exceder de 64K. Supongamos que queremos poner una pila de 64K en
el segmento que comienza en la direccin absoluta 80000h (8000h:0). La ltima
palabra de este segmento est en 8FFFEh (el byte de menor peso en sta y el de
mayor en la 8FFFFh). Por tanto, habra que cargar los registros como sigue:
MOV AX,8000h
MOV SS,AX
MOV SP,0FFFEh
Ms adelante veremos que esto no se puede hacer as, sino que hay que tener
en cuenta las interrupciones, etc.. Por lo que no lo probis. Pero para hacerse
una idea, est bien. En realidad, no es necesario reservar 64K para la pila,
por lo que SP no tiene por qu inicializarse a 0FFFEh. Por ejemplo, si queremos
reservar 32K para la pila inicializaremos SP a 7FFEh.
El funcionamiento exacto de la pila es el siguiente: cuando se ejecuta una
instruccin PUSH se decrementa SP en dos unidades y se guarda en la direccin
SS:SP el valor a empujar. De esta forma, SS:SP siempre apunta al ltimo valor
empujado. Cuando se ejecuta un POP, se recoge el valor de la direccin SS:SP y
se incrementa SP en dos unidades.
Ambas instrucciones llevan un solo operando, que especifica de donde se toma
el valor (en el caso de PUSH) o donde se almacena (en el caso de POP). Los
operandos pueden ser uno cualquiera de los siguientes:
- Un registro de 16 bits de los siguientes: AX, BX, CX, DX, SI, DI, BP o SP.

- Un registro de segmento (no se permite POPear CS, porque implicara un


salto del programa a otra direccin, y para esto ya hay otras instrucciones).
- Una referencia a memoria (con cualquiera de los modos de direccionamiento
que vimos con la instruccin MOV).
- Un valor inmediato (por ejemplo, PUSH 55AAh) (slo para PUSH y en 386 o
superiores).
Veamos un pequeo listado en ASM y los contenidos de la pila y los registros
con cada instruccin. El listado es el siguiente:
MOV AX,8000h
MOV SS,AX
MOV SP,0FFFEh
MOV AX,55AAh
PUSH AX
MOV BX,AX
ADD BX,1111h
PUSH BX
POP AX
POP BX

; inicializa la pila
; empuja el valor 55AAh
; suma a BX el valor 1111h

Estudiemos lo que ocurre a cada paso:


*
*
*

MOV AX,8000h
MOV SS,AX
MOV SP,0FFFEh

; inicializa la pila

Estas instrucciones inicializan SS y SP para situar la pila al final del


segmento que comienza en la direccin absoluta 80000h. En este caso, no nos
interesan los contenidos anteriores de esa zona de memoria. Representaremos el
contenido de la pila como sigue:
Direccin de memoria
Valor
Registros del uP
-------------------------------------- ------------ -----------------Segmento Offset Direccin absoluta
Valor
SS = 8000h
-------- ------ ---------------------SP = 0FFFEh
8000h 0FFFFh
8FFFFh .............. ??
8000h 0FFFEh
8FFFEh .............. ??
8000h 0FFFDh
8FFFDh .............. ??
8000h 0FFFCh
8FFFCh .............. ??
8000h 0FFFBh
8FFFBh .............. ??
8000h 0FFFAh
8FFFAh .............. ??
*

MOV AX,55AAh

Despus de esta instruccin, el registro AX contendr 55AAh.


*

PUSH AX

; empuja el valor 55AAh

Nada ms ejecutarla, as quedarn la pila y los registros del micro:


Direccin de memoria
Valor
Registros del uP
-------------------------------------- ------------ -----------------Segmento Offset Direccin absoluta
Valor
SS = 8000h
-------- ------ ---------------------SP = 0FFFCh
8000h 0FFFFh
8FFFFh .............. ??
AX = 55AAh
8000h 0FFFEh
8FFFEh .............. ??
8000h 0FFFDh
8FFFDh .............. 55h
8000h 0FFFCh
8FFFCh .............. AAh
8000h 0FFFBh
8FFFBh .............. ??
8000h 0FFFAh
8FFFAh .............. ??
*
*
*

MOV BX,AX
ADD BX,1111h
PUSH BX

; suma a BX el valor 1111h

Ya que 55AAh + 1111h = 66BBh, as quedarn las cosas:


Direccin de memoria
Valor
Registros del uP
-------------------------------------- ------------ -----------------Segmento Offset Direccin absoluta
Valor
SS = 8000h
-------- ------ ---------------------SP = 0FFFAh
8000h 0FFFFh
8FFFFh .............. ??
AX = 55AAh
8000h 0FFFEh
8FFFEh .............. ??
BX = 66BBh
8000h 0FFFDh
8FFFDh .............. 55h
8000h 0FFFCh
8FFFCh .............. AAh
8000h 0FFFBh
8FFFBh .............. 66h
8000h 0FFFAh
8FFFAh .............. BBh
*

POP AX
Se tomar el valor de SS:SP, se guardar en AX, y se sumar dos a SP:
Direccin de memoria
Valor
Registros del uP
-------------------------------------- ------------ -----------------Segmento Offset Direccin absoluta
Valor
SS = 8000h
-------- ------ ---------------------SP = 0FFFCh
8000h 0FFFFh
8FFFFh .............. ??
AX = 66BBh
8000h 0FFFEh
8FFFEh .............. ??
BX = 66BBh
8000h 0FFFDh
8FFFDh .............. 55h
8000h 0FFFCh
8FFFCh .............. AAh

8000h
8000h
*

0FFFBh
0FFFAh

8FFFBh .............. 66h


8FFFAh .............. BBh

POP BX
Anlogo a la instruccin anterior, pero con BX:
Direccin de memoria
Valor
Registros del uP
-------------------------------------- ------------ -----------------Segmento Offset Direccin absoluta
Valor
SS = 8000h
-------- ------ ---------------------SP = 0FFFEh
8000h 0FFFFh
8FFFFh .............. ??
AX = 66BBh
8000h 0FFFEh
8FFFEh .............. ??
BX = 55AAh
8000h 0FFFDh
8FFFDh .............. 55h
8000h 0FFFCh
8FFFCh .............. AAh
8000h 0FFFBh
8FFFBh .............. 66h
8000h 0FFFAh
8FFFAh .............. BBh

Os habris dado cuenta de que podramos haber inicializado SP a 0 para aprovechar tambin los dos ltimos bytes del segmento. En realidad, esto es algo
de lo que no es necesario preocuparse prcticamente nunca, pues el DOS se encarga de inicializar la pila generalmente.
Uno de los usos ms habituales de la pila es el de preservar el contenido de
uo o varios registros que van a ser utilizados en un fragmento de cdigo. Suele
ser algo as:
PUSH AX
; Guardamos AX, BX y CX
PUSH BX
PUSH CX
; [...] Cdigo que modifica AX, BX y CX
POP CX
; Recuperamos AX, BX y CX
POP BX
POP CX
Ahora ya estamos preparados para introducir un importante aspecto de la
programacin en ASM: las interrupciones. Conociendo las interrupciones, ya tendremos el bagaje necesario para acometer la escritura de programas usando
ensambladores comerciales como el TASM o el MASM, comenzando con el ya tradicional 'Hello, world'.
Salut :-)
Jon

INTRODUCCION AL ASM: LAS INTERRUPCIONES


=======================================
Como comentbamos al final del captulo dedicado a la pila, en este captulo
vamos a abordar el tema de las interrupciones. Comencemos pues.
Al principio del curso, vimos el funcionamiento del uP, que contnuamente
recoge instrucciones de la memoria (de la direccin apuntada por CS:IP) y las
ejecuta. Aunque ste es el funcionamiento habitual, hay momentos en los que
esta actividad se tiene que interrumpir. Para ver la razn de la existencia de
las interrupciones, veamos un ejemplo.
Supongamos que el funcionamiento del teclado fuese el siguiente: el programa
enviara una seal al teclado, a la que el teclado respondera con un cdigo
indicativo de la tecla. Existira tambin un cdigo para indicar 'ninguna
tecla pulsada'. Dentro de este esquema de funcionamiento, imaginemos que un
programa est en un momento dado llevando a cabo una actividad de clculo, sin
comprobar para nada lo que ocurre con el teclado. Es perfectamente posible que
en ese momento el usuario del ordenador pulse una tecla y la suelte antes de
que el programa compruebe el teclado; si el funcionamiento del teclado fuese el
que hemos visto, esa pulsacin de tecla se perdera. Existen varias posibles
soluciones a este problema: una sera que el programa comprobase constantemente
el teclado, con la consiguiente molestia al desarrollar el programa y la prdida de velocidad. Otra sera aadir hardware al teclado para llevar un buffer
con las teclas pulsadas. Esto ocurre as en los PCs, pero este buffer slo
tiene capacidad para unas cuatro pulsaciones, con lo que el problema sigue presentndose. La solucin ms adecuada, ya que se puede extender a otros problemas parecidos, son las interrupciones hardware, que adems es la solucin
implementada en los PCs.
Cuando algn perifrico necesita que el uP se encargue de algo con urgencia
(por ejemplo, leer el valor de una tecla) pone a nivel activo uno de los pines
del uP, denominado INTR. En este momento, el uP para lo que est haciendo (si
est en medio de la ejecucin de una instruccin, se completa sta primero) y
lee el valor de 8 bits (0 a 255d) de las 8 lneas bajas del bus de datos. El
dispositivo que provoca la interrupcin se debe haber encargado de poner en
stas lneas un cdigo que identifica el nmero de interrupcin. Este nmero es
un cdigo que identifica el dispositivo que ha provocado la interrupcin; por
ejemplo, el teclado tiene el nmero 9 como identificativo y por tanto pone en
las 8 lneas bajas del bus de datos el valor 00001001b cuando se pulsa una tecla. Ahora, lo que ocurre es lo siguiente: el uP no est especialmente diseado
para manejar algunos dispositivos en concreto, por lo que cuando recibe una interrupcin lo que hace es llamar a una rutina que el programador ha dejado residente en la memoria, que es la que lee la tecla pulsada y la almacena en un
buffer en memoria en el caso de la interrupcin 9 (para otros nmeros de interrupcin, se lleva a cabo otra tarea). Esta rutina se suele denominar 'rutina
de servicio a la interrupcin', por razones bastante claras (eso espero). La

ltima instruccin de la rutina de servicio a la interrupcin es la instruccin


IRET, que hace que el uP vuelva a la direccin donde estaba antes de la interrupcin. An nos queda por ver cmo el uP, a partir del nmero recibido por el
bus de datos con el nmero de interrupcin, identifica a qu rutina ha de
saltar.
Los diseadores del 8086 reservaron los primeros 4*256d bytes de la memoria
(1 kilobyte desde la direccin absoluta 00000h hasta la 003FFh, ambas includas) para una tabla denominada 'tabla de vectores de interrupcin'. Esta tabla
contiene cuatro bytes para cada nmero de interrupcin (por eso son 4*256d
bytes, 4 bytes/n de interrupcion * 256d nmeros de interrupcin posibles), que
especifican la direccin de memoria donde comienza la rutina de servicio a cada
interrupcin. Al recibir una demanda de interrupcin, el micro toma el valor
del bus de datos (el n de interrupcin) y lo multiplica por cuatro. Usa el valor obtenido como direccin de la que leer 4 bytes con la direccin a la que
saltar. Estos cuatro bytes son el offset y el segmento que forman la direccin
absoluta. El ordenamiento Intel especifica que se almacenan primero los bytes
de menor peso, y en el caso del 8086 se almacena primero el offset que el valor
de segmento. Por ejemplo, si instalsemos una rutina de servicio a la interrupcin CCh en la direccin de memoria 1122h:3344h, tendramos que introducir los
siguientes valores en la tabla de vectores de interrupcin:
DIRECCION ABSOLUTA
VALOR
---------------------4 * CCh = 816d
44h
4 * CCh + 1 = 817d
33h
4 * CCh + 2 = 818d
22h
4 * CCh + 3 = 819d
11h
Este es el modelo de funcionamiento, pero todava no hemos vistos varios
detalles muy importantes. En realidad, la arquitectura del PC hace que los
perifricos no interrumpan directamente al uP (qu ocurrira si dos perifricos deciden generar una seal de interrupcin a la vez?), sino que stas estn
jerarquizadas mediante unos chips denominados 8259 o PIC ('Programmable
interrupt controller', Controlador de Interrupciones Programable), interactuando de una manera bastante compleja. De momento, esto no nos interesa demasiado.
Cuando el micro recibe la seal de interrupcin, el CS y el IP contienen la
direccin de la siguiente instruccin a ejecutar (ya que el IP se incrementa
nada ms cargar la instruccin, antes incluso de ejecutarla). Estos se cargan
con los valores ledos de la tabla de vectores de interrupcin, por lo que es
necesario preservar su valor actual para luego volver al punto donde se estaba
cuando se produjo la interrupcin. Lo que se hace es empujar estos valores en
la pila antes de saltar a la rutina de servicio a la interrupcin. Adems,
antes de empujar el CS y el IP (en este orden), se empuja el contenido de un
registro que todava no hemos visto (lo veremos dentro de poco), llamado

'registro de flags'. Los bits de este registro tienen cada uno un significado
especial, indicando en todo momento el estado actual del uP y, en el caso de
algunos bits, el resultado de la ltima operacin ejecutada (por ejemplo, el
flag ZERO indica si la ltima operacin dio un resultado de 0). Ya que algunos
flags se utilizan para indicar si se esta ejecutando una rutina de servicio a
la interrupcin (y por tanto se modifican antes de saltar a la rutina), este
registro se salva a fin de que al volver al hilo normal de ejecucin del programa se restaure completamente el estado anterior de micro.
Ya que una interrupcin se puede producir en cualquier momento, sta debe
ser transparente al programa en ejecucin. Cuando se escribe un programa, no
se piensa en que entre una instruccin y la siguiente pueden ejecutarse 200
instrucciones que leen un valor de teclado y lo almacenan en un buffer en
memoria. Por tanto, al volver de una interrupcin se tiene que restaurar por
completo el estado del uP, de forma que el programa interrumpido no se vea
afectado. Por ejemplo, supongamos un fragmento de cdigo as:
MOV AX,[200h]
ADD AX,30h
Si en medio de las dos instrucciones se ejecuta una rutina de servicio a la
interrupcin que modifica el contenido de AX, el resultado para el programa
puede ser catastrfico. El uP se encarga de que el registro de flags sea restaurado, ya que siempre es modificado. Pero es la propia rutina de servicio a
la interrupcin la que debe preservar el contenido de los registros que sta
modifique. Por tanto, las rutinas de servicio a la interrupcin suelen tener el
siguiente aspecto:
PUSH AX
PUSH BX
PUSH CX
PUSH DX
PUSH DS
PUSH ES
PUSH BP
PUSH SI
PUSH DI
[...]
POP
POP
POP
POP
POP
POP

;cdigo que modifica los anteriores 9 registros


DI
SI
BP
ES
DS
DX

POP CX
POP BX
POP AX
IRET

;retorna al programa interrumpido

Otro de los aspectos importantes de las interrupciones es el de la pila.


Hemos visto que el uP empuja el contenido de los flags, el de CS y el de IP en
la pila apuntada por SS:SP en el momento de la interrupcin. Aunque la rutina
de servicio a la interrupcin podra modificar SS y SP para usar su propia
pila, esto no es lo habitual; es decir, las rutinas de servicio a la interrupcin suelen utilizar la pila del programa interrumpido. Por tanto, aunque vimos
que un POP no borra el valor apuntado por SS:SP despus de leerlo, no se puede
suponer que despus de un POP los valores inmediatamente por debajo de SS:SP
continen igual, ya que pueden haber sido pisados debido a una interrupcin.
En realidad, cuando se pone a nivel activo la patilla INTR, el uP no siempre
interrumpe el programa en ejecucin, sino que primero consulta uno de los bits
del registro de flags, llamado IF ('Interruption Flag', Flag de Interrupcin)
y slo si este est a 1 se atiende la interrupcin. En caso de que IF est a 0,
simplemente se ignora la peticin de interrupcin. Es el 8259 (el PIC) el que
se encarga de volver a generar la interrupcin repetidas veces hasta que el uP
est listo. Cuando se salta a una rutina de servicio a la interrupcin, una de
las cosas que se hace es poner este bit a 1 de manera que no se pueda interrumpir la propia rutina de servicio a la interrupcin. Pero esta caracterstica la
puede aprovechar cualquier programa, ya que existen dos instrucciones que ponen
a 0 o a 1 el flag IF:
STI
CLI

Pone a 1 el bit IF, para que se atienda a las


peticiones de interrupcin.
Pone a 0 el bit IF, para desactivar las interrupciones.

Debido a que se puede controlar el uP para que atienda o no a las peticiones


de servicio a la interrupcin, este tipo de interrupciones se denominan
'Interrupciones enmascarables'; existe otra patilla denominada 'NMI' ('Non
Maskable Interrupt', Interrupcin No Enmascarable) de funcin parecida a INTR,
pero a cuya peticin el uP siempre responde. En realidad, no s con certeza
cmo funcionan las NMI, ya que no se usan habitualmente para la programacin.
En el Spectrum, se utilizaba esta interrupcin para los 'transfer', aparatos
que permitian en cualquier momento generar una NMI, por medio de un pulsador, y
que se grabara a cinta o disco los registros del uP y los contenidos de toda la
memoria (con cualquier programa que sta tuviera), para luego poder cargarlo y
continuar la ejecucin en el mismo punto; estos eran copiadores infalibles.
Todo esto que hemos visto hasta ahora es interesante para nosotros, pero en
realidad es al diseador de hardware y al programador de drivers de dispositivos a quien ms le concierne. Adems, las interrupciones hardware son an ms

complicadas, ya que hay que controlar el PIC, etc... En realidad, slo en algunos pocos casos nos interesar todo esto (el caso ms habitual es el de querer
instalar una rutina de servicio a la interrupcin de teclado para poder detectar la pulsacin simultnea de varias teclas, como en los juegos). Estas interrupciones que hemos visto se denominan interrupciones hardware, pero lo que
principalmente nos interesa son las interrupciones software.
Existe una instruccin en ASM llamada INT, que fuerza al uP a realizar el
mismo proceso que lleva a cabo para atender una interrupcin. El nmero de
interrupcin no se lee del bus de datos, sino que viene dado por el operando de
la instruccin INT. Por ejemplo, puede usarse la instruccin 'INT 9' para llamar a la rutina de servicio a la interrupcin de teclado. En realidad, ste no
es el uso habitual, ya que, por ejemplo, la rutina de servicio a la interrupcin 9 espera que el teclado tenga una pulsacin de tecla que enviar, por lo
que el funcionamiento de sta sera anmalo al invocar la INT 9 sin ninguna tecla esperando. En realidad, la utilidad de las interrupciones software es otra.
Una parte muy importante de un sistema operativo (quiz la ms importante)
es el 'kernel' o ncleo. Este kernel no es ms que un conjunto de rutinas o
funciones que ejecutan diversas tareas, como por ejemplo escribir una cadena en
el monitor o escribir datos en un fichero. Estas funciones pueden estar en distintas direcciones de memoria en distintos ordenadores, por lo que es necesaria
una manera de encontrarlas. En el caso del MS-DOS, la mayora de los servicios
del kernel estn integrados en una funcin que lleva a cabo distintas acciones
segn el contenido del registro AH a la entrada; a cada tarea distinta que se
puede ejecutar en funcin de AH se le denomina 'servicio'. En los dems registros se pasan parmetros, cuyo significado vara de un servicio a otro; en caso
de que sea necesario devolver algn valor, se devuelve en los registros. Por
ejemplo, el servicio 9 (AH = 9) imprime una cadena en la pantalla. La cadena se
recoge de la direccin dada por el par DS:DX, y el final de sta se indica con
el carcter '$'. La funcin que ofrece todos estos servicios se instala en
memoria cuando se carga el sistema operativo de los ficheros IO.SYS y
MSDOS.SYS. Pero cada vez que se carga puede hacerse en una direccin distinta,
por lo que se almacena en el vector nmero 21h de la tabla de vectores de interrupcin la direccin donde reside la rutina. Por lo tanto, cuando un programa
quiere invocar algn servicio lo que se hace es ejecutar una instruccin 'INT
21h'. A modo de ejemplo, veamos qu haramos para imprimir una cadena almacenada en la direccin 1234h:ABCDh :
PUSH DS
; preserva el contenido de DS
MOV AX,1234h
; segmento de la dir. de la cadena
MOV DS,AX ; en DS
MOV DX,ABCDh ; offset de la dir. de la cadena
MOV AH,9 ; servicio 9: escribir cadena
INT 21h
; invoca la funcin del DOS
POP DS
; restaura DS

La interrupcin 21h nunca se invoca a causa de una interrupcin hardware,


por lo que no se ejecuta entre dos instrucciones cualquiera. Cuando sta se
ejecuta, es debido a que el programa la ha invocado expresamente. Por tanto, no
es necesario preservar completamente el estado del uP, y de hecho no se hace.
Muchos de los servicios de la interrupcin 21h devuelven valores en los registros, por lo que necesariamente son modificados; en los servicios de ficheros
un flag se utiliza para indicar si ha habido un error, y en caso de que lo haya
habido AX contiene un cdigo de error. SS y SP siempre se conservan (por el
propio funcionamiento de las interrupciones, si la rutina modificase estos registros no se podra volver al programa), pero los dems registros no siempre
lo hacen. En los manuales de referencia de las interrupciones del DOS suele
venir especificado qu registros se conservan, pero lo habitual es empujar los
registros cuyo contenido interesa conservar en lugar de consultar la referencia. A no ser que se use para devolver algn valor, se suele suponer que DS se
conserva. Si interesa conservar el contenido de algn otro registro, es mejor
empujarlo en la pila.
El BIOS tambin es, principalmente, un conjunto de funciones que el programador puede usar. Las funciones son de ms bajo nivel que las del DOS, y de
hecho las funciones del DOS suelen valerse de llamadas a la BIOS para cumplir
su tarea. La BIOS puede variar de una placa a otra, pero siempre debe proporcionar algunas funciones que vienen definidas por el estndar PC, como por
ejemplo la funcin para escribir un carcter en la pantalla o la funcin para
poner un pixel a un color determinado, usada en modo grfico. Las funciones
grficas del BIOS estn agrupadas en la INT 10h, siendo AH el que especifica el
servicio o funcin en concreto, y, en algunas funciones, AL la subfuncin.
Con lo que ya sabemos, podemos utilizar las interrupciones del BIOS y del
DOS sin ms problema. Al igual que para programar en un lenguaje como Pascal o
C es tan importante conocer la sintxis como las funciones que nos da la
librera estndar de nuestro compilador, para programar en ASM tambin es necesario conocer las interrupciones del BIOS y el DOS. Que yo sepa, nadie se
aprende los servicios de memoria, por lo que lo habitual es programar con un
manual de referencia de las interrupciones. Por poco dinero, encontraris en
cualquier librera un libro con estos datos. Se hace casi imprescindible para
programar en ASM, por lo que os recomiendo que os hagis con uno. Adems, a
partir de ahora escribiremos programitas en ASM que habr que ensamblar y
linkar, por lo que os har falta el Macro Assembler de Microsoft (abreviadamente MASM) o el Turbo Assembler de Borland (TASM). No os voy a recomendar ninguno
personalmente, ya que las caractersticas principales de ambos son iguales,
pero debera transmitiros la opinin generalizada del rea de programacin ASM
de FidoNET, que se decanta claramente por el TASM. De todas maneras, en el
curso no usaremos caractersticas particulares de ninguno de los dos, por lo
que podris usar cualquiera de los dos para seguirlo.

En el captulo que viene veremos el 'Hello, world' en ASM, con el objetivo


principal de ver el esqueleto de un listado ASM para el MASM o el TASM. Seguiremos despus con el juego de instrucciones, viendo los flags y los saltos
primero. Cuando conozcamos la mayora de las instrucciones, comenzaremos a ver
las tareas principales de cualquier programa: interaccin con el usuario,
manejo de ficheros, gestin de memoria, etc... Si alguien tiene inters en algn tema en especial, no tiene ms que comentrmelo. Personalmente, me gustara
ver algo de programacin grfica, que es mi hobby preferido, pero slo si hay
gente interesada. Dentro de unos pocos captulos, iremos viendo a la vez
nuevas instrucciones y servicios que proporcionan el DOS y el BIOS, ya que esto
es un curso y no una referencia de ASM. Al igual que hasta ahora, no busco dar
una definicin terica del ASM (esto no es la Universidad), sino que quiero que
quien siga el curso pueda hacer en ASM todo lo que hace en otros lenguajes.
Salut :-)
Jon

INTRODUCCION AL ASM: USO DE LOS PROGRAMAS ENSAMBLADORES


=======================================================
Como anticipamos en el anterior captulo, comenzaremos aqu con el uso de
los ensambladores comerciales. Despus de algo de teora, veremos el famoso
'Hello, world' tal y como quedara en lenguaje ensamblador.
Para entender bien todo el proceso desde el fichero fuente, que suele llevar
la extensin '.ASM', hasta el programa que reside en memoria una vez que el DOS
lo ha cargado, haremos el camino de atrs a delante, es decir, comenzando en el
programa final y acabando en el '.ASM'.
Un programa en memoria, una vez cargado, suele consistir en un bloque de
bytes (desde unas decenas de bytes hasta algunos cientos de kilobytes), en el
que residen tanto el cdigo como los datos y la pila del programa. Ya que la
longitud total puede ser superior a 64 KB, no se puede usar un solo valor de
segmento para acceder a todo el cdigo y a todo los datos del programa. Para
posibilitar el acceso a todo el bloque de programa, este bloque est dividido
en varios bloques distintos, normalmente alineados para comenzar en direcciones
mltiplos de 16d. A estos bloques lgicos (ya que la divisin no es fsica), se
les denomina 'segmentos'; no hay que confundir este trmino con los 'segmentos'
que vimos al ver la arquitectura segmentada de los 80x86: aquellos 'segmentos'
eran siempre de 64K bytes, mientras que estos 'segmentos' pueden tener cualquier longitud menor o igual a 64K bytes.
Estos bloques lgicos o segmentos se clasifican en funcin de lo que contienen. Principalmente, pueden contener cdigo, datos, o constituir espacio para
la pila. Por tanto, se habla de 'segmentos de datos', 'segmentos de cdigo', y
'segmento de pila' (no suele haber ms que un segmento de pila).
Durante la ejecucin del programa, CS suele contener un valor de forma que
CS:0 sea el primer byte de uno de los segmentos de cdigo, mientras que DS
suele contener un valor de manera que DS:0 sea el primer byte de uno de los
segmentos de datos. SS se suele mantener inalterado a lo largo del programa,
apuntando al segmento de pila.
Para saltar de un punto del programa a otro que resida en otro segmento, es
necesario modificar tanto CS como IP, mientras que si el destino reside en el
mismo segmento que la instruccin de salto basta con modificar IP. De la misma
forma, para acceder a una variable que resida en el segmento de datos apuntado
por DS no es necesario modificar DS, mientras que si la variable reside en otro
segmento de datos es necesario cargar DS o ES para que apunte al principio del
otro segmento antes de acceder a la variable. Como podemos intuir, el acceso
a cdigo y datos 'cercanos' (que residan en el CS y DS actuales) es mucho ms
cmodo, adems de ligeramente ms rpido, que el acceso a cdigo y datos lejanos (en otros segmentos que los apuntados por CS y DS).

Ms adelante volveremos a este galimatas de segmentos y veremos lo que son


los famosos 'modelos' estndar de memoria. Por ahora, dejemos este tema y sigamos.
Esta imagen de memoria la crea el DOS a partir del fichero '.EXE' que el
usuario ha ordenado ejecutar (ms adelante veremos las peculiaridades de los
'.COM'). Lo que el DOS hace no es smplemente cargar el '.EXE' tal y como est
en el disco y saltar a la primera direccin del '.EXE', sino que el proceso de
carga es bastante complejo. En realidad, el '.EXE' es el bloque que hemos
visto, compuesto de varios bloques lgicos (llamados segmentos), y precedido de
una cabecera de longitud variable. El estudio detallado de esta cabecera queda
fuera de nuestro mbito, pero nos interesa saber el contenido aproximado.
Los primeros dos bytes del '.EXE' son la cadena 'MZ', que lo identifican
como '.EXE'. La cabecera indica, adems, su propia longitud, un CRC para el
fichero completo (es decir, una suma utilizada para verificar la integridad del
fichero), el punto - dentro del bloque de cdigo y datos - al que hay que saltar (el punto de entrada al programa), los valores iniciales de SS y SP para
apuntar a la pila, y algn detalle ms. Pero, adems de todo esto, lo ms
importante de la cabecera es la llamada 'tabla de reubicacin'.
En un sistema MSDOS, los programas deben ser capaces de correr en cualquier
punto de la memoria. Las instrucciones de los procesadores 80x86 son en muchos
casos relativas (es decir, un salto suele ser 'salta 10 bytes ms adelante' y
no 'salta a la direccin 310'), con lo que gran parte del cdigo funciona en
cualquier punto de la memoria. Pero existen algunas instrucciones que utilizan
direcciones absolutas, como por ejemplo cargar en un registro la direccin
completa de una variable (offset y segmento). Estas instrucciones tienen que
ser distintas cuando los datos estn en la zona baja de la memoria o en la zona
alta. Para solucionar este problema, un '.EXE' lleva en la cabecera una tabla
en la que se indican todas las instrucciones que necesitan ser modificadas al
cargar el programa.
Por tanto, el proceso de carga es el siguiente (realizado por el MSDOS):
- Carga del '.EXE' en dos partes: cabecera y bloque de programa.
- Recorrido de la tabla de reubicacin, arreglando cada referencia
marcada en la tabla.
- Inicializacin de los registros de segmento, tal y como viene
especificado en la cabecera, y salto al punto de entrada.
Hay que especificar dos cosas en es punto, antes de pasar a los ficheros
'.OBJ': la primera, que todas las referencias de la tabla de reubicacin no son
para instrucciones, sino que algunas pueden ser para datos. Esto ocurre en el
caso de que una variable esttica, al ser declarada, sea inicializada con un

valor que dependa de la situacin el memoria de otra variable. La segunda puntualizacin es que el MSDOS se encarga de inicializar algunos registros de segmento antes de entrar al programa, pero no todos. CS, evidentemente, apunta al
segmento de cdigo que contien el punto de entrada. SS apunta ya al segmento de
pila. Pero DS, en principio, no apunta a ningn sitio en concreto. Por ello,
las primeras instrucciones de un programa ASM suelen ser para inicializar DS
apuntando al segmento de datos que vayamos a usar.
Un fichero '.EXE' como los que hemos visto lo construye un programa que se
incluye con cualquier compilador o ensamblador, llamado enlazador o LINKer (en
spanglish). Los linkers de Microsoft suelen llamarse LINK.EXE, mientras que los
de Borland se llaman siempre TLINK.EXE.
Un linker toma como entrada un conjunto de uno o varios ficheros llamados
'ficheros objeto', y genera como salida un fichero '.EXE' (opcionalmente, el
TLINK puede generar un '.COM', mientras que con el LINK es necesario convertir
el '.EXE' resultante en '.COM' con la utilidad EXE2BIN). Los ficheros objeto
tienen extensin '.OBJ', y son ficheros generados directamente por los ensambladores y compiladores a partir de nuestros listados con el cdigo fuente.
Un fichero '.OBJ' contiene varios bloques, que son los que se usan luego
para construir los segmentos que van en el '.EXE'. Adems de estos bloques, los
ficheros '.OBJ' llevan tambin algunas referencias que luego debern ser incluidas en la tabla de reubicacin del '.EXE'. Pero lo que diferencia principalmente a un '.OBJ' de un '.EXE' (aparte del formato de fichero), son algo
llamado 'referencias externas' y 'declaraciones pblicas'.
Para facilitar el trabajo del programador, los lenguajes actuales permiten
que un programa '.EXE' se construya a partir de cdigo de varios listados
diferentes. Adems, cada uno de estos 'mdulos' (que as se llaman) puede estar
escrito en un lenguaje diferente, siempre que tengamos un compilador para ese
lenguaje que genere ficheros '.OBJ' estndar. Una de las complicaciones que
esta posibilidad aade, es que es necesario poder acceder desde un mdulo a
funciones y variables declaradas en otro mdulo. Lo que se hace es permitir a
cualquier mdulo hacer declaraciones externas, en las que se indica al compilador que una variable o funcin est en algn otro mdulo con el que luego
linkaremos ste. As, el '.OBJ' generado incluye precisamente una referencia
externa con el nombre de la funcin o variable. El mdulo que define una variable o funcin a la que se quiere acceder desde otro mdulo debe declarar a sta
como 'pblica', de manera que el '.OBJ' generado lleve una pequea indicacin
advirtiendo que la variable de ese nombre est en ese mdulo, junto con la
direccin de sta. El linker es el que se encarga de resolver todas estas referencias, modificar todas las instrucciones que sea posible en este punto y
dejar las que no se puedan arreglar en la tabla de reubicacin del '.EXE'.
Otra de las cosas que hace el linker es 'unir' los segmentos de distintos

mdulos en uno slo. Ya que cada segmento del '.OBJ' va acompaado de un nombre
y de algunos datos, si estos coinciden el linker supone que se ha hecho as
intencionadamente y los une en uno slo, poniendo uno a continuacin del otro.
Las reglas que sigue el linker para reconocer los segmentos y actuar en consecuencia son, junto con el eslabn perdido, uno de los misterios ms ignotos de
la humanidad. Ahora en serio, las reglas que siguen en LINK y el TLINK para
reconocer el segmento de pila, etc... son ligeramente distintas, pero existe
una manera de hacer todo esto sin preocuparse de los nombres de los segmentos,
y, por ejemplo, linkar un mdulo ASM con uno escrito en C y conseguir fundir
los segmentos de cdigo y datos de los dos mdulos. Para esto usaremos carctersticas del MASM 5.0 y superiores, y del TASM 1.0 y superiores, por lo que os
recomiendo que consigis al menos estas versiones de los ensambladores (con uno
de los dos vale).
Adems de los ficheros '.OBJ', un linker puede tambier linkar ficheros
'.LIB'. Un '.LIB' o fichero de librera es un fichero que contiene un conjunto
de ficheros '.OBJ', uno detrs de otro, que por alguna razn se suelen usar
conjuntamente. Adems, el '.LIB' lleva un ndice hasheado para encontrar rpidamente las referencias a smbolos del '.LIB'. Cuando el linker encuentra una
referencia a un smbolo externo, primero lo busca en los '.OBJ' especificados
por el usuario, y en caso de no encontrarlo busca en los ndices de las '.LIB'
especificadas. Si lo encuentra, extrae el '.OBJ' de la '.LIB' en el que aparece el smbolo (variable o funcin) y lo aade al '.EXE' generado. Por ejemplo,
todas las funciones que un compilador de C provee al programador vienen en una
librera (llamada librera estndar). Es bastante sencillo extraer de un '.LIB'
los '.OBJ' que lo componen, aunque la mayora de los gestores de librera no lo
permiten.
Ahora que ya sabemos cmo son los '.OBJ', el estudio del listado fuente de
programa en ASM resulta bastante sencillo: un listado fuente en lenguaje ensamblador es prcticamente una imagen del '.OBJ' que se quiere generar. El
listado est dividido en fragmentos, denominados 'segmentos', que son los bloques que el '.OBJ' lleva para el linker. Estos segmentos pueden ser de cdigo
o de datos. Adems, podemos encontrar declaraciones PUBLIC (smbolos que se
exportan en el '.OBJ' para uso y disfrute generalizado de los dems mdulos);
podemos tambin encontrar declaraciones EXTRN (smbolos que se indican en el
'.OBJ' al linker para ser buscados entre los dems mdulos).
Veamos ya cmo quedara en ASM el famoso 'Hello, world!':
=================8<============================8<=================
==========
PILA SEGMENT STACK 'STACK'
; Abre el segmento de pila
DW 100h DUP (?)
; 200h (512d) bytes de pila
PILA ENDS
; Cierra el segmento de pila

DATOS
SEGMENT 'DATA'
Msg DB 'Hello, world!$'
DATOS
ENDS

; Abre el segmento de datos


; Mensaje a imprimir
; Cierra el segmento de datos

CODIGO
SEGMENT 'CODE'
; Abre el segmento de cdigo
ASSUME CS:CODIGO, DS:DATOS, SS:PILA
Entrada
PROC
; Abre el procedimiento 'Entrada'
mov ax,DATOS
; Valor de segmento para 'DATOS'
mov ds,ax
; Para acceder a 'Msg'
mov dx,OFFSET Msg
; Para la int 21h, servicio 9
mov ah,9
; Especifica servicio9
int
21h
; Invoca servicio 9: imprimir
cadena
mov ax,4C00h
; Servicio 4Ch, valor de retorno 0
int
21h
; Invoca servicio 4Ch: retorno al
DOS
Entrada
ENDP
; Cierra el procedimiento 'Entrada'
CODIGO
ENDS
; Cierra el segmento 'CODIGO'
END Entrada
; Fin del programa, punto de entrada
'Entrada'
=================8<============================8<=================
==========
Los ensambladores ms modernos permiten una manera ms sencilla de escribir
este programa, usando las directivas de segmento simplificadas, pero creo que
es mejor ver primero la forma 'clsica' y luego la simplificada, con lo que la
comprensin es mucho mayor.
Como vemos, el listado est dividido en tres partes principales, que corresponden directamente a los segmentos del '.OBJ'. El principio de un segmento se
indica con la directiva del ensamblador 'SEGMENT', y el final con la directiva
'ENDS'. Hay que tener en cuenta que estas directivas no son instrucciones en
ASM, sino que indican al ensamblador cmo debe realizar su trabajo, por lo que
no generan cdigo. Al final del listado siempre debe aparecer la directiva END,
que indica el final del listado. Opcionalmente, puede llevar un argumento cuyo
significado veremos ms tarde.
El trabajo del ensamblador es bsicamente el siguiente: va leyendo lneas
del fichero '.ASM', que sern siempre directivas. Cuando encuentra una lnea
con la directiva SEGMENT, prepara un buffer para ir introduciendo en l los
datos que ir leyendo. Inicializa un contador interno a 0, para apuntar al
principio de buffer. Entonces, comienza a procesar las lneas de otra forma
hasta que encuentra un ENDS, que indica el final del segmento. El contenido del
buffer ir al '.OBJ' prcticamente tal y como est. Las lneas interiores a un
segmento (entre un SEGMENT y su ENDS correspondiente) se procesan de otra

forma. Aunque algunas pueden ser directivas, la mayora estn destinadas a generar cdigo o datos que irn en el '.OBJ'. Despus de almacenar los bytes que
una lnea define en el buffer, el contador interno se incrementa para apuntar
a los siguientes bytes libres. Estas lneas pueden ser lneas de cdigo o lneas de datos. Si son de cdigo, la sintxis ser la siguiente:
ETIQUETA: INSTRUCCION OPERANDO(S) ;COMENTARIO
Mientras que si son lneas de datos, la sintxis ser la siguiente:
ETIQUETA D<X> DATOS ;COMENTARIO
En esta lnea, <X> se sustituye por una letra que indica el tamao de los
datos (B: bytes, W: palabras, D:dobles palabras, ...).
La etiqueta es un smbolo, en principio nico (no puede haber dos etiquetas
iguales en un mismo listado), que el ensamblador almacena en una tabla de smbolos interna junto con el valor del contador interno al procesar la lnea.
Tambin se almacena el contexto en el que aparece la etiqueta (si es de datos
o de cdigo, el tamao de los datos a los que apunta, el segmento al que pertenece...). Se utiliza para referenciar desde otro punto una variable por nombre,
en caso de que se trate de una etiqueta de datos, o para saltar a un punto
determinado desde otro lugar del programa.
La instruccin y los operandos son cualquier instruccin vlida del 80x86, y
el comentario puede ser cualquier cosa que aclare la instruccin comentada. Los
datos de una lnea del segundo tipo pueden ser nmeros separados por comas,
en cuyo caso se sitan uno detrs del siguiente. Cuando no se quiere inicializar una variable, se puede poner un smbolo de interrogacin ('?'). Si se quiere repetir varias veces una secuencia, se puede utilizar la construccin:
n DUP (dato, dato, dato)
Donde 'n' es un nmero que representa cuntas veces se debe repetir la secuencia.
Estudiemos ahora con detalle el listado HELLO.ASM. Para hacer la prueba, se
puede recortar el listado y salvarlo como HELLO.ASM. Despus, podemos ensamblar
con 'TASM HELLO' o con 'MASM HELLO;'. El linkado lo haremos con 'TLINK HELLO' o
'LINK HELLO;', tras lo cual tendremos el HELLO.EXE en el directorio actual. La
sintxis de la lnea de comandos vara ligeramente de una versin a otra del
mismo compilador, por lo que os recomiendo consultar el manual o hacer algunas
pruebas.
Las primeras tres lneas del listado proporcionan una segmento para la pila
del programa. Como vemos, el segmento lleva un nombre que le proporcionamos

nosotros, en este caso el nombre 'PILA'. Este nombre es cualquier identificador


vlido en ASM; recordemos que el ensamblador no distingue entre maysculas y
minsculas. Despus de la directiva SEGMENT van varios parmetros opcionales.
Ya que normalmente usaremos las directivas de segmento simplificadas, no entraremos a detallar estos parmetros. Si alguien tiene mucho inters puede consultar el manual del ensamblador o la ayuda online de ste, en caso de que tenga.
Como se aprecia, se dejan 100h (256d) palabras sin inicializar para la pila.
Esto suele ser suficiente para programas que no usen la pila intensivamente.
Las siguientes tres lneas definen otro segmento, que lleva los datos del
programa. En este caso, la nica variable es una cadena con el mensaje a imprimir. Cuando tras un DB aparece una cadena entre comillas simples, el ensamblador introduce todos los caracteres, uno detras de otro.
Para no hacer el mensaje demasiado largo, continuaremos el estudio de ste
listado en el siguiente captulo.
Salut :-)
Jon

INTRODUCCION AL ASM: USO DE LOS PROGRAMAS ENSAMBLADORES (II)


============================================================
Continuamos en este octavo captulo del curso la explicacin del HELLO.ASM
en el punto donde la dejamos al final del anterior captulo.
El ltimo segmento del programa es el segmento de cdigo, al que le hemos
dado el nombre 'CODIGO'. La primera lnea de este segmento es un ASSUME, cuya
funcin vamos a ver detalladamente.
Cuando vimos los distintos modos de direccionamiento, vimos que cada uno
usaba un registro segmento por defecto (por cierto, uno de los registros de
segmento estaba equivocado: el modo [BP+xx] usa el segmento SS por defecto, y
no el DS como apareca). Cuando queremos que una instruccin que opera por
defecto con un segmento opere con otro, es necesario anteponer al operando que
referencia a memoria el nombre del segmento seguido de dos puntos. Por ejemplo,
veamos la siguiente instruccin, que accede al segmento apuntado por DS:
mov ax,[0]
Si queremos que cargue la primera palabra del segmento apuntado por CS:
mov ax,cs:[0]
A nivel de cdigo de mquina, lo que se hace es prefijar la instruccin anterior con un byte (existen cuatro posibles valores, uno para cada segmento)
que especifica que no se debe usar el segmento por defecto. A este prefijo se
le suele denominar 'segment override'.
Cuando en un listado en lenguaje ensamblador se genera un acceso a memoria,
el ensamblador es capaz de meter automticamente 'segment override's donde sea
necesario (adems de permitirnos especificarlos a nosotros). Para ello, hay que
decirle al ensamblador a cual de los segmentos que hemos definido en el listado
est apuntando cada uno de los registros de segmento. As, cuando generamos una
referencia a una etiqueta, comprueba en qu segmento ha sido definida y busca
cul de los registros de segmento apunta a se segmento. As, puede decidir si
es necesario generar un 'segment override', y cul de ellos es necesario. la
directiva 'ASSUME' dice al ensamblador a qu segmento apunta cada registro de
segmento, como vemos en HELLO.ASM. De todas maneras, somos nosotros los
encargados de hacer que, efectivamente, los registros de segmento apunten a los segmentos especificados.
En caso de que el ensamblador encuentre un acceso a una etiqueta definida en
un segmento no mencionado en el ASSUME, se quejar con un error del tipo
'unreachable data' ('datos inaccesibles').

Si un segmento no va a estar apuntando a ninguno de los segmentos definidos


en el listado (por ejemplo, si apunta a la memoria de vdeo), se puede aadir
un ASSUME con nombre de segmento 'NOTHING', de manera que el ensamblador no
asuma que el registro de segmento en cuestin permite acceder a ninguna variable conocida. Al principio del proceso de ensamblado, el ensamblador est como
si hubiera ledo una lnea as:
ASSUME CS:NOTHING, DS:NOTHING, ES:NOTHING, SS:NOTHING
Es importante comprender el funcionamiento de la directiva ASSUME, ya que
puede causar confusiones en algunos casos.
Dentro de un segmento de cdigo, las instrucciones incluidas pueden estar
agrupadas en bloques lgicos, llamados 'procedimientos'. Estos funcionan a modo
de las funciones de C o los procedimientos y funciones de Pascal. Aunque las
ltimas versiones de los ensambladores permiten dar listas de argumentos, variables locales, etc... (generando el cdigo oportuno para manejarlos), no entraremos en ello. Para nosotros no ser ms que una manera de agrupar el cdigo
en bloques lgicos, y de identificar estos bloques mediante un nombre. El nombre de un procedimiento entra en la tabla de smbolos, y se le asocia la direccin de la primera instruccin despus de la directiva 'PROC'.
En el programa HELLO, tenemos un nico PROC, al que llamamos 'Entrada'.
Veamos las lneas detalladamente:
* mov ax,DATOS
La etiqueta DATOS est en la tabla de smbolos, asociada al segmento de este
nombre. El ensamblador genera en el '.OBJ' una referencia, que ir despus en
el '.EXE', de manera que esta instruccin cargue en el registro AX el valor de
segmento necesario para que 'ax:0' apunte al primer byte del segmento datos.
Este valor variar de una vez que se cargue el programa a otra, pero el cargador del DOS hace que siempre se asigne el valor correcto a AX.
* mov ds,ax
Hace que DS, efectivamente, apunte al segmento de cdigo del programa. As,
podremos acceder a la cadena contenida en este segmento.
* mov dx, OFFSET Msg
Carga DX con el offset de la etiqueta Msg dentro de su segmento. Ya que Msg
est al principio del segmento, dx se cargar con el valor 0. Aunque el programa pueda cargarse en cualquier punto de la memoria, el offset de algn dato o
alguna instruccin dentro de su segmento ser siempre el mismo, por lo que esta

instruccin no se incluye en la tabla de reubicacin del '.EXE'.


* mov ah,9
* int 21h
Se invoca el servicio 9 de la interrupcin 21h del DOS. Este servicio escrien la pantalla la cadena apuntada por DS:DX (que en este caso ser la cadena
Msg). Se retorna al encontrar el carcter '$', que no se imprime. Podemos apreciar que en el segmento DATOS aparece el smbolo '$' dentro de la propia
cadena.
* mov ax,4C00h
* int 21h
Se invoca el servicio 4Ch de la interrupcin 21h. Este servicio retorna al
programa que invoc al programa actual (normalmente al COMMAND.COM), con un
nivel de error ('ERRORLEVEL') especificado en el registro AL. En este caso, se
retorna el valor 0 (que habitualmente indica ausencia de errores).
Despus de cerrar el procedimiento 'Entrada' y el segmento 'CODIGO', encontramos una ltima directiva, 'END', con el nombre del procedimiento como parmetro. Esta directiva puede llevar un nico parmetro, opcional, que indica el
punto de entrada al programa. En caso de linkar varios '.OBJ' para generar un
ejecutable, slo uno de ellos puede especificar punto de entrada (pero necesariamente alguno debe especificarlo), por lo que el resto de los mdulos terminan su listado con un simple 'END'. En este caso, ya que 'Entrada' se asocia a
la primera instruccin del procedimiento, el punto de entrada es la instruccin
'mov ax,DATOS'.
Antes de pasar a ver las directivas simplificadas de segmento, veamos un
punto importante de los ensambladores. Los ensambladores comerciales llevan
incorporado un evaluador de expresiones, de manera que en cualquier lugar donde
el ensamblador espere una constante puede aparecer una expresin que pueda
evaluarse en tiempo de ensamblado. Estas expresiones pueden llevar operadores;
podremos encontrar una lista de todos en el manual del ensamblador o en la ayuda online. Los ms importantes son los siguientes:
+,-,*,/,MOD
Aritmticos
OR, AND, XOR, NOT Lgicos
SEG, OFFSET
Aplicados a una etiqueta, dan el valor de segmento donde
reside (se incluye una entrada en la tabla de relocalizacin) y el offset dentro de su segmento
SHL, SHR
Sintxis: X SHL Y. Devuelve el valor X desplazado a la
izquierda (derecha) Y bits.
()
Se utilizan para saltarse la precedencia de operadores

Lneas vlidas utilizando estos operadores son las siguientes (suponiendo


que todas las etiquetas hayan sido definidas previamente):
Msg
DW 55h SHR 2
Variable DB 15 + OFFSET Msg
mov ax,4+3*OFFSET Msg
mov dx,2 + NOT 01010101b
add ax,es:[bx+di+1+OFFSET Msg]
sub si,(2Ah+13d)*2
Son todas vlidas porque las expresiones que aparecen puede evaluarlas el
propio ensamblador para obtener valores numricos. Las siguientes instrucciones, en cambio, aunque perfectamente inteligibles, son ilegales y el ensamblador se quejar si las inclumos:
mov ax,2+bx
add dx,bx*ax
mov cx,NOT dx
Veamos ahora una versin del programa anterior utilizando las directivas de
segmento simplificadas. Veamos cmo se simplifica mucho:
=================8<============================8<=================
==========
.MODEL SMALL
; Un segmento de cdigo, otro con los datos y la pila
.STACK 200h
; 200h (512d) bytes de pila
.DATA
; Abre el segmento de datos
Msg DB 'Hello, world!$'
.CODE
Entrada
mov
mov
mov
mov
int
mov
int
Entrada

; Abre el segmento de cdigo, cierra el de datos


PROC ; Abre el procedimiento 'Entrada'
ax,@data
ds,ax
dx,OFFSET Msg
ah,9
21h
; Servicio 9: imprimir cadena
ax,4C00h
21h
; Servicio 4Ch: retorno al DOS
ENDP ; Cierra el procedimiento 'Entrada'

END Entrada ; Fin del programa (punto de entrada 'Entrada'), cierra


; el segmento de cdigo
=================8<============================8<=================
==========
Las nuevas directivas utilizadas son las que comienzan con un punto. La

primera, '.MODEL', especifica el modelo de memoria que se va a utilizar. En


funcin de el modelo de memoria se seleccionan los nombres de los segmentos, de
manera que se pueda linkar el mdulo con otro mdulo escrito en un lenguaje de
alto nivel usando el mismo modelo de memoria. La directiva '.STACK' genera un
segmento de pila, de 1024 bytes en caso de que no se especifique el tamao. La
directiva '.DATA' abre el segmento de datos, cerrando el segmento anterior en
caso de que haya alguno abierto. La directiva '.CODE' abre el segmento de cdigo, cerrando (en este caso) el segmento de datos. Por fin, la directiva 'END'
es ampliada para cerrar el segmento abierto cuando aparece, en este caso el
segmento de cdigo.
Ya que no sabemos el nombre que da el ensamblador a los distintos segmentos,
existen algunos smbolos que se definen automticamente al encontrar la directiva '.MODEL'. Estos smbolos representan el nombre de los distintos segmentos
generados, y son dos: @code y @data. Por eso, en la primera instruccin cargamos DS con @data, para poder acceder a 'Msg', que est en el segmento dado por
'.DATA'.
El propio ensamblador genera el 'ASSUME...' al encontrar la directiva
'.CODE'. Como vemos, no es necesario preocuparse por casi nada al usar las
directivas simplificadas de segmento. De hecho, ya no es habitual encontrar
programas escritos con las directivas antiguas. Estas directivas se aadieron
para facilitar el desarrollo de programas con mdulos en distintos lenguajes,
ya que la tarea se complica mucho usando las directivas clsicas.
En el siguiente captulo veremos el ltimo concepto que nos queda por ver
de microprocesadores: los flags. Veremos algunas instrucciones nuevas y alguna
interrupcin nueva, vindolo todo con un pequeo programa de ejemplo. De aqu
en adelante, iremos cubriendo distintos temas progresivamente, de manera que
se vean ejemplos de todos los usos habituales del lenguaje ensamblador. De
esta manera, aunque el curso no ser muy til como referencia de ASM, quien
lo siga habr visto la mayora de los aspectos del ASM y podr abordar cualquier proyecto en este lenguaje, que es cuando realmente adquirir verdadero
dominio del lenguaje. Os recomiendo que os hagis al menos con dos manuales o
guas de referencia: una con las instrucciones del 80x86, y otra con las
interrupciones de la BIOS y el MSDOS. Sobre las instrucciones no me atrevo a
recomendar ninguna, mientras que sobre las interrupciones tenis por ah unos
documentos, escritos por Ralph Brown, denominados INTERxxA.ARJ, INTERxxB.ARJ
e
INTERxxC.ARJ (xx es la versin, la ltima es la 33, creo) con la ms completa
referencia de interrupciones que existe (son muchos megas de texto!). Son de
dominio pblico, por lo que las encontraris en bastantes BBS. Ah tendris
todo lo que queris saber sobre los usos de las diferentes interrupciones.
Salut :-)

Jon

Vous aimerez peut-être aussi