INTRODUCCIÓN AL ENSAMBLADOR.
PROCESO DE CREACIÓN DE UN PROGRAMA
Para la creación de un programa es necesario seguir cinco pasos:
Diseño del algoritmo,
Codificación del mismo,
Traducción a lenguaje máquina,
Prueba del programa
Depuración.
En el diseño se plantea el problema a resolver y se propone la mejor solución, creando
diagramas esquemáticos utilizados para el mejor planteamiento de la solución.
La codificación consiste en escribir el programa en algún lenguaje de programación; en este
caso específico en ensamblador.
La traducción al lenguaje máquina es la creación del programa objeto, esto es, el programa
escrito como una secuencia de ceros y unos que pueda ser interpretado por el procesador.
La prueba del programa consiste en verificar que el programa funcione sin errores, o sea,
que haga lo que tiene que hacer.
La última etapa es la eliminación de las fallas detectadas en el programa durante la fase de
prueba. La corrección de una falla normalmente requiere la repetición de los pasos
comenzando desde el primero o el segundo.
CREACIÓN DE PROGRAMAS USANDO DEBUG
Para crear un programa en ensamblador existen dos opciones, la primera es utilizar el MASM
(Macro Assembler, de Microsoft), y la segunda es utilizar el debugger, en esta primera sección
utilizaremos este último ya que se encuentra en cualquier PC con el sistema operativo MS-DOS,
lo cual lo pone al alcance de cualquier usuario que tenga acceso a una máquina con estas
caracteristicas.
Debug solo puede crear archivos con extensión .COM, y por las características de este tipo de
programas no pueden ser mayores de 64 kb, además deben comenzar en el desplazamiento,
offset, o dirección de memoria 0100H dentro del segmento específico.
REGISTROS DE LA UCP
La UCP tiene 14 registros internos, cada uno de 16 bits. Los primeros cuatro, AX, BX, CX, y
DX son registros de uso general y tambien pueden ser utilizados como registros de 8 bits, para
utilizarlos como tales es necesario referirse a ellos como por ejemplo: AH y AL, que son los
bytes alto (high) y bajo (low) del registro AX. Esta nomenclatura es aplicable también a los
registros BX, CX y DX.
Los registros son conocidos por sus nombres específicos:
AX Acumulador
BX Registro base
CX Registro contador
DX Registro de datos
DS Registro del segmento de datos
ES Registro del segmento extra
SS Registro del segmento de pila
CS Registro del segmento de código
BP Registro de apuntadores base
SI Registro índice fuente
DI Registro índice destino
SP Registro del apuntador de la pila
IP Registro de apuntador de siguiente instrucción
F Registro de banderas
Es posible visualizar los valores de los registros internos de la UCP utilizando el programa
Debug. Para empezar a trabajar con Debug digite en el prompt de la computadora:
C:\> Debug [Enter]
En la siguiente linea aparecera un guión, éste es el indicador del Debug, en este momento se
pueden introducir las instrucciones del Debug. Utilizando el comando:
- r [Enter]
Se desplegaran todos los contenidos de los registros internos de la UCP; una forma alternativa de
mostrarlos es usar el comando "r" utilizando como parametro el nombre del registro cuyo valor
se quiera visualizar. Por ejemplo:
- rbx
Esta instrucción desplegará unicamente el contenido del registro BX y cambia el indicador del
Debug de " - " a " : "
Estando así el prompt es posible cambiar el valor del registro que se visualizó tecleando el nuevo
valor y a continuación [Enter], o se puede dejar el valor anterior presionando [Enter] sin telclear
ningún valor.
Es posible cambiar el valor del registro de banderas, así como utilizarlo como estructura de
control en nuestros programas como se verá mas adelante. Cada bit del registro tiene un nombre
y significado especial, la lista dada a continuación describe el valor de cada bit, tanto apagado
como prendido y su relación con las operaciones del procesador:
Overflow NV = no hay desbordamiento;
OV = sí lo hay
Direction UP = hacia adelante;
DN = hacia atras;
Interrupts DI = desactivadas;
EI = activadas
Sign PL = positivo;
NG = negativo
Zero NZ = no es cero;
ZR = sí lo es
Auxiliary Carry NA = no hay acarreo auxiliar;
AC = hay acarreo auxiliar
Parity PO = paridad non;
PE = paridad par;
Carry NC = no hay acarreo;
CY = Sí lo hay
LA ESTRUCTURA DEL ENSAMBLADOR
En el lenguaje ensamblador las lineas de código constan de dos partes, la primera es el nombre
de la instrucción que se va a ejecutar y la segunda son los parámetros del comando u operandos.
Por ejemplo:
add ah bh
Aquí "add" es el comando a ejecutar (en este caso una adición) y tanto "ah" como "bh" son los
parámetros.
El nombre de las instrucciones en este lenguaje esta formado por dos, tres o cuatro letras. a estas
instrucciones tambien se les llama nombres mnemónicos o códigos de operación, ya que
representan alguna función que habrá de realizar el procesador.
Existen algunos comandos que no requieren parametros para su operación, asi como otros que
requieren solo un parámetro.
Algunas veces se utilizarán las instrucciones como sigue: add al,[170]
Los corchetes en el segundo parámetro nos indican que vamos a trabajar con el contenido de la
casilla de memoria número 170 y no con el valor 170, a ésto se le conoce como direccionamiento
directo.
NUESTRO PRIMER PROGRAMA
Vamos a crear un programa que sirva para ilustrar lo que hemos estado viendo, lo que haremos
será una suma de dos valores que introduciremos directamente en el programa:
El primer paso es iniciar el Debug, este paso consiste unicamente en teclear debug [Enter] en el
prompt del sistema operativo.
Para ensamblar un programa en el Debug se utiliza el comando "a" (assemble); cuando se utiliza
este comando se le puede dar como parámetro la dirección donde se desea que se inicie el
ensamblado, si se omite el parámetro el ensamblado se iniciará en la localidad especificada por
CS:IP, usualmente 0100H, que es la localidad donde deben iniciar los programas con
extensión .COM, y sera la localidad que utilizaremos debido a que debug solo puede crear este
tipo específico de programas.
Aunque en este momento no es necesario darle un parametro al comando "a" es recomendable
hacerlo para evitar problemas una vez que se haga uso de los registros CS:IP, por lo tanto
tecleamos:
- a0100 [Enter]
Al hacer ésto aparecerá en la pantalla algo como: 0C1B:0100 y el cursor se posiciona a la
derecha de estos números, nótese que los primeros cuatro dígitos (en sistema hexagesimal)
pueden ser diferentes, pero los últimos cuatro deben ser 0100, ya que es la dirección que
indicamos como inicio. Ahora podemos introducir las instrucciones:
0C1B:0100 mov ax,0002 ;coloca el valor 0002 en el registro ax
0C1B:0103 mov bx,0004 ;coloca el valor 0004 en el registro bx
0C1B:0106 add ax,bx ;le adiciona al contenido de ax el contenido de bx
0C1B:0108 int 20 ; provoca la terminación del programa.
0C1B:010A
No es necesario escribir los comentarios que van despues del ";". Una vez digitado el último
comando, int 20, se le da [Enter] sin escribir nada mas, para volver al prompt del debuger.
La última linea escrita no es propiamente una instrucción de ensamblador, es una llamada a una
interrupción del sistema operativo, estas interrupciones serán tratadas mas a fondo en un capítulo
posterior, por el momento solo es necesario saber que nos ahorran un gran número de lineas y
son muy útiles para accesar a funciones del sistema operativo.
Para ejecutar el programa que escribimos se utliza el comando "g", al utilizarlo veremos que
aparece un mensaje que dice: "Program terminated normally". Naturalmente con un mensaje
como éste no podemos estar seguros que el programa haya hecho la suma, pero existe una forma
sencilla de verificarlo, utilizando el comando "r" del Debug podemos ver los contenidos de todos
los registros del procesador, simplemente teclee:
- r [Enter]
Aparecera en pantalla cada registro con su respectivo valor actual:
AX=0006BX=0004CX=0000DX=0000SP=FFEEBP=0000SI=0000DI=0000
DS=0C1BES=0C1BSS=0C1BCS=0C1BIP=010A NV UP EI PL NZ NA PO NC
0C1B:010A 0F DB oF
Existe la posibilidad de que los registros contengan valores diferentes, pero AX y BX deben ser
los mismos, ya que son los que acabamos de modificar.
Otra forma de ver los valores, mientras se ejecuta el programa es utilizando como parámetro para
"g" la dirección donde queremos que termine la ejecución y muestre los valores de los registros,
en este caso sería: g108, esta instrucción ejecuta el programa, se detiene en la dirección 108 y
muestra los contenidos de los registros.
También se puede llevar un seguimiento de lo que pasa en los registros utilizando el comando "t"
(trace), la función de este comando es ejecutar linea por linea lo que se ensambló mostrando cada
vez los contenidos de los regitros.
Para salir del Debug se utiliza el comando "q" (quit).
GUARDAR Y CARGAR LOS PROGRAMAS
No sería práctico tener que digitar todo un programa cada vez que se necesite, para evitar eso es
posible guardar un programa en el disco, con la enorme ventaja de que ya ensamblado no será
necesario correr de nuevo debug para ejecutarlo.
GUARDAR LOS PROGRAMAS
Los pasos a seguir para guardar un programa ya almacenado en la memoria son:
Obtener la longitud del programa restando la dirección final de la dirección inicial,
naturalmente en sistema hexadecimal.
Darle un nombre al programa y extensión
Poner la longitud del programa en el registro CX
Ordenar a Debug que escriba el programa en el disco.
Utilizando como ejemplo el programa del capítulo anterior tendremos una idea mas clara de
como llevar estos pasos:
Al terminar de ensamblar el programa se vería así:
0C1B:0100 mov ax,0002
0C1B:0103 mov bx,0004
0C1B:0106 add ax,bx
0C1B:0108 int 20
0C1B:010A
- h 10a 100
020a 000a
- n prueba.com
- r cx
CX 0000
:000a
-w
Writing 000A bytes
Para obtener la longitud de un programa se utiliza el comando "h", el cual nos muestra la suma y
resta de dos números en hexadecimal. Para obtener la longitud del nuestro le proporcionamos
como parámetros el valor de la dirección final de nuestro programa (10A) y el valor de la
dirección inicial (100). El primer resultado que nos muestra el comando es la suma de los
parámetros y el segundo es la resta.
El comando "n" nos permite poner un nombre al programa.
El comando "rcx" nos permite cambiar el contenido del registro CX al valor que obtuvimos del
tamaño del archivo con "h", en este caso 000a, ya que nos interesa el resultado de la resta de la
dirección inicial a la dirección final.
Por último el comando w escribe nuestro programa en el disco, indicándonos cuantos bytes
escribió.
CARGAR LOS PROGRAMAS
Para cargar un archivo ya guardado son necesarios dos pasos:
Proporcionar el nombre del archivo que se cargará.
Cargarlo utilizando el comando "l" (load).
Para obtener el resultado correcto de los siguientes pasos es necesario que previamente se haya
creado el programa anterior.
Dentro del Debug escribimos lo siguiente:
- n prueba.com
-l
- u 100 109
0C3D:0100 B80200 MOV AX,0002
0C3D:0103 BB0400 MOV BX,0004
0C3D:0106 01D8 ADD AX,BX
0C3D:0108 CD20 INT 20
El último comando, "u", se utiliza para verificar que el programa se cargó en memoria, lo que
hace es desensamblar el código y mostrarlo ya desensamblado. Los parámetros le indican a
Debug desde donde y hasta donde desensamblar.
Debug siempre carga los programas en memoria en la dirección 100H, a menos que se le indique
alguna otra.
CONDICIONES, CICLOS Y BIFURCACIONES
Estas estructuras, o formas de control le dan a la máquina un cierto grado de decisión basado en
la información que recibe. La forma mas sencilla de comprender este tema es por medio de
ejemplos.
Vamos a crear tres programas que hagan lo mismo: desplegar un número determinado de veces
una cadena de caracteres en la pantalla.
- a100
0C1B:0100 jmp 125 ; brinca a la dirección 125H
0C1B:0102 [Enter]
- e 102 'Cadena a visualizar 15 veces' 0d 0a '$'
- a125
0C1B:0125 MOV CX,000F ; veces que se desplegara la cadena
0C1B:0128 MOV DX,0102 ; copia cadena al registro DX
0C1B:012B MOV AH,09 ; copia valor 09 al registro AH
0C1B:012D INT 21 ; despliega cadena
0C1B:012F LOOP 012D ; si CX>0 brinca a 012D
0C1B:0131 INT 20 ; termina el programa.
Por medio del comando "e" es posible introducir una cadena de caracteres en una determinada
localidad de memoria, dada como parámetro, la cadena se introduce entre comillas, le sigue un
espacio, luego el valor hexadecimal del retorno de carro, un espacio, el valor de linea nueva y
por último el símbolo '$' que el ensamblador interpreta como final de la cadena.
La interrupción 21 utiliza el valor almacenado en el registro AH para ejecutar una determinada
función, en este caso mostrar la cadena en pantalla, la cadena que muestra es la que está
almacenada en el registro DX.
La instrucción LOOP decrementa automaticamente el registro CX en uno y si no ha llegado el
valor de este registro a cero brinca a la casilla indicada como parámetro, lo cual crea un ciclo que
se repite el número de veces especificado por el valor de CX.
La interrupción 20 termina la ejecución del programa.
Otra forma de realizar la misma función pero sin utilizar el comando LOOP es la siguiente:
- a100
0C1B:0100 jmp 125 ; brinca a la dirección 125H
0C1B:0102 [Enter]
- e 102 'Cadena a visualizar 15 veces' 0d 0a '$'
- a125
0C1B:0125 MOV BX,000F ; veces que se desplegara la cadena
0C1B:0128 MOV DX,0102 ; copia cadena al registro DX
0C1B:012B MOV AH,09 ; copia valor 09 al registro AH
0C1B:012D INT 21 ; despliega cadena
0C1B:012F DEC BX ; decrementa en 1 a BX
0C1B:0130 JNZ 012D ; si BX es diferente a 0 brinca a 012D
0C1B:0132 INT 20 ; termina el programa.
En este caso se utiliza el registro BX como contador para el programa, y por medio de la
instrucción "DEC" se disminuye su valor en 1.
La instrucción "JNZ" verifica si el valor de B es diferente a 0, esto con base en la bandera NZ, en
caso afirmativo brinca hacia la dirección 012D. En caso contrario continúa la ejecución normal
del programa y por lo tanto se termina.
Una última variante del programa es utilizando de nuevo a CX como contador, pero en lugar de
utilizar LOOP utilizaremos decrementos a CX y comparación de CX a 0.
- a100
0C1B:0100 jmp 125 ; brinca a la dirección 125H
0C1B:0102 [Enter]
- e 102 'Cadena a visualizar 15 veces' 0d 0a '$'
- a125
0C1B:0125 MOV DX,0102 ; copia cadena al registro DX
0C1B:0128 MOV CX,000F ; veces que se desplegara la cadena
0C1B:012B MOV AH,09 ; copia valor 09 al registro AH
0C1B:012D INT 21 ; despliega cadena
0C1B:012F DEC CX ; decrementa en 1 a CX
0C1B:0130 JCXZ 0134 ; si CX es igual a 0 brinca a 0134
0C1B:0132 JMP 012D ; brinca a la direcci&oauten 012D
0C1B:0134 INT 20 ; termina el programa
En este ejemplo se usó la instrucción JCXZ para controlar la condición de salto, el significado de
tal función es: brinca si CX=0
El tipo de control a utilizar dependerá de las necesidades de programación en determinado
momento.
LAS INTERRUPCIONES
Definición de interrupción:
Una interrupción es una instrucción que detiene la ejecución de un programa para
permitir el uso de la UCP a un proceso prioritario. Una vez concluido este último proceso
se devuelve el control a la aplicación anterior.
Por ejemplo, cuando estamos trabajando con un procesador de palabras y en ese momento llega
un aviso de uno de los puertos de comunicaciones, se detiene temporalmente la aplicación que
estabamos utilizando para permitir el uso del procesador al manejo de la información que está
llegando en ese momento. Una vez terminada la transferencia de información se reanudan las
funciones normales del procesador de palabras.
Las interrupciones ocurren muy seguido, sencillamente la interrupción que actualiza la hora del
día ocurre aproximadamente 18 veces por segundo.
Para lograr administrar todas estas interrupciones, la computadora cuenta con un espacio de
memoria, llamado memoria baja, donde se almacenan las direcciones de cierta localidad de
memoria donde se encuentran un juego de instrucciones que la UCP ejecutará para despues
regresar a la aplicación en proceso.
En los programas anteriores hicimos uso de la interrupción número 20H para terminar la
ejecución de nuestros programas, ahora utilizaremos otra interrupción para mostrar información
en pantalla:
Utilizando Debug tecleamos:
- a100
2C1B:0100 JMP 011D
2C1B:0102 [ENTER]
- E 102 'Hola, como estas.' 0D 0A '$'
- A011D
2C1B:011D MOV DX,0102
2C1B:0120 MOV AH,09
2C1B:0122 INT 21
2C1B:0123 INT 20
En este programa la interrupción 21H manda al monitor la cadena localizada en la dirección a la
que apunta el registro DX.
El valor que se le da a AH determina cual de las opciones de la interrupción 21H sera utilizada,
ya que esta interrupción cuenta con varias opciones.
El manejo directo de interrupciones es una de las partes mas fuertes del lenguaje ensamblador, ya
que con ellas es posible controlar eficientemente todos los dispositivos internos y externos de
una com