Académique Documents
Professionnel Documents
Culture Documents
TEMARIO
Introducción.
I.- Definiciones y conceptos preliminares.
1.- Conceptos básicos.
1.1.- El lenguaje de maquina y el lenguaje ensamblador.
1.2.- Interpretes, compiladores y ensambladores.
1.3.- El proceso de link, las rutinas de RUN-TIME y
los servicios del sistema operativo.
2.- La arquitectura de las computadoras personales IBM y
Compatibles.
3.- La arquitectura de los microprocesadores Intel.
4.- El sistema operativo MS-DOS.
5.- Ensambladores y Macroensambladores.
6. Sistemas numéricos
7. Operaciones con bytes
7.1. AND
7.2. OR
7.3. XOR
7.4. NOT
8. El juego de registros.
9.- ¿Qué es....la BIOS?
9.1. Configuración avanzada y del chipset
9.2. Periféricos integrados
9.3. Administración de energía
9.4. Configuración de PNP y slots PCI
9.5. Autoconfiguración de la BIOS
9.6. Otras utilidades
9.7. Salir de la BIOS
9.8. Actualizar la BIOS
9.9. La BIOS y la pila
9.10. Cómo saltarse la password de la BIOS
II.- El lenguaje ensamblador.
1.- Un ejemplo clásico.
2.- El formato del ensamblador.
3.- Directivas.
4.- Conjunto de instrucciones.
5.- Macros y procedimientos.
6.- Interrupciones.
III.- Creación y depuración de programas en lenguaje ensamblador
1.- Edición.
2.- Ensamblado.
3.- Link.
4.- Ejecución.
5.- Depuración.
5.1.- Uso de DEBUG de DOS I
5.2.- Uso de DEBUG II: Trazar un Programa
Manual de Lenguaje Ensamblador
Bibliografía.
Manual de Lenguaje Ensamblador
INTRODUCCION.
Tal y como lo indica su título, está enfocado al lenguaje ensamblador de los microprocesadores Intel,
particularmente el 8088, 8086, 80186, 80188 y 80286. Sin embargo, todos los programas generados para estos
procesadores correrán en un 80386 ó 80486. Adicionalmente se proporciona el juego de instrucciones de los
coprocesadores matemáticos 8087 y 80287, que es compatible con el de los microprocesadores ya citados, es
decir, el manual se enfoca completamente hacia las computadoras que operan con procesadores de la familia x86
de Intel y, considerando que el ensamblador basa su funcionamiento en los recursos internos del procesador, los
ejemplos descritos no son compatibles con ninguna otra arquitectura.
Este documento trata de abarcar, de la forma más general, todo aquello que involucra el conocimiento y
uso del lenguaje ensamblador. Así, ha sido organizado en tres partes. La primera describe los conocimientos
básicos que deben poseerse para una mejor comprensión e interpretación de lo que es el lenguaje ensamblador y
cómo debe ser usado. La segunda parte está dedicada por completo a lo que es el lenguaje ensamblador, las
interrupciones del sistema operativo MS-DOS y el conjunto de instrucciones 8086-80286. La última parte se ha
dedicado al proceso de ensamble, la depuración de programas en ensamblador, y algunas consideraciones sobre
la creación de programas y su ejecución.
Manual de Lenguaje Ensamblador
Ahora bien, siendo el lenguaje de máquina un conjunto de números, ¿cómo es capaz el microprocesador
de saber cuándo un número representa una instrucción y cuándo un dato? El secreto de esto reside en la
dirección de inicio de un programa y en el estado del microprocesador. La dirección de inicio nos indica en qué
localidad de memoria comienza un programa, y en consecuencia que datos deberemos considerar como
instrucciones. El estado del microprocesador nos permite saber cuándo éste espera una instrucción y cuándo
éste espera un dato.
Ahora bien, mientras que con el lenguaje de máquina, nosotros obtenemos un control total del
microprocesador, la programación en este lenguaje resulta muy difícil y fácil para cometer errores. No tanto por el
hecho de que las instrucciones son sólo números, sino porque se debe calcular y trabajar con las direcciones de
memoria de los datos, los saltos y las direcciones de llamadas a subrutinas, además de que para poder hacer
ejecutable un programa, se deben enlazar las rutinas de run-time y servicios del sistema operativo. Este proceso
es al que se le denomina ensamblado de código. Para facilitar la elaboración de programas a este nivel, se
desarrollaron los Ensambladores y el Lenguaje Ensamblador.
Manual de Lenguaje Ensamblador
Existe una correspondencia 1 a 1 entre las instrucciones del lenguaje de máquina y las del lenguaje
ensamblador. Cada uno de los valores numéricos del lenguaje de máquina tiene una representación simbólica de
3 a 5 letras como instrucción del lenguaje ensamblador. Adicionalmente, este lenguaje proporciona un conjunto de
pseudo-operaciones (también conocidas como directivas del ensamblador) que sirven para definir datos, rutinas
y todo tipo de información para que el programa ejecutable sea creado de determinada forma y en determinado
lugar.
Aún cuando el lenguaje ensamblador fue diseñado para hacer más fácil la programación de bajo nivel,
esta resulta todavía complicada y muy laboriosa. Por tal motivo se desarrollaron los lenguajes de alto nivel, para
facilitar la programación de los computadores, minimizando la cantidad de instrucciones a especificar. Sin
embargo, esto no quiere decir que el microprocesador ejecute dichos lenguajes. Cada una de las instrucciones de
un lenguaje de alto nivel o de un nivel intermedio, equivalen a varias de lenguaje máquina o lenguaje
ensamblador.
La traducción de las instrucciones de nivel superior a las de bajo nivel la realizan determinados
programas. Por una parte tenemos los intérpretes, como DBase, BASIC, APL, y Lisp. En estos, cada vez que se
encuentra una instrucción, se llama una determinada rutina de lenguaje de máquina que se encarga de realizar
las operaciones asociadas, pero en ningún momento se genera un código objeto y mucho menos un código
ejecutable. Por otra parte, tenemos los compiladores, como los desarrollados para Fortran, Clipper, COBOL,
Pascal o C, que en vez de llamar y ejecutar una rutina en lenguaje de máquina, éstos juntan esas rutinas para
formar el código objeto que, después de enlazar las rutinas de run-time y llamadas a otros programas y servicios
del sistema operativo, se transformará en el programa ejecutable.
Finalmente, tenemos los ensambladores— como los descritos en este trabajo —que son como una
versión reducida y elemental de un compilador (pero que de ninguna manera deben considerarse como tales), ya
que lo único que tienen que hacer es cambiar toda referencia simbólica por la dirección correspondiente, calcular
los saltos, resolver referencias y llamadas a otros programas, y realizar el proceso de enlace. Los ensambladores
son programas destinados a realizar el ensamblado de un determinado código.
1.3.- EL PROCESO DE LIGA, LAS RUTINAS DE RUN-TIME Y LOS SERVICIOS DEL SISTEMA
OPERATIVO.
Para crear un programa ejecutable a partir de un código objeto se requiere que se resuelvan las llamadas
a otros programas y a los servicios del sistema operativo, y agregar las rutinas o información de run-time para que
el programa pueda ser cargado a memoria y ejecutado. Este proceso es lo que se conoce como Link o proceso
de liga, y se realiza a través de un ligador o Linker que toma de entrada el código objeto y produce de salida el
código ejecutable.
Las rutinas de RUN-TIME son necesarias, puesto que el sistema operativo requiere tener control sobre el
programa en cualquier momento, además de que la asignación de recursos y su acceso deben hacerse
solamente a través del sistema operativo. Para los computadores personales, esto no es tan complejo como para
otros computadores y sistemas operativos, pero es requerido.
Manual de Lenguaje Ensamblador
Los computadores PC están compuestos físicamente por: monitor, teclado, CPU, floppy drives, hard disk
drives, periféricos y componentes adicionales. Lógicamente están compuestos por el BIOS (Basic Input-Output
System) y el sistema operativo MS-DOS (MicroSoft Disk Operating System). El teclado se encuentra conectado a
un puerto de entrada destinado específicamente para tal propósito. Este tiene asociado un área de memoria
(buffer) al cual llegan los códigos de las teclas presionadas en el teclado.
La CPU está compuesta por la memoria RAM, memoria ROM (donde reside el BIOS), los controladores
de disco, la tarjeta de video, los puertos de entrada-salida y el microprocesador. Los periféricos se encuentran
conectados y asociados por el sistema operativo con un puerto en particular (una vez que el puerto ha sido
abierto). Las tarjetas controladores de floppy y disco duro se encargan de intercambiar información entre
memoria, procesador y unidades de disco. Para tal propósito se reservan en memoria áreas específicas, para
servir de buffers en el intercambio de datos del computador con las unidades de disco.
El monitor y la tarjeta de video están muy relacionados entre sí, ya que si bien el monitor se encarga de
desplegar la información, no es éste quien la mantiene, sino la memoria designada a la tarjeta de video. Esta
memoria es como cualquier otra, direccionable y modificable, y es en donde se guarda la información que
aparece en pantalla. Debido al tipo de microprocesador empleado, la memoria de la PC se encuentra dividida en
una serie de blocks denominados segmentos, de 64KB cada uno. La memoria es accesada especificando el
segmento y el desplazamiento dentro del segmento (segmento: desplazamiento, para mayor detalle ver el
apéndice C). Para las PC´s la memoria se clasifica en tres tipos:
- Convencional: Es la memoria de tipo básico y que abarca las direcciones de 0 a 640KB. En ésta es
donde se cargan los programas de usuario y el sistema operativo, y es la que está disponible para equipo XT
(8088,8086, 80186 y 80188).
- Extendida: Esta memoria sólo está disponible para procesadores 80286 y mayores (equipo AT, 80386
y 80486). Muchos programas que usan la memoria convencional no pueden usar la memoria extendida porque
las direcciones en memoria extendida están más allá de las que el programa puede reconocer. Únicamente las
direcciones dentro de los 640KB pueden ser reconocidas por todos los programas. Para reconocer la memoria
extendida se requiere de un manejador de memoria extendida, como HIMEM.SYS que provee MS-DOS.
- Expandida: Esta es la memoria que se agrega al computador a través de una tarjeta de expansión, y
que debe ser administrada por un programa especial, como el EMM386.EXE. A diferencia de la memoria
convencional o extendida, la memoria expandida es dividida en bloques de 16K llamados páginas (pages).
Cuando un programa solicita información de memoria expandida el manejador copia la página correspondiente
en un área denominada page frame para poder ser accesada en la memoria extendida.
Como se puede observar, el 8088, 8086, 80188 y 80186 son capaces de direccionar hasta 1 MB de
memoria. Ya hemos indicado que la memoria convencional sólo abarca 640KB, así nos quedan 384KB libres.
Esta parte de la memoria es denominada parte alta, y como no está disponible para muchos programas
generalmente se usa para cargar drivers del sistema operativo, programas residentes y datos de hardware
(ROM y páginas de video).
Manual de Lenguaje Ensamblador
Sin importar de qué microprocesador se trate, los microprocesadores del 8088 al 80486 usan el modelo
de registros del 8086. Los microprocesadores matemáticos 80287 al 80487 utilizan el modelo de registros
expandidos del 8087. Para mayor detalle ver los apéndices A y B.
La diferencia entre los 8086 y 8088 con los 80186 y 80188 no es muy grande, ésta radica en un grupo
de instrucciones que fueron agregadas al 80186 y al 80188. La diferencia entre el 8086 y el 8088, lo mismo que
entre el 80186 y el 80188, es el modelo de memoria que usan ambos procesadores. El 8088 y el 80188 están
diseñados como microprocesadores de 8 bits por lo que el modo de acceso a la memoria es ligeramente distinto
pero compatible con el 8086 y el 80186. Esto se verá con más detalle en un tema posterior.
Junto con todo lo visto anteriormente, y como se mencionó anteriormente, uno de los componentes que
caracterizan los computadores personales es su sistema operativo. Una PC puede correr varios sistemas
operativos: CP/M, CP/M-86, XENIX, Windows, PC-DOS, y MS-DOS. Lo que los define es la forma en que están
integrados sus servicios y la forma en que se accesa a ellos. Esto es precisamente lo que el linker debe enlazar y
resolver. Aquí nos enfocaremos exclusivamente en el sistema operativo MS-DOS, y lo que se mencione aquí será
valido para las versiones 3.0 y superiores. Este sistema operativo está organizado de la siguiente manera:
Los servicios, más conocidos como interrupciones o vectores de interrupción, es parte medular de lo que
es MS-DOS, y no son más que rutinas definidas por MS-DOS y el BIOS, ubicadas a partir de una localidad de
memoria específica. La manera de acceder a estas rutinas y a los servicios que ofrecen es mediante una
instrucción que permite ejecutar una interrupción. MS-DOS proporciona dos módulos: IBMBIO.COM (provee una
interfaz de bajo nivel para el BIOS) e IBMDOS.COM (contiene un manejador de archivos y servicios para manejo
de registros). En equipos compatibles estos archivos tienen los nombres IO.SYS y MSDOS.SYS,
respectivamente. El acceso a los servicios del computador se realiza de la siguiente manera:
Existen varios ensambladores disponibles para ambiente MS-DOS: el IBM Macro Assembler, el Turbo
Assembler de Borland, el Turbo Editassm de Speedware, por citar algunos. Una breve descripción de cada uno
se proporciona a continuación.
Macro Ensamblador IBM: Está integrado por un ensamblador y un macroensamblador. En gran medida
su funcionamiento y forma de invocarlo es sumamente similar al de Microsoft. Su forma de uso consiste en
generar un archivo fuente en código ASCII, se procede a generar un programa objeto que es ligado y se genera
un programa .EXE. Opcionalmente puede recubrirse a la utilería EXE2BIN de MS-DOS para transformarlo a
.COM. Es capaz de generar un listado con información del proceso de ensamble y referencias cruzadas.
Turbo Editassm: Este es desarrollado por Speddware, Inc., y consiste de un ambiente integrado que
incluye un editor y utilerías para el proceso de ensamble y depuración. Es capaz de realizar el ensamble línea a
línea, conforme se introducen los mnemónicos, y permite revisar listas de referencias cruzadas y contenido de los
registros. Este ensamblador trabaja con tablas en memoria, por lo que la generación del código ejecutable no
implica la invocación explícita del ligador por parte del programador. Adicionalmente permite la generación de
listados de mensajes e información de cada etapa del proceso y la capacidad de creación de archivos de código
objeto.
Turbo Assembler (De Borland Intl): es muy superior al Turbo Editassm. Trabaja de la misma forma,
pero proporciona una interfaz mucho más fácil de usar y un mayor conjunto de utilerías y servicios.
En lo que se refiere a las presentes notas, nos enfocaremos al Microsoft Macro Assembler v4.0. Los
programas ejemplo han sido desarrollados con éste y está garantizado su funcionamiento. Estos mismos
programas posiblemente funcionen con otros ensambladores sin cambios o con cambios mínimos cuando utilizan
directivas o pseudoinstrucciones. Realmente la diferencia entre los ensambladores radica en la forma de generar
el código y en las directivas con que cuente, aunque estas diferencias son mínimas. El código ensamblador no
cambia puesto que los microprocesadores con los que se va a trabajar son comunes. Así, todos los programas
que se creen con un ensamblador en particular podrán ser ensamblados en otro, cambiando las pseudo-
operaciones no reconocidas por el equivalente indicado en el manual de referencia del paquete empleado.
Los programas que componen el Macro Ensamblador Microsoft v4.0 son los siguientes:
Programa Descripción
MASM.EXE Microsoft Macro Assembler
SYMDEB.EXE Microsoft Symbolic Debuger Utility
LINK.EXE Microsoft 8086 object linker
MAPSYM.EXE Microsoft Symbol File Generator
CREF.EXE Microsoft Cross-Reference Utility
Manual de Lenguaje Ensamblador
El Microsoft Macro Assembler v4.0 crea código ejecutable para procesadores 8086, 8088, 80186, 80188,
80286, 8087 y 80287. Además es capaz de aprovechar las instrucciones del 80286 en la creación de código
protegido y no protegido. El término macroensamblador es usado para indicar que el ensamblador en cuestión
tiene la capacidad de poder ensamblar programas con facilidad de macro. Una macro es una pseudo-instrucción
que define un conjunto de instrucciones asociadas a un nombre simbólico. Por cada ocurrencia en el código de
esta macro, el ensamblador se encarga de sustituir esa llamada por todas las instrucciones asociadas y, en caso
de existir, se dejan los parámetros con los que se estaba llamando la macro y no con los que había sido
definida. Es importante señalar que no se deja una llamada, como a una subrutina o procedimiento, sino que se
incorporan todas las instrucciones que definen a la macro.1
6. SISTEMAS NUMÉRICOS
Comencemos por los sistemas de numeración que más utilizaremos al programar. El básico va a ser el
sistema hexadecimal, aunque debemos de explicar antes el binario, el sistema de numeración que utiliza el
ordenador. Los números que conocemos están escritos en base 10. Esto significa que tenemos, desde el 0 hasta
el 9, diez símbolos para representar cada cifra. Es decir, cada cifra ir de 0 a 9, y al superar el valor "9", cambiar a
0 y sumar uno a su cifra de la izquierda: 9+1: 10.
El sistema binario utiliza tan sólo dos símbolos, el "0" y el "1". Imaginemos que tenemos el número binario
"0". Al sumarle una unidad, este número binario cambiar a "1". Sin embargo, si volvemos a añadirle otra unidad,
a este número en formato binario ser el "10" (aumenta la cifra a la izquierda, que era 0, y la anterior toma el valor
mínimo).
Sumemos ahora otra unidad: el aspecto del número ser "11" (tres en decimal). Y podríamos seguir:
Esto nos permite establecer un sistema bastante sencillo de conversión del binario al decimal;
etc.,...
Y así continuaríamos, aumentando el número al que se eleva 2. Traduzcamos entonces el número binario
'10110111'
De todos modos, esta transformación la he puesto simplemente para que se comprenda con más claridad
cómo funcionan los números binarios. Es mucho más aconsejable el uso de una calculadora científica que
permita realizar conversiones entre decimales, hexadecimales y binarios. Se hace su uso ya casi imprescindible
al programar. La razón del uso de los números binarios es sencilla. Es lo que entiende el ordenador, ya que
interpreta diferencias de voltaje como activado (1) o desactivado (0), aunque no detallaré esto. Cada byte de
información está compuesto por ocho dígitos binarios, y a cada cifra se le llama bit. El número utilizado en el
ejemplo, el 10110111, sería un byte, y cada una de sus ocho cifras, un bit. Y a partir de ahora, cuando escriba un
número binario, lo haré con la notación usual, con una "b" al final del número (ej: 10010101b)
Hex Dec
1 --> 1
2 --> 2
3 --> 3
4 --> 4
5 --> 5
6 --> 6
7 --> 7
8 --> 8
9 --> 9
A --> 10
B --> 11
C --> 12
D --> 13
E --> 14
F --> 15
10 --> 16
11 --> 17
Etc,...
Como vemos, este sistema nos planteas bastantes problemas para la conversión, una calculadora
científica será casi imprescindible para realizar esta operación.
Por que utilizar este sistema? Bien sencillo. Volvamos al byte, y traduzcamos su valor más alto,
"11111111". Resulta ser 256. Ahora pasemos esta cifra al sistema hexadecimal, y nos resultará "FF". Obtenemos
un número más comprensible que el binario (difícil de recordar), y ante todo mucho más compacto, en el que dos
cifras nos representaran cada byte. Podremos además traducir fácilmente el binario a hexadecimal con esta tabla;
cada cuatro cifras binarias pueden traducirse al hexadecimal:
Manual de Lenguaje Ensamblador
Binario Hexadecimal
0000 0
0001 1
0010 2
0011 3
0100 4
0101 5
0110 6
0111 7
1000 8
1001 9
1010 A
1011 B
1100 C
1101 D
1110 E
1111 F
Por ejemplo, el número binario:
1111 0011 1010 1110
En hexadecimal sería:
1111 0011 1010 1110
F 3 A E
Para referirnos a un número hexadecimal sin especificarlo, usaremos la notación que se suele usar al
programar, con un 0 al principio (necesario cuando hay letras) y una h al final, por ejemplo, el número anterior
sería 0F3AEh
7. OPERACIONES CON BYTES
Hay cuatro operaciones básicas que se pueden realizar con un número binario, y coinciden con
operaciones de la lógica matemática, con lo que cualquiera que la haya estudiado tendrá cierta ventaja para
entenderla. Para explicarlas, llamar‚ al valor 0 resultado "falso", y al valor 1 "verdadero". Las operaciones son
AND, OR, XOR y NOT
7.1. AND
Es un 'y' lógico. Se realiza entre dos cifras binarias confrontando cada cifra con su correspondiente, y el
resultado será "1" si las dos son verdaderas (si las dos valen "1”), y "0" (falso) en el resto de los casos.
AND
1er. numero 2do. numero Resultado
1 1 1
1 0 0
0 1 0
0 0 0
Vuelvo a la lógica para explicarlo más claramente: Imaginemos la frase: "El hombre es un mamífero y
camina erguido". El hecho de que el hombre sea un mamífero es cierto (1), y el de que camine erguido, otro (1).
Por lo tanto, al unirlos mediante una conjunción (‘y’ o ‘AND’), resulta que ya que se dan las dos, la oración es
verdadera.
Pongamos un ejemplo más complejo, queremos realizar un AND lógico entre dos bytes:
11011000 AND 01101001
Cuando coinciden dos valores de "verdad", el resultado es "verdad", si uno es falso, el resultado es "falso"
(no es verdad que "El hombre es un mamífero y respira debajo del agua”), y si los dos son falsos, el resultado es
falso (no es cierto que "El hombre es un ave y respira debajo del agua”)
7.2. OR
El "o" lógico. El resultado es "verdadero" cuando al menos uno de los factores es verdadero. O sea, es
"1" cuando al menos uno de los dos factores es "1". Sería como la frase "Voy a buscar el peine o la caja de
bombones", donde que uno sea cierto no significa que el otro no lo sea; es cierta la frase, es verdadera mientras
uno de los términos sean verdaderos.
Manual de Lenguaje Ensamblador
Manual de Lenguaje Ensamblador
Manual de Lenguaje Ensamblador
Ahora se desea más memoria que 64 Kb, y para esto se poseen los otros dos bytes. Para formar la
dirección completa, se toman los 16 bits del registro de segmento y se sitúan en los 16 bits superiores de la
dirección de 20 bits, dejando los otros cuatro a cero. Vamos, como si se añade cuatro ceros a la derecha, y se
suma a este valor de 20 bits, el Offset, da el resultado de la dirección real de memoria
Sea el valor de Segmento 0Ah (o decir, 10 decimal o 1010b, en binario). Y el del Offset que va a valer (en
binario) 01011111 00001010.
0000 0000 0000 1010 0000 (segmento multiplicado*16, con 4 ceros mas)
+ 0101 1111 0000 1010 (el offset)
--------------------------------------
0000 0101 1111 1010 1010
Y esta sería la dirección *real* de memoria (05FAAh o 24490 Dec). Como puedes observar, y como
curiosidad final, distintos segments y offsets especifican direcciones de memoria distintas; por ejemplo, los pares
0040h: 0000 (donde el primero es el Segment y el segundo el Offset, así lo tomaremos a partir de ahora), son
iguales que 0000:0400h, y los dos se referirían a la misma posición de memoria física, la 0400h o 1024d. Espero
que haya quedado claro, aunque sea simplemente tener una ligera idea. Lo próximo serán los registros, y (y
ahora me pongo como los del Pcmanía cuando hablan de Windoze95) podremos empezar en serio con nuestro
lenguaje favorito X-)
8. JUEGO DE REGISTROS
Quizá alguno de vosotros se está preguntando a estas alturas: ¨ Y eso del Segment y Offset, dónde se
guarda, que indica al ordenador esos sitios en memoria, que indica al ordenador en que punto de la memoria
esta y que tiene que ejecutar? Pues bien, para esto y mucho mas sirven los registros.
Se trata de una serie de "variables", que contienen información que puede ser cambiada.
Comenzaré, al contrario que todos los libros, por los de segmento y offset actual: CS e IP.
El registro CS es una variable de un tamaño de dos bytes. Contiene el Segmento actual en que se
encuentra el programa. IP, es la variable, de dos bytes también, que contiene el Offset actual. Esto significa, el
ordenador va interpretando las secuencias de bytes, pero necesita "algo" que le indique donde tiene que leer. La
combinación CS: IP (tal y como me referí antes en lo de segments & Offsets) contiene la dirección en la que el
ordenador est interpretando información *en el momento*. O sea, indica la dirección de la próxima instrucción
que se va a ejecutar.
El registro DS y el registro ES también sirven para guardar direcciones de Segmentos, y también son
variables de dos bytes, ser n utilizada para por ejemplo mover datos en memoria, imprimir cadenas, bueno, un
etcétera largísimo. Digamos que son "punteros", que apuntan a cierta zona de memoria (siempre combinado con
otro que haga de Offset, claro).
El registro SS apunta a la pila, y el SP es el que contiene el offset de la pila, pero esto lo explicaré mas
adelante.
Luego tenemos una serie de registros que utilizaremos mas comúnmente: AX, BX, CX y DX.
Todas ocupan dos bytes, y se pueden utilizar divididas en dos partes de longitud un byte, cambiando de
nombre. AX se divide en AH y AL, BX en BH y BL, CX en CH y CL y DX en DH y DL. La 'H' se refiere a High en
inglés, alto (de mayor valor), y la 'l' a Low (de menor valor). Lo ilustro un poquillo:
AX
|-----------------------------|
11010110 10111000
AH AL
AX se suele utilizar como propósito general, indica función a las interrupciones, etc., y es el más flexible,
ya que ser el único que permita multiplicaciones y divisiones. Se denomina a veces acumulador.
BX nos servirá mucho como "handler", para abrir/cerrar archivos, etc., y como registro de propósito
general al igual que AX, CX y DX. CX se suele usar como contador. DX suele ser el puntero, señalando haciendo
el papel de Offset lugares en memoria (suele combinarse con DS en la forma DS: DX).
Manual de Lenguaje Ensamblador
Y nos quedan ya sólo tres registros, BP, SI y DI, que son también punteros. SI y DI los utilizaremos a
menudo para copiar bytes de un lado a otro, etc. Ni que decir que, como el resto de registros, contienen dos
bytes. Igual sucede con BP, de otros dos bytes de tamaño.
Manual de Lenguaje Ensamblador
PM Timers: para controlar el tiempo que debe permanecer inactivo el ordenador ( System) o el disco
duro (HDD) antes de que se active el ahorro de energía. Existen 3 grados de ahorro de energía:
o Doze: reduce la velocidad de la CPU (el microprocesador).
o Standby: reduce la actividad de todo el ordenador.
o Suspend: reduce al mínimo la actividad del ordenador; sólo debe utilizarse con CPUs tipo SL,
como son la mayoría de los 486 rápidos y superiores.
PM Events: una larga serie de eventos o sucesos que deben ser controlados para saber si el ordenador
está inactivo o trabajando. Es habitual no controlar ( Disable) la actividad de la IRQ8 (reloj de la BIOS), ya
que rara vez se la puede considerar como totalmente inactiva.
CPU Fan Off in Suspend: si el ventilador de la CPU va conectado a la placa base, lo apaga cuando el
equipo está en suspenso, ya que en ese momento la CPU está prácticamente parada.
Modem Wake Up: activa el equipo cuando se detecta una llamada entrante en el módem. Necesita que
el módem soporte esta característica y que esté conectado a la placa base mediante un cable especial.
LAN Wake Up: igual que la anterior, pero para la tarjeta de red. También necesita estar conectado a la
placa base mediante un cable.
Un menú lleno de opciones complicadas (en esta página pocas no lo son), de la clase que sería
deseable no tener que alterar nunca; ése es mi consejo, escoja Auto todas las veces que pueda, pero si tiene
algún conflicto entre dispositivos (misma IRQ, sobre todo)... Probablemente se pregunte qué tiene que ver PNP
con PCI; pues bien, la gran mayoría de dispositivos PCI soportan PNP, a diferencia de las tarjetas ISA, mucho
más problemáticas. Por eso, si su placa no tiene slots PCI (como las primeras para 486), no se extrañe si este
menú no aparece.
Ah, para el que no lo conozca, el Plug&Play , PNP o P&P, es una tecnología que facilita la conexión de
dispositivos, ya que se supone que basta con enchufar y listo. Claro que no todos los dispositivos son PNP ni es
una tecnología perfecta, si fuera así este menú no existiría...
PNP OS Installed: informa al sistema de si hay un sistema operativo PNP instalado , es decir, uno que
soporta Plug&Play, como Windows 95 (o eso dicen que hace...), en cuyo caso pasa a éste el control de
los dispositivos PNP. De cualquier forma, muchas veces lo que esta casilla indique no afecta al correcto
funcionamiento del sistema.
Resources Controlled by: pues eso, recursos controlados bien manual, bien automáticamente. De
nuevo, muchas veces es indiferente una u otra opción... siempre que no haya problemas, claro.
IRQx/DMAx assigned to: una lista de las interrupciones (IRQs) y canales DMA que podemos asignar
manualmente, bien a tarjetas PCI/ISA PnP (compatibles con PNP), bien a tarjetas Legacy ISA (tarjetas
ISA no PNP, que son las más conflictivas). Necesitaremos conocer los valores de IRQ y/o DMA a
reservar, que vendrán en la documentación del dispositivo problemático.
PCI IDE IRQ Map to: algo que muy probablemente no necesite cambiar nunca, ya que sólo afecta a
controladoras IDE no integradas en la placa base, sino en forma de tarjeta, que no sean PNP.
Assign IRQ to USB: pues eso, si el puerto USB debe tener una interrupción asignada o no. Si no tiene
ningún dispositivo USB conectado (¿y quién los tiene hoy en día?) puede liberar esa IRQ para otros
usos; suele ser la misma interrupción que para uno de los slots PCI o ISA.
Manual de Lenguaje Ensamblador
9.5. AUTOCONFIGURACIÓN DE LA BIOS
Este apartado comprende diversas opciones que se proporcionan para facilitar la configuración de la
BIOS, de las cuales las más comunes son:
LOAD BIOS DEFAULTS: carga una serie de valores por defecto con poca o nula optimización,
generalmente útiles para volver a una posición de partida segura y resolver problemas observados al
arrancar.
LOAD SYSTEM DEFAULTS: una opción cuyos efectos varían de unas BIOS a otras. En unos casos
carga unos valores por defecto seguros (como LOAD BIOS DEFAULTS), en otros carga unos valores ya
optimizados para conseguir un rendimiento adecuado, o incluso puede servir para cargar la última serie
de valores guardados por el usuario.
LOAD TURBO DEFAULTS: carga los valores que estima óptimos para incrementar el rendimiento.
En cualquier caso, debe tenerse en cuenta que los cambios no suelen ser guardados automáticamente, sino que
deben confirmarse al salir de la BIOS.
Las BIOS pueden hacer todavía más cosas, dependiendo del modelo en concreto; algunas de las más
usuales están a continuación.
Esta opción permite detectar los discos duros que están conectados al sistema, así como su
configuración. Resulta muy útil para simplificar la tarea de instalar un disco nuevo, así como cuando los datos del
disco no están completos o no parecen funcionar en nuestra BIOS.
Manual de Lenguaje Ensamblador
Su uso es sencillísimo: se entra en este menú y se va detectando cada uno de los cuatro posibles
dispositivos IDE. Apunte las opciones que le aparezcan y pruebe a usarlas; recuerde usar el modo LBA para
discos de más de 528 MB. Tenga en cuenta que muchas veces sólo por entrar en esta utilidad se alteran
automáticamente los valores de configuración del disco, así que después de salir de ella compruebe si los
cambios corresponden a los que quería realizar.
Es decir, por una clave de acceso en forma de palabra secreta que sólo conozca usted. Tenga en cuenta
que si la olvida se verá en graves problemas, hasta el punto de tener que borrar toda la BIOS para poder volver a
usar el ordenador, así que apúntela en algún lugar seguro.
Se suele poder seleccionar, bien en un menú específico o en las BIOS Features, entre tener que
introducir la clave cada vez que se arranca el ordenador o sólo cuando se van a cambiar datos de la BIOS. Lo
primero es el método ideal para seguridad, y además es gratis; lo segundo es útil cuando gente inexperta pero
manazas tiene acceso al ordenador (por ejemplo, su sobrinito el tocalotodo).
O, en inglés, HDD Low Level Format . Se trata de un formateo mucho más intenso que el normal; no
sólo elimina los datos, sino que reorganiza la propia estructura del disco. Generalmente sólo debe usarse cuando
el disco está fallando muy a menudo o ha sido infectado por un virus tremendamente resistente, y aun así no
resulta recomendable.
Si será duro, que realizarlo ¡suele ser motivo de pérdida de la garantía del disco duro! En fin, si se atreve,
ármese con los datos de configuración del disco (cilindros, cabezas...) y rece por no tener que interrumpirlo por
nada del mundo, cortes de luz incluidos.
Antivirus
No, no crea que con esta opción podrá ahorrarse el comprar uno de esos programas antivirus tan
tristemente necesarios en los PC. En realidad, lo único que suele hacer esta opción (que en ocasiones tiene un
menú propio y en otras se engloba bajo el Standard Setup, tal vez con el nombre de Virus Warning) es no
permitir que se escriba sobre la tabla de particiones o el sector de arranque del disco duro, bien sólo durante el
arranque o en cualquier momento, dependiendo del modelo concreto de BIOS.
La idea es impedir que un virus destroce el disco duro sin darle oportunidad a cargar un disquete de
arranque con un antivirus para desinfectar el sistema; no impedirá la infección, pero es una medida más de
seguridad y gratis. Por cierto, puede ser necesario deshabilitar esta opción durante la instalación del sistema
operativo o al formatear el disco duro, no sea que la BIOS crea que se trata de un ataque viral.
Pues es sencillo, pero revisémoslo para los que no entiendan inglés en absoluto. Generalmente existen dos
opciones:
Save and Exit Setup: o bien Write to CMOS and Exit o algo similar; pues eso, grabar los cambios y
salir, con lo cual se reinicia el equipo. Debería pedirle confirmación, en forma de " Y/N?" (Yes o No).
Manual de Lenguaje Ensamblador
Exit Without Saving: o Do Not Write to CMOS and Exit o Discard Changes and Exit o similar; lo
contrario, salir sin grabar los cambios. También debería pedir confirmación.
La BIOS maneja temas tan críticos como el soporte de uno u otro microprocesador; además, como programa
que es, no está exenta de fallos y se revisa periódicamente para eliminarlos o añadir nuevas funciones.
Antiguamente, la única forma de actualizar una BIOS era extraer el chip de BIOS y sustituirlo por otro, lo cual
no se lo recomiendo a nadie, tanto por las posibles incompatibilidades como por lo delicado y caro de la
operación.
Modernamente han aparecido BIOS que pueden modificarse con un simple programa software; se las
denomina Flash-BIOS, y no son un invento desdeñable. Lo que es más, la existencia de una de estas BIOS o
no debería ser argumento de peso a la hora de comprar una placa base, al menos entre los manitas informáticos.
Tenga en cuenta que mantener un registro de BIOS actualizadas es un servicio que sólo ofrecen los grandes
fabricantes de placas base... y aun así no siempre se puede encontrar la necesaria actualización.
Vaya a Actualizar la BIOS para conocer los pasos concretos a seguir; eso sí, le aviso de que deberá
asegurarse de que la operación de actualización no se interrumpe por nada del mundo, así que nada de hacer
multitarea, meterle prisa o tocar el teclado mientras se actualiza; son unos segundos, pero de importancia crítica.
¿Adivina qué día del año va a saltar la luz? Efectivamente, justo cuando esté actualizando su BIOS.
Como dijimos, la pila conserva los datos de la BIOS cuando el ordenador está apagado. Dura
mucho (unos tres años de media), pero al final se agota. Para cambiarla, apunte todos los datos
de la BIOS, desconecte todo y sustitúyala por una igual, o bien por un paquete externo de baterías
que se conectan a un jumper (un microinterruptor) de la placa base; ambas cosas las debería
encontrar en tiendas de electrónica.
Después conecte todo, arranque el ordenador, entre en la BIOS y reintroduzca todos los datos,
ya que se habrán borrado. ¿Se imagina si no tuviera una copia escrita qué aventura? A mí me pasó hace años, y
no me quedó más remedio que aprender sobre BIOS... bueno, no hay mal que por bien no venga.
No, no se trata de hacer ilegalidades en ordenadores ajenos, se trata de saber qué hacer si sufre una
repentina amnesia o si la BIOS trae una password ya introducida; por ejemplo, una BIOS con la que luché una
vez tenía como password por defecto "AMI", el nombre de su fabricante. Además, en ordenadores de segunda
mano pasa no pocas veces.
Los métodos son pocos; realmente sólo uno, y muy radical: borrar la BIOS entera. Para ello existen tres
formas:
Manual de Lenguaje Ensamblador
Por software tipo "hacker": algunos programas se especializan en destrozar BIOS, y si tiene suerte
quizá incluso le digan cuál es la password sin tener que borrar la BIOS. Busque en los "bajos fondos" de
Internet... ¡y tenga cuidado con estos programas y con los posibles virus!
Mediante un jumper en la placa base: en algunas, no todas, existe un jumper que al cerrarse (al
conectarse ambas patillas), y tras unos minutos de espera, permite borrar la BIOS limpiamente.
Desconectando la pila: drástico, brutal, pero absolutamente efectivo.
En fin, sea como sea, recuerde tener su copia en papel de la BIOS y de la password para no tener que llegar
a estos extremos.
Para comenzar veamos un pequeño ejemplo que ilustra el formato del programa fuente. Este ejemplo
está completamente desarrollado en lenguaje ensamblador que usa servicios o funciones de MS-DOS (system
calls) para imprimir el mensaje Hola mundo!! En pantalla.
; HOLA.ASM
; Programa clásico de ejemplo. Despliega una leyenda en pantalla.
STACK SEGMENT STACK ; Segmento de pila
DW 64 DUP (?) ; Define espacio en la pila
STACK ENDS
DATA SEGMENT ; Segmento de datos
SALUDO DB "Hola mundo!!",13,10,"$" ; Cadena
DATA ENDS
CODE SEGMENT ; Segmento de Codigo
Manual de Lenguaje Ensamblador
ASSUME CS: CODE, DS: DATA, SS: STACK
INICIO: ; Punto de entrada al programa
MOV AX,DATA ; Pone direccion en AX
MOV DS, AX ; Pone la direccion en los registros
MOV DX, OFFSET SALUDO ; Obtiene direccion del mensaje
MOV AH, 09H ; Funcion: Visualizar cadena
INT 21H ; Servicio: Funciones alto nivel DOS
MOV AH, 4CH ; Funcion: Terminar
INT 21H
CODE ENDS
END INICIO ; Marca fin y define INICIO
2.- La variable SALUDO en el segmento DATA, define la cadena a ser desplegada. El signo del dólar al
final de la cadena (denominado centinela) es requerido por la función de visualización de la cadena de MS-DOS.
La cadena incluye los códigos para carriage-return y line-feed.
3.- La etiqueta START en el segmento de código marca el inicio de las instrucciones del programa.
4.- La declaración DW en el segmento de pila define el espacio para ser usado por el stack del
programa.
5.- La declaración ASSUME indica que registros de segmento se asociarán con las etiquetas declaradas
en las definiciones de segmentos.
6.- Las primeras dos instrucciones cargan la dirección del segmento de datos en el registro DS. Estas
instrucciones no son necesarias para los segmentos de código y stack puesto que la dirección del segmento de
código siempre es cargada en el registro CS y la dirección de la declaración del stack segment es
automáticamente cargada en el registro SS.
7.- Las últimas dos instrucciones del segmento CODE usa la función 4CH de MS-DOS para regresar el
control al sistema operativo. Existen muchas otras formas de hacer esto, pero ésta es la más recomendada.
8.- La directiva END indica el final del código fuente y especifica a START como punto de arranque.
De acuerdo a las convenciones y notación seguidas en el manual del Microsoft Macro Assembler, y que
usaremos nosotros también, tenemos:
Notación Significado
Negritas Comandos, símbolos y parámetros a ser usados como se muestra.
Itálicas Todo aquello que debe ser reemplazado por el usuario
Manual de Lenguaje Ensamblador
Indican un parámetro opcional
,,, Denota un parámetros que puede repetirse varias veces
¦ Separa dos valores mutuamente excluyentes
letra chica Usada para ejemplos. Código y lo que aparece en pantalla.
Cada programa en lenguaje ensamblador es creado a partir de un archivo fuente de código ensamblador.
Estos son archivos de texto que contienen todas las declaraciones de datos e instrucciones que componen al
programa y que se agrupan en áreas o secciones, cada una con un propósito especial. Las sentencias en
ensamblador tienen la siguiente sintaxis:
En cuanto a la estructura, todos los archivos fuente tienen la misma forma: cero o más segmentos de
programa seguidos por una directiva END. No hay una regla sobre la estructura u orden que deben seguir las
diversas secciones o áreas en la creación del código fuente de un programa en ensamblador. Sin embargo la
mayoría de los programas tiene un segmento de datos, un segmento de código y un segmento de stack, los
cuales pueden ser puestos en cualquier lugar.
Para los números reales tenemos al designador R, que sólo puede ser usado con números
hexadecimales de 8, 16, ó 20 dígitos de la forma digitosR. También puede usarse una de las directivas DD, DQ, y
DT con el formato [+¦-] digitos.digitos [E [+¦-] dígitos]. Las cadenas de carácter y constantes alfanuméricas son
formadas como ´caracteres´ o "caracteres”. Para referencias simbólicas se utilizan cadenas especiales
denominadas nombres. Los nombres son cadenas de caracteres que no se entrecomillan y que deben comenzar
con una A..Z ¦ a..z ¦ _ ¦ $ ¦ @ los caracteres restantes pueden ser cualquiera de los permitidos, y solamente los
31 primeros caracteres son reconocidos.
3.- DIRECTIVAS.
Conjunto de instrucciones: Dentro de las directivas más importantes, tenemos las que establecen
el conjunto de instrucciones a soportar para un microprocesador en especial:
.8086(default) Activa las instrucciones para el 8086 y 8088 e inhibe las del 80186 y 80286.
.8087(default) Activa instrucciones para el 8087 y desactiva las del 80287.
.186 Activa las instrucciones del 80186.
.286c Activa instrucciones del 80286 en modo no protegido.
.289p Activa instrucciones del 80286 en modo protegido y no protegido.
.287 Activa las instrucciones para el 80287
Declaración de segmentos: En lo que respecta a la estructura del programa tenemos las directivas
SEGMENT y ENDS que marcan el inicio y final de un segmento del programa. Un segmento de programa es
una colección de instrucciones y/o datos cuyas direcciones son todas relativas para el mismo registro de
segmento. Su sintaxis es:
El nombre del segmento es dado por nombre, y debe ser único. Segmentos con el mismo nombre se
tratan como un mismo segmento. Las opciones alineación, combinación, y clase proporcionan información al
LINK sobre cómo ajustar los segmentos. Para alineación tenemos los siguientes valores: byte (usa cualquier byte
de dirección), word (usa cualquier palabra de dirección, 2 bytes/word), para (usa direcciones de párrafos, 16
bytes/parráfo, deafult), y page (usa direcciones de página, 256 bytes/page). Combinación define cómo se
combinarán los segmentos con el mismo nombre. Puede asumir valores de: public (concatena todos los
segmentos en uno solo), stack (igual al anterior, pero con direcciones relativas al registro SS, common (crea
segmentos sobrepuestos colocando el inicio de todos en una misma dirección), memory (indica al LINK tratar los
segmentos igual que MASM con public, at address (direccionamiento relativo a address). clase indica el tipo
de segmento, señalados con cualquier nombre. Cabe señalar que en la definición está permitido el anidar
segmentos, pero no se permite de ninguna manera el sobreponerlos.
Fin de código fuente: Otra directiva importante es la que indica el final de un módulo. Al alcanzarla
el ensamblador ignorará cualquier otra declaración que siga a ésta. Su sintaxis es:
END [expresión]
Asignación de segmentos: La directiva ASSUME permite indicar cuales serán los valores por
default que asimilan los registros de segmento. Existen dos formas de hacer esto:
ASSUME registrosegmento:nombre,,,
ASSUME NOTHING
Nombre:
donde directiva puede ser DB (bytes), DW (palabras), DD (palabra doble), DQ (palabra cuádruplo), DT
(diez bytes). También pueden usarse las directivas LABEL (crea etiquetas de instrucciones o datos), EQU (crea
símbolos de igualdad), y el símbolo = (asigna absolutos) para declarar símbolos. Estos tienen la siguiente
sintaxis:
Nombre = expresión
Nombre EQU expresión
Nombre LABEL tipo
Donde tipo puede ser BYTE, WORD, DWORD, QWORD, TBYTE, NEAR, FAR.
El juego completo de instrucciones reconocidas por los procesadores intel 8086 a 80286, junto con los
coprocesadores 8087 y 80287, se enlistan en el apéndice E. Como puede verse en dicho apéndice, la mayoría
de las instrucciones requieren algunos operandos o expresiones para trabajar, y lo cual es válido también para
las directivas. Los operandos representan valores, registros o localidades de memoria a ser accesadas de
alguna manera. Las expresiones combinan operandos y operadores aritméticos y lógicos para calcular en valor o
la dirección a acceder.
Constantes: Pueden ser números, cadenas o expresiones que representan un valor fijo. Por ejemplo,
para cargar un registro con valor constante usaríamos la instrucción MOV indicando el registro y el valor que
cargaríamos dicho registro.
mov ax,9
mov al,´c´
mov bx,65535/3
mov cx,count
count sólo será válido si este fue declarado con la directiva EQU.
mov ax,ss:0031h
mov al,data:0
mov bx,DGROUP:block
Relocalizables: Por medio de un símbolo asociado a una dirección de memoria y que puede ser
usado también para llamados.
mov ax, value
call main
mov al,OFFSET dgroup:tabla
mov bx, count
Contador de localización: Usado para indicar la actual localización en el actual segmento durante el
ensamblado. Representado con el símbolo $ y también conocido como centinela.
help DB ´OPCIONES´,13,10
F1 DB ´ F1 salva pantalla´,13,10
.
.
.
F10 DB ´ F10 exit´,13,10,´$
DISTANCIA = $-help
Basados: Un operador basado representa una dirección de memoria relativa a uno de los registros
de base (BP o BX). Su sintaxis es:
Desplazamiento[BP]
Desplazamiento [BX]
[Desplazamiento][BP]
[BP+desplazamiento]
[BP].desplazamiento
[BP]+desplazamiento
En cada caso la dirección efectiva es la suma del desplazamiento y el contenido del registro.
mov ax,[BP]
mov al,[bx]
mov bx,12[bx]
mov bx,fred[bp]
Indexado: Un operador indexado representa una dirección de memoria relativa a uno de los registros
índice (SI o DI). Su sintaxis es:
Manual de Lenguaje Ensamblador
desplazamiento[DI]
desplazamiento[SI]
[desplazamiento][DI]
[DI+desplazamiento]
[DI].desplazamiento
[DI]+desplazamiento
En cada caso la dirección efectiva es la suma del desplazamiento y el contenido del registro.
mov ax,[si]
mov al,[di]
mov bx,12[di]
mov bx,fred[si]
desplazamiento[BP][SI]
desplazamiento[BX][DI]
desplazamiento[BX][SI]
desplazamiento[BP][DI]
[desplazamiento][BP][DI]
[BP+DI+desplazamiento]
[BP+DI].desplazamiento
[DI]+desplazamiento+[BP]
en cada caso la dirección efectiva es la suma del desplazamiento y el contenido del registro.
mov ax,[BP][si]
mov al,[bx+di]
mov bx,12[bp+di]
mov bx,fred[bx][si]
date STRUC
mes DW ?
dia DW ?
aa DW ?
date ENDS
mov ax,actual.dia
mov actual.aa, ´85´
Manual de Lenguaje Ensamblador
Operadores y expresiones: Se cuenta con los siguientes operadores:
Aritméticos:
expresión1 * expresión2
expresión1 / expresión2
expresión1 MOD expresión2
expresión1 + expresión2
expresión1 - expresión2
+ expresión
- expresión
De corrimiento:
Relacionales:
expresión1 EQ expresión2
expresión1 NE expresión2
expresión1 LT expresión2
expresión1 LE expresión2
expresión1 GT expresión2
expresión1 GE expresión2
De bit:
NOT expresión
expresión1 AND expresión2
expresión1 OR expresión2
expresión1 XOR expresión2
De índice:
[expresión1] [expresión2]
ejemplos:
mov al, string[3]
mov string[last],al
mov cx,dgroup:[1] ; igual a mov cx,dgroup:1
De apuntador:
tipo puede ser BYTE ó 1, WORD ó 2, DWORD ó 4, QWORD ó 8, TBYTE ó 10, NEAR ó 0FFFFh, FAR ó
0FFFEh. Ejemplos:
Manual de Lenguaje Ensamblador
call FAR PTR subrout3
mov BYTE ptr [array], 1
add al, BYTE ptr [full_word]
De nombre de campo:
estructura.campo
ejemplos:
inc month.day
mov time.min,0
mov [bx].dest
De propósito especial:
La manera más fácil de construir el programa en módulos, es dividirlo en dos o más partes. Para esto, es
necesario que datos, símbolos, y demás valores de un módulo sean reconocidos por el otro u otros módulos.
Para este tipo de declaraciones globales existen dos directivas:
PUBLIC nombre,,, que hace la variable, etiqueta o símbolo absoluto disponible para todos los
programas.
Manual de Lenguaje Ensamblador
EXTRN nombre:tipo,,, que especifica una variable, etiqueta o símbolo externos identificados por
nombre y tipo (que puede ser BYTE, WORD, DWORD, QWORD, TBYTE, NEAR, FAR, o ABS, éste último para
números absolutos).
El siguiente ejemplo ilustra el uso de las directivas. El primer listado corresponde al módulo principal,
mientras que el segundo al módulo que contiene una rutina. Ambos módulos son archivos que se editan por
separado, se ensamblan por separado, pero se ligan juntos.
SUBMODULO: TASK.ASM
NAME task
PUBLIC print
EXTRN exit:near
La declaración de macros se hace a través de las directivas MACRO y ENDM. Su sintaxis es:
Los parámetros son los valores que se substituirán en la macro cada vez que se haga referencia a ésta.
Para la definición de procedimientos se emplean las directivas PROC y ENDP. Su sintaxis es:
nombre PROC [distancia]
sentencias
nombre ENDP
La distancia, que puede ser NEAR (default) o FAR permiten indicar el tipo de acciones a realizar en
brincos y llamados a subrutinas. nombre se puede usar como dirección en llamados o brincos.
6.- INTERRUPCIONES.
Como se mencionó anteriormente la PC esta constituida lógicamente por su BIOS y sistema operativo.
La mayoría de las rutinas que controlan al computador están grabadas en el ROM del BIOS, aunque muchas
rutinas son establecidas por el sistema operativo y se cargan en RAM al momento de encender al computador.
Estas rutinas son denominadas interrupciones y son activadas mediante la instrucción: INT número. Una
interrupción es una operación que invoca la ejecución de una rutina específica que suspende la ejecución del
programa que la llamó, de tal manera que el sistema toma control del computador colocando en el stack el
contenido de los registros CS e IP. El programa suspendido vuelve a activarse cuando termina la ejecución de la
interrupción y son restablecidos los registros salvados. Existen dos razones para ejecutar una interrupción: (1)
intencionalmente como petición para la entrada o salida de datos de un dispositivo, y (2) un error serio y no
intencional, como sobreflujo o división por cero.
El operando de una interrupción indica cúal es la rutina a activar. La dirección de la rutina es localizada
por medio de una tabla que el sistema mantiene a partir de la dirección 0000:0000h. Existen 256 entradas de 4
bytes de longitud, y cada interrupción proporciona varias funciones. Las interrupciones de 00h a 1Fh correponden
al BIOS y de 20h a FFh son del DOS y BASIC. El apéndice F proporciona una lista de las interrupciones para
equipo XT.
Manual de Lenguaje Ensamblador
1.- EDICION.
Los archivos fuente de código ensamblador deben estar en formato ASCII standard. Para esto puede
usarse cualquier editor que permita crear archivos sin formato, e.g. Edlin, Edit, Write, El editor del Turbo Pascal,
Works, Word, WordStar, etcétera. Las declaraciones pueden ser introducidas en mayúsculas y/o minúsculas.
Una buena práctica de programación es poner todas las palabras reservadas (directivas e instrucciones) en
mayúsculas y todo lo del usuario en minúsculas para fines de facilidad de lectura del código. Las sentencias
pueden comenzar en cualquier columna, no pueden tener más de 128 caracteres, no se permiten líneas
múltiples ni códigos de control, y cada línea debe ser terminada con una combinación de line-feed y carriage-
return. Los comentarios se declaran con ; y terminan al final de la línea.
2.- ENSAMBLADO.
El ensamblado se lleva a cabo invocando al MASM. Este puede ser invocado, usando una línea de
comando, de la siguiente manera:
OpcionesPueden ser:
/A: escribe los segmentos en orden alfabético
/S: escribe los segmentos en orden del fuente
/Bnum: fija buffer de tamaño num
/C: especifica un archivo de referencias cruzadas
/L: especifica un listado de ensamble
/D: crea listado del paso 1
/Dsym: define un símbolo que puede usarse en el ensamble
/Ipath: fija path para búscar archivos a incluir
/ML: mantiene sensitividad de letras (mayús./minús) en nombres
/MX: mantiene sensitividad en nombre publicos y externos
/MU: convierte nombres a mayúsculas
/N: suprime tablas en listados
/P: checa por código impuro
/R: crea código para instrucciones de punto flotante
/E: crea código para emular instrucciones de punto flotante
/T: suprime mensajes de ensable exitoso
/V: despliega estadísticas adicionales en pantalla
/X: incluir condicionales falsos en pantalla
/Z: despliega líneas de error en pantalla
si el ; al final se omite es necesario poner todas las comas que se indican. Si no se quiere poner algún
valor basta con dejar la coma.
La otra forma de invocar al ensamblador es sólo tecleando MASM y respondiendo a la información que
se solicita. Para omitir algún valor sólo basta teclear ENTER si dar ningún valor.
3.- LINK.
De la misma forma que el ensamblado, la fase de liga se lleva a cabo con el LINK. Este puede ser
invocado de la misma forma que el MASM. Los parámetros que este requiere son:
donde:
4.- EJECUCION.
Para la ejecución del programa simplemente basta teclear su nombre en el prompt de MS-DOS y teclear
ENTER. Con esto el programa será cargado en memoria y el sistema procederá a ejecutarlo. El proceso
completo para poder crear un programa ejecutable con el Microsoft Macro Assembler se muestra abajo.
C:\DATA\PROGRAMS\ASM>masm main
Microsoft (R) Macro Assembler Version 4.00
Copyright (C) Microsoft Corp 1981, 1983, 1984, 1985. All rights reserved.
0 Warning Errors
0 Severe Errors
C:\DATA\PROGRAMS\ASM>masm task
Microsoft (R) Macro Assembler Version 4.00
Copyright (C) Microsoft Corp 1981, 1983, 1984, 1985. All rights reserved.
0 Warning Errors
0 Severe Errors
C:\DATA\PROGRAMS\ASM>link main+task
C:\DATA\PROGRAMS\ASM>main
Entrando a un submodulo....
.......saliendo del submodulo.
C:\DATA\PROGRAMS\ASM>
5.- DEPURACION.
Para la depuración de un programa en ensamblador tenemos disponibles dos herramientas. Por un lado
tenemos el debuger que nos proporciona MS-DOS (DEBUG.EXE) y por otro lado tenemos el que nos
proporciona Microsoft (SYMDEB.EXE). Este último trabaja igual que el de MS-DOS pero nos proporciona
muchas ventajas más. Una de ellas es la facilidad de desplegar el código fuente correspondiente a la instrucción
que se esta ejecutando (si el programa ejecutable fue ensamblado o compilado con un ensamblador o
compilador compatible), nos permite ejecutar comandos del S.O. y nos permite obtener información de las
interrupciones de manera simbólica.
Con el depurador podemos revisar paso por paso la ejecución de nuestro programa, revisar como va
modificándose el contenido de los registros hasta ubicar donde está el "bicho" y aplastarlo. Imagino que el
nombre se debe a que los primeros desperfectos en las primeras máquinas de cómputo electrónicas se debía
generalmente a la presencia de cucarachas u otros insectos en los circuitos de tubos. El debug de DOS
permite, entre otras cosas, editar archivos ejecutables y hasta escribir programas en lenguaje ensamblador.
DEBUG, como muchos otros programas auxiliares de la programación, trabaja con el sistema
hexadecimal. Para ejecutar DEBUG escribimos DEBUG en el puntero de DOS o en el cuadro de ejecución
(Inicio-Ejecutar) de Windows. Aparecerá sólo un guión que es el apuntador del DEBUG.
A: assemble (ensamblar)
C: compare (comparar)
D: display (desplegar)
E: enter (ingresar)
F: full (llenar)
G: go (ir)
H: Hexadecimal
I: in (entrada)
L: load (cargar)
M: move (mover)
N: name (nombrar)
O: out (salida)
P: proceed (proceder)
Q: quit (quitar)
R: register (registro)
S: search (buscar)
T: trace (trazar o rastrear)
U: unassemble (desensamblar)
W: write (escribir)
Ahora no las usaremos todas. Antes de escribir un programa con DEBUG, primero revisemos el que
escribimos al comienzo. Cargamos el programa en el DEBUG:
C:\tasm\works\primer>DEBUG primer.exe
Manual de Lenguaje Ensamblador
-t
Ingresemos de nuevo t:
-t
La siguiente instrucción mueve a DX la dirección de nuestra cadena. Habíamos escrito "LEA DX, string";
DEBUG lo ha interpretado como "mover a DX lo que está 4 bytes por encima del inicio del segmento de datos".
Si se quiere verificar escribimos: D DS:4, desplegar lo que está 4 bytes después del inicio del segmento
de datos:
-d ds:4
0E00:0000 48 6F 6C 61-20 67 65 6E 74 65 21 24 Hola gente!$
0E00:0010 C7 06 0E 96 3E 2B 2E C7-06 10 96 3D 3B E8 83 09 ....>+.....=;...
0E00:0020 73 13 B8 FF FF 53 26 8B-1D 26 3A 0F 73 03 B8 02 s....S&..&:.s...
Sigamos trazando:
-t
Vemos ahora que "AX=0900", que confirma que hemos movido 9 a la parte alta de AX. Se va a
ejecutar la interrupción 21h, servicio 9; recuérdese que anteriormente habíamos escrito "int 33". DEBUG
trabaja en hexadecimal, así que despliega la versión hexadecimal de lo que escribamos. Para trazar por
encima de la interrupción, no es bueno usar T, ya que esto nos llevará a la rutina de la interrupción. Así que
mejor ejecutamos P:
-p
Hola gente!
AX=0924 BX=0000 CX=0020 DX=0004 SP=0040 BP=0000 SI=0000 DI=0000
DS=0E00 ES=0DEF SS=0E01 CS=0DFF IP=000C NV UP EI PL NZ NA PO NC
0DFF:000C B410 MOV AH,10
Esto despliega nuestra cadena en la cónsola. Observa que todos los registros se han mantenido iguales
después de la interrupción, excepto AX; vemos que en la parte baja de AX, que es AL, aparece un 24h. Se trata
del caracter "$". Quiere decir que el servicio 9 de la interrupción 21h usa el registro AL para revisar la cadena.
Ahora moveremos 16 a AH. DEBUG lo presenta como "MOV AH, 10"; 10h = 16 decimal.
Hacemos T:
Se va a ejecutar el servicio 16 o 10h de la interrupción 22 o 16h. Lo que hace este servicio es esperar
a que el usuario pulse un tecla, cuyo código devuelve en AH el código de rastreo y en AL el código de la tecla.
Para teclas de función extendida (F1 - F12) más importante es el código de rastreo, ya que como código de
tecla se pasará 00, para las teclas F1-F12, E0h para para otras teclas de control como Inicio o RePag. Hacemos
P y se ejecuta nuestra pausa; pulsamos cualquier tecla, en mi caso "enter":
-p
Código de rastreo en AH=1Ch y código de tecla en AL=0Dh. Ahora vamos a salir. Devolvemos el valor
FALSE (=0) en AX a DOS. Un simple gesto de educación.
Por último regresamos a DOS. La instrucción RETF es una operación ceroaria (sin operandos) que
significa RETURN FAR, regresar lejos. Recordemos que hemos declarado nuestro procedimiento así:
"main" es el nombre del procedimiento; proc es una directiva que indica inicio de una rutina o función
(en ensamblador se llaman procedimientos a las funciones) y far indica que el procedimiento puede ser
accedido desde otros segmentos. La instrucción RETF nos saca del procedimiento a la línea de órdenes.
Hay otras formas más limpias de salir de un programa, pero con esta basta por ahora. Nota también
que a la derecha de cada instrucción aparece la dirección en memoria, en sistema hexadecimal, de la
instrucción, y luego es seguida por otra cifra en hexadecimal. Ésta última es la versión en hexadecimal del
código de operación de la instrucción. Si no fuera por el sistema hexadecimal, ahí aparecerían una serie de
unos y ceros. Con DEBUG también podemos obtener el código fuente en ensamblador de un fichero ejecutable.
Esto se hace cargando el fichero y ejecutando la orden U, seguida por la dirección a partir de la cual
queremos que se inicie el desensamblado y el número de líneas a desensablar:
-u cs:00
0CE2:0000 B8E30C MOV AX,0CE3
0CE2:0003 8ED8 MOV DS,AX
0CE2:0005 BA0400 MOV DX,0004
2:0008 B409 MOV AH,09
Tememos aquí un desensamblado de nuestro código. También podemos obtener este desensamblado
en un archivo de texto aparte:
Esta orden volcará en prim.asm un desensablado de los primeros 20 (14h) bytes de primer.exe, que
podemos revisar con un editor.
cls
@echo off
@echo U CS:00 13>t_$
@echo.>>t_$
@echo Q>>t_$
2echo.>>t_$
@echo Desensamblando....
DEBUG %1.exe<t_$>%1_.asm
Manual de Lenguaje Ensamblador
EDIT %1_.asm
Luego ejecutamos:
disasm primer
y ya! Podríamos mejorarlo para que desensamble exactamente las instruccciones del segmento de
código; e incluso hacer que nuestro .BAT cambie algunos valores hexadecimales en el .EXE, pero no es este
el lugar para explicarlo.
Escribamos ahora un programa con el DEBUG. Antes una observación sobre el formato de los
ejecutables en DOS. Existen dos formatos: .COM y .EXE. El primero, .COM, es el formato original. En este
formato, todo, código y datos, es puesto en un único segmento cuyo tamaño no debe exeder los 64KB.
En el formato .EXE, que es posterior, se reserva un segmento para datos, uno para código y uno para la
pila.
Nuestro programa fue escrito en formato .EXE. Con el DEBUG se pueden escribir programas en
formato .COM, que son bastante más pequeños.
El programa debe comenzar a ejecutarse en la dirección 256 [100h], ya que los ejecutables de DOS
reservan los primeros 256 bytes para colocar ahí una estructura de datos conocida como PSP, cuando es
cargado en la memoria. El PSP (Program Segment Prefije: Prefijo de Segmento del Programa) contine
información que será utilizada por el cargador de DOS.
-a 100
0DAB:0100 jmp 108
0DAB:0102 db "hola$"
0DAB:0107 nop
0DAB:0108 mov dx,102
0DAB:010B mov ah,9
0DAB:010D int 21
0DAB:010F mov ah,10
0DAB:0111 int 16
0DAB:0113 int 20
0DAB:0115
La primera instrucción ,"jmp 108", es saltar a la localidad 108. Necesitamos un espacio para nuestra
cadena. Como no sabemos el tamaño del programa y no podemos determinar al comienzo donde estará la
cadena si la ponemos al final del programa, la ponemos al comienzo, y para evitar que el programa
comience a ejecutarse en la cadena (lo que daría error) le pasamos por encima. La instrucción jmp 108 tiene
dos bytes, reservamos 6 bytes para la cadena. Nos sobra un byte, así que escribimos "nop" en 107, un
operador ceroario que significa No operación. Vemos que la dirección de la cadena es 102, así que ponemos
Manual de Lenguaje Ensamblador
este valor en DX; luego llamamos al servicio 9 (mov ah, 9) de la interrupción 21h (int 21) para desplegar la
cadena. Luego ejecutamos el servicio 10h de la interrupción 16h (que detiene la ejecución del programa) y por
último regresamos a DOS con la interrupción 20h. Pulsamos dos veces enter para salir de la orden A.
-g
hola
Ahora debemos escribir nuestro programa a un archivo .COM en disco. Para ello empleamos la orden W
[write]. Para realizar esta escritura correctamente, primero elegimos un nombre para el programa, como
pr.com. La extensión debe ser .COM, porque W no puede crear ejecutables con formato .EXE; la orden es:
-n pr.com
También debemos especificar el tamaño de nuestro ejecutable. W creará un archivo con el tamaño
indicado en el registro CX; así que deberíamos poner en CX el tamaño de nuestro ejecutable empleando la
orden R:
-r cx
CX 0000
:15
-w
Escribiendo 00015 bytes
-q
y ejectamos pr.com:
hola
¿Todo bien?
Para MS-DOS sólo existen dos tipo de archivos ejecutables los .COM y .EXE. Ambos archivos difieren en
algunas cosas. Primero, las ventajas de los .EXE son dobles, nos permiten tener archivos reubicables y el uso de
hasta cuatro segmentos (STACK, DATA, EXTRA y CODE) de hasta 64KB cada uno. Un archivo .COM sólo
puede tener un segmento de 64KB, en el que se tiene tanto código como pila, y datos. La desventaja de los
.EXE es que agregan 512 bytes como cabecera con información para la reubicación del código. Un .COM no es
reubicable, siempre inicia en la dirección 0100H.
Si nuestro programa no es muy grande 64KB son mas que suficientes. Por lo que conviene crearlo
como .COM, para esto se cuenta con la utilería EXE2BIN.EXE que nos proporciona el sistema operativo. y que
nos permite crear .COM a partir de .EXE . Las restricciones para esto son las suguientes: el archivo a convertir
no debe estar empacado, no debe tener segmento de stack, debe tener sólo segmento de código y su tamaño
debe ser menor a 64KB.
C:\>debug
-
Ya estamos posicionados en el DEBUG y éste está preparado para recibir instrucciones en
ASSEMBLER.
Ensamblar A [dirección]
Comparar C dirección de intervalo
Volcar D [intervalo]
escribir E dirección [lista]
llenar F lista de intervalos
ir G [=dirección] [direcciones]
hex H valor1 valor2
entrada I puerto
cargar L [dirección] [unidad] [primer_sector] [número]
mover M dirección de intervalo
nombre N [nombre_ruta] [lista_argumentos]
salida O byte de puerto
Manual de Lenguaje Ensamblador
proceder P [=dirección] [número]
salir Q
registrar R [registrar]
buscar S lista de intervalos
seguimiento T [=dirección] [valor]
desensamblar U [intervalo]
escribir W [dirección] [unidad] [primer_sector] [número]
asignar memoria expandida XA [#páginas]
desasignar memoria expandida XD [identificador]
asignar páginas de memoria expandida XM [Lpágina] [Ppágina] [identificador]
mostrar estado de la memoria
XS
expandida
-
Note que al terminar aparece el guión que nos indica que el DEBUG tiene nuevamente el
control de las operaciones, con lo cual Uds. podrán ingresar más comandos
Cada una de la instrucciones que se mostraron anteriormente se irán explicando a lo largo de este
apunte, donde aplicaremos un ejercicio específico para cada una de ellas incrementando las posibilidades, desde
las más simples hasta las más complejas, para perfeccionar nuestro dominio de la programación.
Este ejercicio nos permite que miremos el valor de varios registros y del registro de estado de la ALU
(STATUS REGISTER). Para eso tipeamos:
-r
El comando r (registro) nos permite interactuar con los registros de la CPU, posibilitando modificar sus
contenidos. Cuando tipeamos r sin ningún otro parámetro nos muestra todos los registros con sus respectivos
contenidos, pero si agregamos un parámetro nos va a mostrar solo el contenido del registro que nosotros
mencionamos:
-r bx
BX 0000
:
Como podemos observar no nos devuelve el signo – sino que ahora nos muestra : para que ingresemos
el valor que queremos que sea almacenado en el registro BX.
-r bx
BX 0000
:5555
-r
AX=0000 BX=5555 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=1987 ES=1987 SS=1987 CS=1987 IP=0100 NV UP EI PL NZ NA PO NC
1987:0100 8936C7DE MOV [DEC7],SI DS:DEC7=0000
-
En negrilla vemos que el registro BX quedó modificado con el nuevo valor tipeado.
Suma y Resta.
Vamos a ingresar las siguientes instrucciones en el orden como se dan. Los números indicados
-a
1987:0100 mov ax,200
1987:0103 mov bx,FFF
1987:0106 add ax,bx
1987:0108 int 20
1987:010A
-
Ingresemos ahora el comando –r
Manual de Lenguaje Ensamblador
-u
Este comando nos muestra nuestro programa compilado y muchas otras instrucciones que se
encuentran en máquina pero que a nosotros no os interesan y que solo sirven para ensuciar nuestro
razonamiento, por ello es preferible el comando
Si ingresamos el comando –u0100 0108 nos muestra el programa compilado. No tipeamos hasta 010A
porque esta posición de memoria no la utilizamos y aparece instruccines que nosotros no hemos introducido en
el sistema.
-u 0100 0108
1987:0100 B80002 MOV AX,0200
1987:0103 BBFF0F MOV BX,0FFF
1987:0106 01D8 ADD AX,BX
1987:0108 CD20 INT 20
-
Hagamos un alto y veamos como queda nuestro programa. En la primer columna tenemos las posición de
nuestras instrucciones comenzando desde la posición 0100 (las 4 primeras indican también la posición pero a
ellas no nos referiremos por el momento).
En la segunda aparece la compilación. Aquí hay algo realmente curioso:
B80002 – El primer byte es el código de operación. Fácil de entender. El segundo corresponde a los dos
últimos dígitos del número que nosotros queremos cargar en el acumulador. El tercer byte tiene los dos primeros
número del número que nosotros ingresamos. Es decir que 0200 se representa en memoria como 0020.
Complicado pero así funciona el INTEL y no vamos ahora a entrar en detalle. En la tercera y cuarta las
instrucciones que nosotros ingresamos en lenguaje simbólico.
Manual de Lenguaje Ensamblador
Llegó el momento de analizar los diferentes tamaños de las instrucciones que utilizamos: La primera y
segunda corresponden a la sentencia MOV (mover) y tienen un longitud de 3 (tres ) bytes. La tercera
corresponde a una instrucción de suma y tiene solo dos. La cuarta también tiene dos.
-d
(DISPLAY) que nos muestra la memoria. Esta está dividida en dos partes:
a. La comprendida por caracteres hexadecimales que nos permiten ver todo el contenido de la
memoria porque la combinación de bits en un nibble (4 bits) siempre tiene representación en
hexadecimal.
El resto es basura que no nos pertenece. Si nosotros podemos leer esa basura, se pueden imaginar que
importante puede ser todo esta que queda en la memoria para un HACKER.
-a
1987:0100 mov ax,[0200]
1987:0103 mov cx,[0FFF]
1987:0107 add ax,cx
1987:0109 int 20
1987:010B
-
Ahora vamos a ejecutar el programa. Partimos del comando r para que nos muestre el estado de los
registros y la primera instrucción del programa.
-r
AX=0000 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=1987 ES=1987 SS=1987 CS=1987 IP=0100 NV UP EI PL NZ NA PO NC
1987:0100 A10002 MOV AX,[0200] DS:0200=A708
-
Vemos que AX = 0000 que CX= 0000 y que la dirección [0200] = A708 que está indicada por el registro
DS que se encuentra en la parte inferior derecha del ejemplo y en negrilla. Ahora vamos a usar el comando
TRACE que se representa por la letra t. Este nos permite ver paso a paso que sucede en nuestro computador.
-t
AX=A708 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=1987 ES=1987 SS=1987 CS=1987 IP=0103 NV UP EI PL NZ NA PO NC
1987:0103 8B0EFF0F MOV CX,[0FFF] DS:0FFF=DB52
-
Vemos que la primera instrucción modificó el contenido del registro AX con el valor de DS. Esto está bien, porque
es lo que nosotros pedimos.
Si volvemos a tipear t ejecutaremos la segunda instrucción.
-t
AX=A708 BX=0000 CX=DB52 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=1987 ES=1987 SS=1987 CS=1987 IP=0107 NV UP EI PL NZ NA PO NC
1987:0107 01C8 ADD AX,CX
-
Vemos que ahora no aparece el registro DS porque en la instrucción que se va a ejecutar no mencionamos
direcciones.
-t
AX=825A BX=0000 CX=DB52 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=1987 ES=1987 SS=1987 CS=1987 IP=0109 NV UP EI NG NZ NA PE CY
1987:0109 CD20 INT 20
-
a) El registro AX se ha modificado pero notemos que el nuevo valor es inferior al que ya teníamos
almacenado. ¿Porqué? La respuesta es simple porque se ha producido un acarreo. Se recomienda
realizar la suma manualmente para comprobar que lo sucedido es cierto.
b) Veamos que el STATUS REGISTER ha modificado su valor indicándonos que eso ha sucedido, para
mayor claridad lo marcamos en negrilla y en color.
-t
AX=825A BX=0000 CX=DB52 DX=0000 SP=FFE8 BP=0000 SI=0000 DI=0000
DS=1987 ES=1987 SS=1987 CS=00CA IP=0FA8 NV UP DI NG NZ NA PE CY
00CA:0FA8 90 NOP
-
Observemos que la nueva instrucción que aparece es un NOP (NO OPERATION) indicándonos que el
programa ha terminado.
NOTA: Podemos ejecutar directamente (t) pero nos mostrará la segunda instrucción a ejecutarse y la
primera ya ejecutada. Tipee q en la línea de comando, vuelva a tipear DEBUG y cargue nuevamente el
programa, posteriormente empiece directamente con el TRACE.
Resolver e informar.
Utilice el comando TRACE (t) para ejecutar el programa.
Ejercicio 1. Sumar el contenido de una dirección y de una constante.
Ejercicio 2: Reste dos números utilizando registros. El primero menor que el segundo.
La instrucción para restar es SUB.
Verifique el valor de los bits del STATUS REGISTER
Ejercicio 3: Suma y resta. Sume dos números y reste otro. Utilice constantes, no direcciones.
Verifique el valor de los bits del STATUS REGISTER
Ejercicio 4: Sume dos números y reste una dirección.
-r
AX=0000 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=1987 ES=1987 SS=1987 CS=1987 IP=0100 NV UP EI PL NZ NA PO NC
1987:0100 A10002 MOV AX,[0200] DS:0200=A708
-
Como vemos AX = 0000 si tipeamos el comando r ax nos permite modificar el el acumulador, como
muestra el ejemplo.
-rax
Manual de Lenguaje Ensamblador
AX 0000
:faf7
-r
AX=FAF7 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=1987 ES=1987 SS=1987 CS=1987 IP=0100 NV UP EI PL NZ NA PO NC
1987:0100 A10002 MOV AX,[0200] DS:0200=A708
-
Comprobamos que está modificado. Pero también podríamos hacerlo de otra manera.
Como dice el título el registro AX lo podemos dividir en parte alta y parte baja siendo sus códigos de
reconocimiento AH (ACUMULATOR HIGH) y AL (ACUMULATOR LOW).
Entones podemos escribir lo siguiente:
-a 0100
1987:0100 mov ah,03
1987:0102 int 20
1987:0104
Observemos que cuando se ejecutó la instrucción MOV AH,03 el registro AX quedo modificado en su
parte superior por el valor (03) que ingresamos.
-t
AX=03F7 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=1987 ES=1987 SS=1987 CS=1987 IP=0102 NV UP EI PL NZ NA PO NC
1987:0102 CD20 INT 20
-t
AX=03F7 BX=0000 CX=0000 DX=0000 SP=FFE8 BP=0000 SI=0000 DI=0000
DS=1987 ES=1987 SS=1987 CS=00CA IP=0FA8 NV UP DI PL NZ NA PO NC
00CA:0FA8 90 NOP
-
Así terminamos de ejecutar esta parte. Hagamos lo mismo con la parte inferior.
-a100
1987:0100 mov al,bb
1987:0102 int 20
1987:0104
-r
AX=03FF BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=1987 ES=1987 SS=1987 CS=1987 IP=0100 NV UP EI PL NZ NA PO NC
1987:0100 B0BB MOV AL,BB
Manual de Lenguaje Ensamblador
-t
AX=03BB BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=1987 ES=1987 SS=1987 CS=1987 IP=0102 NV UP EI PL NZ NA PO NC
1987:0102 CD20 INT 20
-t
AX=03BB BX=0000 CX=0000 DX=0000 SP=FFE8 BP=0000 SI=0000 DI=0000
DS=1987 ES=1987 SS=1987 CS=00CA IP=0FA8 NV UP DI PL NZ NA PO NC
00CA:0FA8 90 NOP
-
Ahora vamos a sumar las dos partes que componen el acumulador (AX). Para eso hacemos el siguiente
programa.
-a100
00CA:0100 add ah,al
00CA:0102 int 20
00CA:0104
y lo ejecutamos.
-r
AX=03BB BX=0000 CX=0000 DX=0000 SP=FFE8 BP=0000 SI=0000 DI=0000
DS=1987 ES=1987 SS=1987 CS=00CA IP=0100 NV UP DI PL NZ NA PO NC
00CA:0100 00C4 ADD AH,AL
-t
AX=BEBB BX=0000 CX=0000 DX=0000 SP=FFE8 BP=0000 SI=0000 DI=0000
DS=1987 ES=1987 SS=1987 CS=00CA IP=0102 NV UP DI NG NZ NA PE NC
00CA:0102 CD20 INT 20
Multiplicación y división.
Aquí tenemos algunas variantes que no se observaron el la suma y en la resta. Podemos multiplicar
entidades de 8 bites y de 16 bits (una palabra) o 16 y 32 o... dependiendo del tamaño de la palabra y del
procesador que estamos utilizando. Independientemente si queremos multiplicar por media palabra el valor debe
estar en la parte baja del registro que vamo a utilizar, por ejemplo en AL y si queremos multiplicar por la palabra
deberá ocupar todo el registro, es decir que deberá estar en AX por ejemplo.
Ingresamos el programa:
Manual de Lenguaje Ensamblador
C:\>debug
-a
1987:0100 mov al,4
1987:0102 mov cl,5
1987:0104 mul al,cl
1987:0106 int 20
1987:0108
-g106
AX=0010 BX=0000 CX=0005 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=1987 ES=1987 SS=1987 CS=1987 IP=0106 NV UP EI PL NZ NA PO NC
1987:0106 CD20 INT 20
En este caso vamos a utilizar dos valores que multiplicados superen la capacidad de almacenamiento de
la parte baja del registro.
-a
1987:0100 mov al,bb
1987:0102 mov cl,aa
1987:0104 mul al,cl
1987:0106 int 20
1987:0108
-g108
Observemos que ya hay modificaciones . ¿Determine cuales y que es lo que sucedió y porqué?
Ejecutamos el programa paso a paso.
-r
AX=0000 BX=0000 CX=0000 DX=0000 SP=FFE2 BP=0000 SI=0000 DI=0000
DS=1987 ES=1987 SS=1987 CS=1987 IP=0100 NV UP DI PL NZ NA PO NC
1987:0100 B008 MOV AL,08
-t
AX=0008 BX=0000 CX=0000 DX=0000 SP=FFE2 BP=0000 SI=0000 DI=0000
DS=1987 ES=1987 SS=1987 CS=1987 IP=0102 NV UP DI PL NZ NA PO NC
1987:0102 B1FE MOV CL,FE
-t
Manual de Lenguaje Ensamblador
AX=0008 BX=0000 CX=00FE DX=0000 SP=FFE2 BP=0000 SI=0000 DI=0000
DS=1987 ES=1987 SS=1987 CS=1987 IP=0104 NV UP DI PL NZ NA PO NC
1987:0104 F6E9 IMUL CL
-t
AX=FFF0 BX=0000 CX=00FE DX=0000 SP=FFE2 BP=0000 SI=0000 DI=0000
DS=1987 ES=1987 SS=1987 CS=1987 IP=0106 NV UP DI PL NZ NA PO NC
1987:0106 CD20 INT 20
-
Analicen que sucedió. ¿Qué pasa si tomamos AX en lugar de AL? ¿Qué sucedió con los bits
de bandera? ¿Están correctos o hay errores en ellos? Realicen el informe correspondiente.
Realice el mismo programa con la diferencia de la siguiente instrucción
Mov cl,-1 e informe el análisis particular y comparativo
Realizados los ejemplos anteriores y convenientemente analizados podemos pasar a realizar ejercicios
con registros completos.
Informe cada uno de los ejercicios.
En una división de valores enteros vamos a suponer que el dividendo siempre será mayor que el divisor,
por lo menos para esta primera parte de nuestro estudio.
Aclaraciones: Para un divisor de 8 bits el dividendo será de 16 bits, por lo tanto el dividendo lo
ubicaremos en AX (16 bits) y el divisor en CL (8bits)
El programa es:
-a
1987:0100 mov ax,8
1987:0103 mov cl,4
1987:0105 div cl Daría lo mismo escribir div ax,cl
1987:0107 int 20
1987:0109
-t
Cuando hacemos un programa seguramente tendremos ue realizar en algún momento una pregunta que
nos permita testear un registro, variable, archivo, etc., y optar por realizar tal o cual cosa dependiendo de que
resultado nos devuelva el dato consultado. Por otro lado también existe la psibilidad de que después de ejecutar
una serie de instrucciones debamos continuar el programa por otro lado saltando un montón de instrucciones, en
este caso estamos utilizando un salto inconsicional , no hay ninguna condición previa , y por último podemos
realizar una serie de repeticiones o iteraciones o LOOPS dentro de nuestro programa. Esto sucede enla mayoría
de los programas y es por eso que esta nueva sección investiga las tres posibilidades.
Para visualizar mejor las posibilidades de Verdadero y Falso que se pueden presentar utilizaremos dos
variables alfabéticas. El programa
-a
1987:0100 jmp 0113
1987:0102
-e 102 "Saltamos varias instrucciones verdad " 0d 0a "$"
-a 113
1987:0113 mov dx,102
1987:0116 mov ah,9
1987:0118 int 21
1987:011A int 20
1987:011C
-
-g
Saltamos varias instrucciones verdad
-u 100 011a
1987:0100 EB11 JMP 0113
1987:0102 53 PUSH BX
1987:0103 61 DB 61
1987:0104 6C DB 6C
1987:0105 7461 JZ 0168
1987:0107 6D DB 6D
1987:0108 6F DB 6F
1987:0109 7320 JNB 012B
1987:010B 7661 JBE 016E
1987:010D 7269 JB 0178
1987:010F 61 DB 61
1987:0110 7320 JNB 0132
1987:0112 69 DB 69
1987:0113 BA0201 MOV DX,0102
Manual de Lenguaje Ensamblador
1987:0116 B409 MOV AH,09
1987:0118 CD21 INT 21
1987:011A CD20 INT 20
-
En negrilla todas las instrucciones que saltamos, que por supuesto no son de nuestro programa. Otra
manera de ver la ejecución de nuestro programa es utilizando el comando p. En este caso aconsejamos hacerlo
indicando además la instrucción desde donde debe comenzar como en la secuencia que sigue.
-p 100
AX=0000 BX=0000 CX=0000 DX=0102 SP=FFEE BP=0000 SI=0000 DI=0000
DS=1987 ES=1987 SS=1987 CS=1987 IP=0116 NV UP EI PL NZ NA PO NC
1987:0116 B409 MOV AH,09
AX=0900 BX=0000 CX=0000 DX=0102 SP=FFEE BP=0000 SI=0000 DI=0000
DS=1987 ES=1987 SS=1987 CS=1987 IP=0118 NV UP EI PL NZ NA PO NC
1987:0118 CD21 INT 21
HEMOS SALTADO VAR¦ ¦ -!¦ UCCIONES VERDAD
AX=0924 BX=0000 CX=0000 DX=0102 SP=FFEE BP=0000 SI=0000 DI=0000
DS=1987 ES=1987 SS=1987 CS=1987 IP=011A NV UP EI PL NZ NA PO NC
1987:011A CD20 INT 20
Para realizar este ejercicio vamos a utilizar una nueva intrucción que es XCHG, que realiza el pasaje del
contenido entre dos registros. No importa la manera en que de especifiquen los registros en la instrucción. Para
ello damos el siguiente ejemplo:
C:\>debug
-a 100
1987:0100 mov ax,333
1987:0103 mov bx,222
1987:0106 xchg bx,ax
1987:0107 int 20
1987:0109
-t 3
Para comenzar vamos a formatear un diskette de la manera tradicional. El comando para realizar el
formateo, para los que no lo recuerdan, es: FORMAT A:/S.
C:\>format a:/s
Inserte un nuevo disco en la unidad A:
y presione Entrar cuando esté listo...
Y mostrará lo siguiente:
Manual de Lenguaje Ensamblador
Co Significado
mando
?? Permite ver el set de
instrucciones básico del Assembler
a Abre el DEBUG para ingresar
sentencias
g Permite la ejecutar un
programa en forma completa o parcial
r Permite ver el estados de los
registros
t Muestra paso a paso la
ejecución. Corresponde al comando
TRACE.
Manual de Lenguaje Ensamblador
APENDICE A.- MODELO DE REGISTROS DEL 8086
Bit 15 Bit 1
AH AL AX (Acumulador)
BH BL BX (Base)
CH CL CX (Contador)
DH DL DX (Datos)
SP (Apuntador de pila)
BP (Apuntador de base)
SI (Indice a fuente)
DI (Indice de destino)
Manual de Lenguaje Ensamblador
ASSUME
segmentregister:segmentname... Selecciona segmentregister que sea el
segmento por defecto para todos los símbolos
en el segmento o grupo nombrados. Si
segmentname es NOTHING, ningún registro
es seleccionado.
COMMENT delimitador texto delimitador Trata como un comentario a todo texto dado
entre delimitador y delimitador.
[nombre] DB valor...
[nombre] DW valor...
[nombre] DD valor...
[nombre] DQ valor...
[nombre] DT valor...
Aloja e inicializa un byte (DB), una palabra (DW, 2 bytes), una palabra doble (DD), una
palabra cuádruple (DQ), y 10 bytes ( (DT) de almacenamiento para cada valor.
nombre RECORD
campo:ancho[=expresión].. Define una estructura para un registro de 8 ó
16 bits que contiene uno o más campos.
nombre SEGMENT
[alineación][combine]['clase'] Marca el inicio de un segmento del programa
llamado nombre y que posee los atributos
indicados.
APENDICE E.- JUEGO DE INSTRUCCIONES DEL 8086, 8087, 80186, 80286 Y 80287.
Para el 8086/8088
CMP
acum,inmed
r/m,inmed Comparación
r/m, reg
reg, r/m
CMPS src,dest Comparación de cadenas.
CMPSB Compara cadenas byte por byte.
CMPSW Compara cadenas palabra por palabra.
CWD Convierte palabra a palabra doble.
DAA Ajuste decimal para adición.
DAS Ajuste decimal para substracción.
DEC r/m
Decremento.
reg
DIV r/m División.
ESC inmed, r/m Escape con 6 bits.
HLT Alto.
IDIV r/m División entera.
IMUL r/m Mutiplicación entera.
IN
accum,inmed Entrada desde puerto.
acum, DX
INC r/m
Incremento.
reg
INT 3 Interrupcion 3 codificada como un byte.
INT inmed Interrupción 0-255.
INTO Interrupción en overflow.
IRET Retorno de interrupción.
Brinco incondicional. J(condición) etiqueta Brinca de
acuerdo a las condiciones: A (arriba), AE (arriba o
igual), B (siguiente), BE (siguiente o igual), C (acarreo),
CXZ (CX en cero), E (igual), G (mayor), GE (mayor o
igual), L (menor), LE (menor o igual), NA (no anterior),
JMP etiqueta NAE (no anterior o igual), NB (no siguiente), NBE (no
r/m siguiente o igual), NC (no acarreo), NE (no igual), NG
(no mayor), NGE (no mayor o igual), NL (no menor),
NLE (no menor o igual), NO (no sobreflujo), NP (no
paridad), NS (no signo), NZ (no cero), O (sobreflujo), P
(paridad), PE (paridad par), PO (paridad impar), S
(signo), Z (cero).
Manual de Lenguaje Ensamblador
LAHF Carga AH con las banderas.
LDS r/m Carga DS.
LEA r/m Carga la dirección.
LES r/m Carga ES.
LOCK Cierra bus.
LODS src Carga cadena.
LODSB Carga byte de cadena en AL.
LODSW Carga palabra de la cadena en AX.
LOOP etiqueta Ciclo.
LOOPE etiqueta Ciclo mientras igual.
LOOPNE
Ciclo mientras no igual.
etiqueta
LOOPNZ
Ciclo mientras no cero.
etiqueta
LOOPZ etiqueta Ciclo mientras cero.
MOV acum,mem
r/m,inmed
mem, acum
r/m, reg
Mueve un valor del segundo al primer operando
r/m,segreg
reg, inmed
reg,r/m
segreg,r/m
Para el 8087
Para el 80287
Dirección Interrupción
Función
(Hex) (Hex)
0-3 0 Division by zero
4-7 1 Single step trace
8-B 2 Nonmaskable interrupt
C-F 3 Breakpoint instruction
10-13 4 Overflow
14-17 5 Print screen
18-1F 6,7 Reserved
20-23 8 Timer
24-27 9 Keyboard interrupt
28-37 A,B,C,D Reserved
38-3B E Diskette interrupt
3C-3F F Reserved
40-43 10 Video screen I/O
44-47 11 Equipment check
48-4B 12 Memory size check
4C-4F 13 Diskette I/O
50-53 14 Communication I/O
54-57 15 Cassete I/O
58-5B 16 Keyboard input
5C-5F 17 Printer Output
60-63 18 ROM Basic entry code
64-67 19 Bootstrap loader
68-6B 1A Time of day
6C-6F 1B Get control on keyboard break
70-73 1C Get control on timer interrupt
74-77 1D Pointer to video initialization table
78-7B 1E Pointer to diskette parameter table
7C-7F 1F Pointer to table for graphicscharacters ASCII 128-255
80-83 20 DOS program terminate
84-87 21 DOS function call
88-8B 22 DOS terminate address
90-93 24 DOS fatal error vector
94-97 25 DOS absolute disk read
98-9B 26 DOS absolute disk write
9C-9F 27 DOS terminate, fix in storage
A0-FF 28-3F Reserved for DOS
Manual de Lenguaje Ensamblador
100-1FF 40-7F Not used
200-217 80-85 Reserved by BASIC
218-3C3 86-F0 Used by BASIC interpreter
3C4-3FF F1-FF Not Used
Manual de Lenguaje Ensamblador
BIBLIOGRAFIA.
Páginas consultadas
- www.monografías.com
- www.lawebdelprogramador.com
- www.google.com
-