Académique Documents
Professionnel Documents
Culture Documents
=================================
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
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...
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
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
->
->
->
->
; MAL
; MAL
MOV AX,[BP]
----------------------------------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
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
; inicializa la pila
; empuja el valor 55AAh
; suma a BX el valor 1111h
MOV AX,8000h
MOV SS,AX
MOV SP,0FFFEh
; inicializa la pila
MOV AX,55AAh
PUSH AX
MOV BX,AX
ADD BX,1111h
PUSH BX
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
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
'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
POP CX
POP BX
POP AX
IRET
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
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
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
Jon