Hemos visto que generalmente (aunque no necesariamente) una instrucción consta de una parte de operación y una de dirección. La parte de dirección puede contener la dirección de un operando utilizado en la ejecución de la instrucción. En otras ocasiones la parte dirección de la instrucción puede no contener la dirección donde se encuentra el operando, sino la dirección donde se encuentra la dirección del operando. En el primer caso la dirección se describe como la dirección directa; en el segundo caso es una operación. En las computadoras, mini computadoras y microcomputadoras se emplea una amplia gama de modos de direccionamiento de los que consideraremos algunos en esta sección.
• DIRECTO. En el direccionamiento directo, como ya señalamos, la instrucción contiene la dirección de la posición de memoria donde se encuentra el operando.
• INDIRECTO. En el direccionamiento indirecto, señalamos de nuevo, la dirección contiene no la dirección donde se encuentra el operando, sino la dirección donde se encuentra la dirección del operando.
• RELATIVO. En el direccionamiento relativo la parte dirección de la instrucción contiene el número N. En memoria la dirección del operando se encuentra sumando el numero N al número del contador del programa.
• INDEXADO. En el direccionamiento indexado como en el relativo, la parte dirección de la instrucción contiene un número N que puede ser positivo o negativo. Sin embargo para utilizar el direccionamiento indexado, el computador debe estar equipado con un registro especial empleado para permitir direccionamiento indexado, y denominado naturalmente registro índice.
La posición de memoria donde se localiza el operando se encuentra mediante la suma I + N.
• REGISTRO INDIRECTO. Algunos computadores que incorporan la facultad del direccionamiento de registro indirecto tienen un registro especial, a menudo llamado registro (P). Este registro contiene la dirección de memoria del operando. Una instrucción que invoque realmente direccionamiento de registro indirecto no tiene bits significativos en su parte dirección. En lugar de ello, la instrucción completa se incluye en los bits asignados a la parte de operación de la instrucción. Una instrucción típica que use un registro de direccionamiento indirecto debería especificar “cargar” el acumulador con el operando localizado en la dirección de dada en el registro (p).
• INMEDIATO. EN el direccionamiento inmediato, la parte de dirección de la instrucción contiene no la dirección del operando sino el mismo operando.
• INHERENTE. Ordinariamente una dirección que es parte de una instrucción se refiere a una posición de memoria. Cuando una instrucción indica una fuente o un destino de datos y no se direcciona específicamente, ya no se hace referencia a la posición de memoria, se dice que la instrucción tiene una dirección inherente.
Existen ciertas normas para crear un programa en lenguaje ensamblador. La primera se refiere al formato interno de cada instrucción del lenguaje. Toda línea del código debe digitarse bajo ciertas reglas, las cuales pueden considerarse como la sintaxis propia del lenguaje. Sin embargo, como se verá más adelante, en la formación de instrucciones también entra en juego la semántica. La segunda norma es el formato externo del programa, que puede equipararse con una capa que rodeará al programa para ayudar a definir su entorno.
EL FORMATO INTERNO
El forma interno de una línea de código se puede resumir en cuatro partes.
• La etiqueta/variable/constante.- puede definirse o no, y si se define debe estar seguida por separadores, ya sea uno o más espacios o tabuladores.
• El nombre nemónico/directiva.- que puede equipararse con el verbo o la acción por realizar, seguido de uno o más separadores (espacios o tabuladores).
• El operando.- que generalmente se divide en dos (aunque existen instrucciones que solo operan con uno): el destino (que será el depósito de algún resultado) y la fuente de la información (el originador de la “acción”). El destino y la fuente (si existe ésta) deben estar separados por una coma.
• El comentario.- que siempre va precedido por el símbolo “;”, debe de estar separado del operando por uno o más espacios o tabulaciones, y puede existir o no.
EL FORMATO EXTERNO
El formato externo esta formado por varios comandos clave que permiten establecer el entorno operativo del programa. Entre ellos tenemos uno que define el modelo de memoria (básicamente establece cuáles y cuántos segmentos se van a usar), los diferentes segmentos que contendrá el código, los datos y la pila, así como el comienzo y final del programa. Ahora bien, existen dos maneras de definir el programa: en la primera se utilizan directivas simplificadas y en la segunda no se usan. Las directivas simplificadas ayudan a esclarecer ciertos comandos crípticos del programa, permitiendo que sean más legibles, y se incluyeron en las versiones del MASM 5.0 y posteriores.
PROCESO Y ENSAMBLADO DE LIGADO
|
Este proceso es muy sencillo y se describe a continuación:
Si está trabajando en MS-DOS siga estos pasos:
1.- Escriba el programa, tal y como aparece en el listado anterior, usando su editor de texto preferido.
2.- Guárdelo con algún nombre y la extensión .ASM
3.- En el símbolo del MS-DOS escriba lo siguiente
C:PASS32BIN>PASS32 Nombre.ASM –t <Enter>
4.- Ejecute el programa .COM que se genera.
Para probar el programa abra una ventana de MS-DOS y seleccione el programa haciendo doble clic sobre el icono.
Directivas de ensamble (Seudo instrucciones)
Pass32 cuenta con algunas palabras reservadas que cumplen tareas especiales para facilitar la programación en ensamblador, estas palabras son llamadas seudo instrucciones o directivas de ensamble.
La siguiente es una lista de las directivas de ensamble más utilizadas en Pass32:
DB Reserva un byte en memoria
DW Reserva una palabra (Word) en memoria o 2 bytes
DD Reserva una palabra doble (Double Word)
.EQU Se utiliza para reemplazar símbolos por valores
PROC-ENDP Se utilizan para declarar procedimientos en los programas
.MACRO-ENDM Se utilizan para declarar macros
DUP Sirve para inicializar cadenas de caracteres o arreglos numéricos
.INCLUDE Se utiliza para obtener datos o subrutinas de otros programas
.EXTERN Declara un símbolo como externo, trabaja en conjunto con .INCLUDE .PUBLIC Declara un símbolo como público
Los programas incluidos como ejemplos muestran la forma de utilizar estas directivas.
Instrucciones de transferencia Son utilizadas para mover los contenidos de los operandos. Cada instrucción se puede usar con diferentes modos de direccionamiento.
Instrucciones de carga Son instrucciones específicas de los registros. Son usadas para cargar en algœn registro bytes o cadenas de bytes.
Instrucciones de la pila Estas instrucciones permiten el uso de la pila para almacenar y extraer datos.
Propósito: Transferencia de datos entre celdas de memoria, registros y acumulador.
Sintaxis:
MOV Destino,Fuente
Donde Destino es el lugar a donde se moverán los datos y fuente es el lugar donde se encuentran dichos datos.
Los diferentes movimientos de datos permitidos para esta instrucción son:
Destino: memoria. Fuente: acumulador
Destino: acumulador. Fuente: memoria
Destino: registro de segmento. Fuente: memoria/registro
Destino: memoria/registro. Fuente: registro de segmento
Destino: registro. Fuente: registro
Destino: registro. Fuente: memoria
Destino: memoria. Fuente: registro
Destino: registro. Fuente: dato inmediato
Destino: memoria. Fuente: dato inmediato
Ejemplo:
MOV AX,0006h
MOV BX,AX
MOV AX,4C00h
INT 21H
Este pequeño programa mueve el valor 0006H al registro AX, luego mueve el contenido de AX (0006h) al registro BX, por último mueve el valor 4C00h al registro AX para terminar la ejecución con la opción 4C de la interrupción 21h.
Propósito: Mover cadenas de bytes o palabras desde la fuente, direccionada por SI, hasta el destino direccionado por DI.
Sintaxis:
MOVS
Este comando no necesita parametros ya que toma como dirección fuente el contenido del registro SI y como destino el contenido de DI. La secuencia de instrucciones siguiente ilustran esto:
MOV SI, OFFSET VAR1
MOV DI, OFFSET VAR2
MOVS
Primero inicializamos los valores de SI y DI con las direcciones de las variables VAR1 y VAR2 respectivamente, despues al ejecutar MOVS se copia el contenido de VAR1 a VAR2.
Los comandos MOVSB y MOVSW se utilizan de la misma forma que MOVS, el primero mueve un byte y el segundo una palabra.
Propósito: Cargar cadenas de un byte o palabra al acumulador.
Sintaxis:
LODS
Esta instrucción toma la cadena que se encuentre en la dirección especificada por SI, la carga al registro AL (o AX) y suma o resta 1 (segun el estado de DF) a SI si la transferencia es de bytes o 2 si la transferencia es de palabras.
MOV SI, OFFSET VAR1
LODS
La primer linea carga la dirección de VAR1 en SI y la segunda linea lleva el contenido de esa localidad al registro AL.
Los comandos LODSB y LODSW se utilizan de la misma forma, el primero carga un byte y el segundo una palabra (utiliza el registro completo AX).
Propósito: Transfiere al registro AH el contenido de las banderas
Sintaxis:
LAHF
Esta instrucción es útil para verificar el estado de las banderas durante la ejecución de nuestro programa.
Las banderas quedan en el siguiente orden dentro del registro:
SF ZF ¿? AF ¿? PF ¿? CF
El simbolo "¿?" significa que en esos bits habrá. un valor indefinido.
Propósito: Cargar el registro del segmento de datos
Sintaxis:
LDS destino, fuente
El operando fuente debe ser una palabra doble en memoria. La palabra asociada con la dirección mas grande es transferida a DS, o sea que se toma como la dirección del segmento. La palabra asociada con la dirección menor es la dirección del desplazamiento y se deposita en el registro señalado como destino.
Propósito: Carga la dirección del operando fuente.
Sintaxis:
LEA destino, fuente
El operando fuente debe estar ubicado en memoria, y se coloca su desplazamiento en el registro índice o apuntador especificado en destino.
Para ilustrar una de las facilidades que tenemos con este comando pongamos una equivalencia:
MOV SI, OFFSET VAR1
Equivale a:
LEA SI, VAR1
Es muy probable que para el programador sea mas sencillo crear programas extensos utilizando este último formato.
Propósito: Carga el registro del segmento extra
Sintaxis:
LES destino, fuente
El operando fuente debe ser un operando en memoria de palabra doble. El contenido de la palabra con la dirección mayor se interpreta como la dirección del segmento y se coloca en ES. La palabra con la dirección menor es la dirección del desplazamiento y se coloca en el registro especificado en el parámetro destino.
Propósito: Recupera un dato de la pila
Sintaxis:
POP destino
Esta instrucción transfiere el último valor almacenado en la pila al operando destino, despues incrementa en dos el registro SP.
Este incremento se debe a que la pila va creciendo desde la dirección mas alta de memoria del segmento hacia la mas baja, y la pila solo trabaja con palabras (2 bytes), entonces al incrementar en dos el registro SP realmente se le esta restando dos al tamaño real de la pila.
Propósito: Extrae las banderas almacenadas en la pila.
Sintaxis:
POPF
Este comando transfiere bits de la palabra almacenada en la parte superior de la pila hacia el registro de banderas.
La forma de transferencia es la siguiente:
BIT BANDERA
0 CF
2 PF
4 AF
6 ZF
7 SF
8 TF
9 IF
10 DF
11 OF
Estas localizaciones son las mismas para el comando PUSHF
Una vez hecha la transferencia se incrementa en 2 el registro SP disminuyendo así el tamaño de la pila.
Propósito: Coloca una palabra en la pila.
Sintaxis:
PUSH fuente
La instrucción PUSH decrementa en dos el valor de SP y luego transfiere el contenido del operando fuente a la nueva dirección resultante en el registro recién modificado.
El decremento en la dirección se debe a que al agregar valores a la pila ésta crece de la dirección mayor a la dirección menor del segmento, por lo tanto al restarle 2 al valor del registro SP lo que hacemos es aumentar el tamaño de la pila en dos bytes, que es la única cantidad de información que puede manejar la pila en cada entrada y salida de datos.
Propósito: Coloca el valor de las banderas en la pila
Sintaxis:
PUSHF
Este comando decrementa en 2 el valor del registro SP y luego se transfiere el contenido del registro de banderas a la pila, en la dirección indicada por SP.
Las banderas quedan almacenadas en memoria en los mismos bits indicados en el comando POPF
MACRO
Macro, del griego μακρο significa “grande”. En el ámbito informático es la abreviatura del término “macroinstrucción”.
Una macro o macroinstrucción es una serie de instrucciones que se almacenan para que se puedan ejecutar de forma secuencial mediante una sola llamada u orden de ejecución.
CARACTERISTICAS
-
Una macroinstrucción es por tanto una instrucción compleja, formada por otras instrucciones más sencillas.
-
Además tiene que estar almacenada, el término no se aplica a una serie de instrucciones escritas en la linea de comandos enlazadas unas con otras por redirección de sus resultados (piping) o para su ejecución consecutiva.
-
Las macros suelen almacenarse en el ámbito del propio programa que las utiliza y se ejecutan pulsando una combinación especial de teclas.
-
La diferencia entre una macroinstrucción y un programa es que en las macroinstrucciones la ejecución es secuencial y no existe otro concepto del flujo de programa que por tanto, no puede bifurcarse.
MACROS DE APLICACIONES
Las macros son grupos de instrucciones que tienen un seguimiento cronológico usadas para economizar tareas; una macro no es más que un conjunto de instrucciones tales como “borrar archivo”, “añadir registro”, etc., y que se almacenan en una ubicación especial (por ejemplo en Microsoft Access observamos que hay una zona para crear macros, una macro en Access trabajando para una base de datos podría ser un archivo que al llamarse desde otra instrucción: borrara los registros de un cliente o accionista, luego borrara ciertos registros en otras tablas, extraerá su información de un log, entre otras cosas.
La primera cosa de todas, ¿qué son las interrupciones?, bien, las interrupciones son un “mecanísmo” por medio del cual hacemos que la CPU deje la tarea en la que estaba para que se vaya a ocupar de otra cosa distinta, es decir, es una forma de llamar la atención de la CPU de tal forma que cada dispositivo cuando necesita ser atendido por la CPU, emite una interrupción o señal haciendo que la CPU vaya a atenderla de inmediato. Esto es importantísimo ya que de no existir interrupciones, la CPU debería de ir preguntando, cada cierto tiempo, a los dispositivos para ver si necesitan de su intervención y como podeis suponer, eso significaría lentitud, mucha lentitud. Por tanto, quedaros con que las interrupciones sirven para controlar el hardware, ya que son las que llaman a la CPU cuando este, el hardware, necesita la intervención de la misma.
Las interrupciones se pueden dividir en 2 grupos:
1. Interrupciones de Software. También son conocidas como “falsas interrupciones” ya que se producen como consecuencia de la ejecución de otra instrucción al no ser el hardware las que las produce. Otra forma de entender estas interrupciones, es verlas desde el punto de vista de llamadas a subrutinas, lógicamente, la gracia está en que esas subrutinas no son nuestras, sino que son las propias de cada sistema operativo, driver o similar tiene. Quedaros pues, con que somos nostros los que hacemos invocamos a la interrupción. Este tipo de interrupción es el más habitual en la programación.
2. Interrupciones de Hardware. Este tipo de interrupción es invocado directamente por los dispositivos hardware de nuestro ordenador, por lo que “son bastante más auténticas” que las anteriores. Al producir algún dispositivo hardware la interrupción el controlador de interrupciones o PIC se encarga de gestionarla determinando, en el caso de producirse más de una interrupción a la vez, cual de ellas tiene más prioridad y debe de ser gestionada por la CPU. El funcionamiento de este tipo de interrupciones es bastante similar y se suele utilizar mucho para la programación de sistemas de comunicaciones.
La Tabla de Vectores de Interrupciones. Seguro que alguno se ha preguntado, “si cuando llamamos a una interrupción se ejecuta una determinada rutina…¿dónde narices se encuentra esta rutina?, es más, ¿cómo sabe nuestra CPU dónde encontrarla?.” Bien, la respuesta a estas dos preguntas (y muchas más) se encuentra en la tabla de vectores de interrupción. Dicha tabla es una estructura que se crea durante la inicialización del ordenador y se coloca, en el principio de nuestra memoria (segmento 0 y desplazamiento 0). Dicha estrucutura ocupa y de forma justa 1Kb, ya que dispone de 256 entradas de 4 bytes cada una. Lo importante de todo esto es que, dicha estructura, almacena la dirección, en memoria, de las distintas rutinas que van ligadas a las distintas interrupciones, de tal modo que cuando invocamos, mediante interrupción software, o se invoca, mediante hardware, una interrupción, lo que se hace es utilizar un índice que va ligado a la interrupción, de tal manera que, con dicho índice, se acude a la tabla de vectores de interrupción para que el sistema encuentre la dirección de la rutina en dónde se encuentra el verdadero tratamiento a la interrupción, es decir, si se produce la interrupción x, lo que se hace es acudir a la tabla con el índice x para encontrar la entrada (recordar que eran 256 entradas con 4 bytes cada una) que contiene la dirección en memoria de la rutina que sirve para tratar a la interrupción de índice x. Como cada entrada es de 4 bytes, es fácil adivinar que dichos 4 bytes forman la dirección, ya que 2 de esos bytes se utilizan como segmento y los otros 2 como desplazamiento, total, que ya tenemos la dirección de memoria con la rutina que hay que utilizar.
Ejemplo 1
El siguiente es un ejemplo del programa clásico Hola mundo escrito para la arquitectura de procesador x86 (bajo el sistema operativo DOS).
.model small
.stack
.data
Cadena1 DB 'Hola Mundo.$'
.code
programa:
mov ax, @data
mov ds, ax
mov dx, offset Cadena1
mov ah, 9
int 21h
end programa
Ejemplo 2
Una selección de instrucciones para una computadora virtual[3] ) con las correspondientes direcciones de memoria en las que se ubicarán las instrucciones. Estas direcciones NO son estáticas. Cada instrucción se acompaña del código ensamblador generado (código objeto) que coincide con la arquitectura de computador virtual, o conjunto de instrucciones ISA.
Dir.
|
Etiqueta
|
Instrucción
|
|
|
|
.begin
|
|
|
|
.org 2048
|
|
|
a_start
|
.equ 3000
|
|
2048
|
|
ld length,%
|
|
2064
|
|
be done
|
00000010 10000000 00000000 00000110
|
2068
|
|
addcc %r1,-4,%r1
|
10000010 10000000 01111111 11111100
|
2072
|
|
addcc %r1,%r2,%r4
|
10001000 10000000 01000000 00000010
|
2076
|
|
ld %r4,%r5
|
11001010 00000001 00000000 00000000
|
2080
|
|
ba loop
|
00010000 10111111 11111111 11111011
|
2084
|
|
addcc %r3,%r5,%r3
|
10000110 10000000 11000000 00000101
|
2088
|
done:
|
jmpl %r15+4,%r0
|
10000001 11000011 11100000 00000100
|
2092
|
length:
|
20
|
00000000 00000000 00000000 00010100
|
2096
|
address:
|
a_start
|
00000000 00000000 00001011 10111000
|
|
|
.org a_start
|
|
3000
|
|
a:</tt
|
|
Ejemplo 3
Ensamblador para μCIntel 80C51
ORG 8030H
T05SEG:
SETB TR0
JNB uSEG,T05SEG ;esta subrutina es utilizada
CLR TR0 ;para realizar una cuenta de
CPL uSEG ;0.5 segundos mediante la
MOV R1,DPL ;interrupción del timer 0.
MOV R2,DPH
CJNE R2,#07H,T05SEG
CJNE R1,#78H,T05SEG
MOV DPTR,#0000H
RET
Ejemplo 4
ORG 0
Inicio
bsf STATUS,RP0
clrf PORTB
movlw 0xFF
movwf PORTA
bcf STATUS,RP0
Principal
movf PORTA,W
movwf Contador
movf Contador,F
btfsc STATUS,Z
goto PuntoDecimal
sublw d'9'
btfss STATUS,C
END
Un macro ensamblador es un ensamblador modular, descendiente de los ensambladores básicos. Fueron muy populares en los años 1950 y años 1960, antes de la generalización de los lenguajes de alto nivel. Hacen todo lo que puede hacer un ensamblador, y además proporcionan una serie de Directivas para definir y ejecutar macro instrucciones (o simplemente, Macros). Cuando ejecutamos a una “macro”, ésta se expande al cuerpo que hayamos definido.