BREVE RESUMEN DE LENGUAJE ENSAMBLADOR

0.- MOTIVACION.

El lenguaje ensamblador continúa siendo imprescindible para imple-
mentar fragmentos de código donde la velocidad del  ejecutable y/o
su tamaño sean críticos. Afortunadamente, muchos compiladores per-
miten la inclusión directa en  el código  fuente de  sentencias en
ensamblador, de forma que ya no  es necesario en la mayor parte de
los casos el uso de  ensambladores.  Como ventaja adicional, no es
necesario conocer las directivas de ensamblador, ya que pueden es-
tablecerse desde el  entorno de desarrollo y van implícitas en las
directivas del  compilador, por  ejemplo, el  compilador Pascal de
Borland o el compilador C/C++ de la misma compañía.
Aún así, a  muchos  programadores les resulta difícil renunciar al
control absoluto de la máquina  que permite  el lenguaje ensambla-
dor.

1.-REGISTROS DEL 8086

El 8086 dispone de ocho registros de propósito general, que  pode-
mos considerar como memorias implementadas sobre la  misma  CPU de
acceso muy rápido. Estos registros reciben los nombres de  AX, BX,
CX, DX, SI, DI, BP, SP. Existe tambien un registro IP (Instruction
Pointer)que apunta a la siguiente instrucción a ejecutar, formando
su  dirección  junto  con  el  registro  CS,  que   citaremos    a
continuación. Aparte de los registros generales y  de  IP, existen
registros de segmento, llamados CS, DS, SS, ES  y  un  registro de
flags, cuyos bits no son accesibles  directamente  y  que reflejan
los resultados de distintas operaciones.
Cualquiera de los registros de propósito general puede usarse para
escribir a/desde memoria, realizar  operaciones,  como  punteros o
contadores, pero cada uno tiene una personalidad  especial,  y  es
una buena práctica de programación  usarlos  para  lo  que  fueron
principalmente concebidos.
AX se usa siempre en multiplicaciones  y  divisiones y  es  el mas
eficiente para operaciones aritméticas y de movimiento de datos.
BX se usa como puntero, y junto con DS  referencia  posiciones  de
memoria. Por ejemplo, para cargar en AL el contenido de  la  posi-
ción de memoria número 9:

    MOV AX,0
    MOV DS,AX
    MOV BX,9
    MOV AL,[BX]

CX se usa principalmente como contador  en los  bucles. Estos  son
tan frecuentes que existe  una  instrucción  especial,  LOOP,  que
comprueba su  valor,  volviendo  al  principio  del  bucle  si  es
distinto de cero:

    MOV CX,10
    BUCLE:
        instrucciones

    LOOP BUCLE

DX  es el único  registro que puede usarse para acceder a puertos.
Por ejemplo, para escribir 62H en la dirección de puerto 1000H:

    MOV AL,62H
    MOV DX,1000H
    OUT DX,AL

SI se usa como puntero. Su nombre proviene de Source Index,  y  se
usa principalmente con instrucciones de cadena:

    CLD
    MOV AX,0
    MOV DS,AX
    MOV SI,20
    LODSB


carga en AX el valor de la posición 20 de memoria. SI se incrementa
en una unidad. En combinación con LOOP permite leer posiciones su-
cesivas de memoria.
DI tambien se usa como  puntero.  Permite  escribir  en posiciones
sucesivas de memoria cuando se usa con instrucciones de cadena:

    CLD
    MOV DX,0
    MOV ES,DX
    MOV DI,2048
    STOSB

escribe el contenido de AL en 0000:2048. Mientras que DI usa a  ES
como segmento, SI usa a DS.
BX, DI y SI actuan como punteros relativos a DS, o a ES en el caso
de DI cuando se usa con funciones de cadena. BP actua tambien  co-
mo puntero, pero relativo al segmento de pila SS.
SP es entre los registros de uso general el mas específico, ya que
no se recomienda su uso, pues apunta al extremo superior de la pi-
la, y por ello no debe  modificarse salvo que  se sepa exactamente
que se está haciendo.

2.-ESTRUCTURA DE UN PROGRAMA EN ENSAMBLADOR.

Expondremos  brevemente la estructura de un programa fuente en en-
samblador. Este está  compuesto por una serie de segmentos para la
pila, el código y los  datos. Los segmentos no pueden estar anida-
dos.
Cada  uno  comienza con el nombre del segmento seguido de la pala-
bra SEGMENT y alguna o algunas cadenas exigidas por el enlazador.
Cada  segmento  termina  con  el nombre del segmento seguido de la
palabra ENDS.
El programa termina con la palabra END seguida del nombre del pun-
to de entrada al programa.
De  esta forma, el armazón de un programa en ensamblador es el si-
guiente:

            PILA SEGMENT STACK 'STACK'
            .
            .
            PILA ENDS
            DATOS SEGMENT 'DATA'
            .
            .
            DATOS ENDS
            CODIGO SEGMENT 'CODE'
            .
            .
            CODIGO ENDS
            END [ENTRADA]

donde  [ENTRADA] es el nombre del punto donde se inicia la secuen-
cia de instrucciones.
Dentro  del  segmento de pila, se  establece el tamaño de ésta. La
forma habitual de hacerlo  es mediante la sentencia DW (define pa-
labra) seguida  del  número de palabras  que vamos a reservar y la
sentencia DUP (?). Así:

        PILA SEGMENT STACK 'STACK'
        DW 100H DUP(?)
        PILA ENDS

establece un segmento de pila con un tamaño de 100H  palabras (512
bytes), sin especificar el contenido inicial.
Las directivas  que  se  emplean habitualmente  para establecer el
tamaño de la pila son:

        DB  Define Byte                1 Byte.
        DW  Define Word                2 Bytes.
        DD  Define doble palabra       4 Bytes.
        DQ  Define cuádruple palabra   8 Bytes.

La sintaxis de DUP es expresión1 DUP expresión2.  expresión1 indi-
ca  el número de veces a incluir consecutivamente expresión2 en el
segmento.  Cuando expresión2 es (?) indicamos que no es importante
el  contenido  inicial. No se debe cometer el error que con (?) la
pila queda inicializada a ceros o a cualquier  otro valor particu-
lar.

Pasemos  al segmento de datos. La sintaxis  de las líneas conteni-
das en este segmento es [nombre] [directiva] expresión(es).
Vayamos por partes:  [nombre]  es un campo opcional pero absoluta-
mente  conveniente,  ya  que  especificándolo nos podremos referir
después a la dirección de la variable simplemente nombrándola.
La [directiva] puede ser DB, DW, DD, DQ. La expresión puede ser:

    . Una cadena entre comillas
    . Un valor numérico
    . Una tabla de valores separados por ","

Así, son válidas las líneas siguientes:

    Mensaje DB 'Hola'
    Peso DB 70
    Nada DW (?)
    Vector DB 0,0,0,13,120,76,76,76,76,76,76

La última línea se puede simplificar con DUP en la forma siguien-
te:

    Vector DB 3 DUP(0),13,120,6 DUP(76)

Considérese la variable como un puntero a un dato del  tipo  defi-
nido por D[X], y el valor especificado a continuación como el  va-
lor apuntado.
El  segmento de código suele tener una primera línea con la direc-
tiva ASSUME, que informa al ensamblador de los segmentos a los que
apuntarán durante la ejecución los diferentes registros de segmen-
to. Por ejemplo:

    ASSUME CS:CODIGO, DS:DATOS, SS:PILA

Si  deseamos  generar un ejecutable de tipo COM, es necesario cum-
plir  ciertas normas.  La primera  es que el programa fuente  debe
contener un solo segmento, donde residirán el código, los datos  y
la pila. La otra limitación es que el punto de entrada debe  estar
en el desplazamiento 100H de dicho segmento. La directiva ORG 100H
indica  al ensamblador  que continúe  el ensamblado a partir de la
dirección dada por el argumento, en este caso 100H.
Lo  habitual  es que a continuación forzemos un salto al verdadero
punto  de  entrada, estando  el espacio intermedio ocupado por los
datos.  De  esta  forma, el segmento de código de un programa .COM
comenzaría:

    CODIGO SEGMENT 'CODE'
    ASSUME CS:CODIGO, DS:CODIGO, SS:CODIGO
    ORG 100H
    Inicio: JMP Entrada
    .
    . ; espacio para los datos
    .
    Entrada PROC
    .
    .
    .
    Entrada ENDP
    CODIGO ENDS
    END Inicio

Un  dato a tener en cuenta es que somos responsables de  iniciali-
zar correctamente los registros  para que  apnten a los segmentos,
por ejemplo, para inicializar el registro DS:


    MOV AX,DATOS
    MOV DS,AX

Por otro lado, siempre será necesario llamar a una función del DOS
para terminar el programa, liberando la memoria y  devolviendo  el
control al sistema operativo. La encargada de realizar esta  tarea
es la función 4CH de INT 21H, de manera que al final del  segmento
de código será preciso introducir las siguientes dos líneas:

    MOV AH,4CH
    INT 21H

3.-MODOS DE DIRECCIONAMIENTO.

Existen dieciseis formas de referenciar una posición  de  memoria.
No nos ocuparemos aquí de sus denominaciones, sino de la forma  en
que se construyen, pues, en realidad, todas se forman  mediante la
combinación  de  unos  pocos  elementos.  Así,  se  podía escribir
simbólicamente

               BX      SI
                   +       +  despl
               BP      DI

donde  despl  es una constante  o una expresión  resoluble en  una
constante y, por defecto, BX indica  desplazamientos  respecto  al
segmento DS, mientras que BP indica  desplazamientos  respecto  al
segmento SS.
Como ilustración, sea una cadena de caracteres definida en el seg-
mento de datos. Se dan una serie de formas equivalentes para refe-
renciar al elemento número ocho de la cadena:

    .DATA
    CADENA DB 'ABCDEFGHIJKL',0
    .CODE
    MOV AX,@DATA
    MOV DS,AX
    ...
    MOV SI,OFFSET CADENA + 8
    MOV AL,[SI]
    ...
    MOV BX,8
    MOV AL,[CADENA+BX]
    ...
    MOV BX,OFFSET CADENA
    MOV AL,[BX+8]
    ...
    MOV SI,8
    MOV AL,[CADENA+SI]
    ...
    MOV BX,3
    MOV SI,4
    MOV AL,[CADENA+BX+SI+1]


4.- JUEGO DE INSTRUCCIONES

4.1. Instrucciones de transferencia de datos.
Todos los ejemplos anteriores se han puesto con la instrucción MOV
que es con diferencia la mas usada en cualquier programa. Hay  que
tener en cuenta sin embargo algunas  cosas que no  pueden  hacerse
con la instrucción MOV.
En primer lugar, no pueden moverse  datos  directamente entre  dos
posiciones de memoria. Así MOV Datos1,Datos2 es una expresión ile-
gal. En su lugar se escriben las dos líneas:

    MOV AX,Datos2
    MOV Datos1,AX

En segundo lugar, no se pueden mover datos directamente de un  re-
gistro de segmento a otro. MOV DS,ES es ilegal. En su lugar  pode-
mos escribir:

    MOV AX,ES
    MOV DS,AX

En tercer lugar, no se puede mover una constante directamente a un
registro de segmento. MOV DS,Datos es ilegal. En su lugar escriba-
mos

    MOV AX,Datos
    MOV DS,AX

Finalmente, no se puede usar CS como operando.

MOV pertenece a un primer grupo de instrucciones de  transferencia
de datos, entre la memoria y los  registro y  a/de los puertos  de
entrada/salida. Las instrucciones mas usadas de este grupo son las
siguientes:
PUSH, POP y sus variantes para almacenar o extraer datos de la pi-
la.
IN, OUT para obtener datos de un puerto o enviarlos.
LEA, LDS, LES para mover direcciones de variables, en lugar de sus
contenidos. Describiremos brevemente cada una de ellas.

PUSH se usa para guardar en la pila un dato, y POP para recuperar-
lo. Como  la pila tiene estructura LIFO, las instrucciones POP  se
realizarán en orden inverso a las instrucciones PUSH. Por ejemplo,
si se quieren guardar los registro AX y BX y recuperarlos después,
la secuencia correcta es

    PUSH AX
    PUSH BX
    .
    .
    POP BX
    POP AX

PUSH y POP también son adecuados para transferir el  contenido  de
un registro a otro, por ejemplo

    PUSH AX
    POP BX

transfiere AX a BX. Este método es sin embargo mas lento. A  veces
se necesita guardar todos los registro generales para recuperarlos
después. En lugar de escribir  una serie de PUSH  y otra serie  de
POP, se usan las instrucciones PUSHA y POPA. Además de aportar co-
modidad al programador y legibilidad al programa, estas instruccio-
nes se ejecutan más rápido. Cuando se quiere  guardar el  registro
de indicadores, se usan las formas PUSHF y POPF.

Las instrucciones de entrada/salida IN/OUT se usan para comunicar-
se con los periféricos del sistema, y tienen el formato

    IN acumulador,puerto
   OUT puerto,acumulador

donde el acumulador es AX o AL según se trata de bytes o palabras.
Algunos ejemplos pueden ser los siguientes:

    IN AL,200 ; pasa a AL el contenido del puerto 200
   OUT 20H,AX ; envía al puerto 20H el contenido de AX

Las instrucciones de transferencia de direcciones permiten  código
fuente mas compacto sobre todo cuando se trata con direccionamien-
tos indexados. Por ejemplo, consideremos la instrucción

    MOV AL,[BX+SI+20]

El micro toma el valor de BX, le suma el valor de SI y al resulta-
do le suma 20, usando el valor obtenido como desplazamiento dentro
del segmento DS para acceder a un byte y  transferirlo al registro
AL. Esta instrucción lleva implícitas las operaciones:

    MOV DX,BX
    ADD DX,SI
    ADD SI,20

Existe una instrucción mas rápida y compacta que sustituye a  este
conjunto, llamada LEA (Load Effective Address) para cargar un des-
plazamiento. Su sintaxis es

    LEA registro,mem16

donde registro es un registro de propósito general  de 16  bits  y
mem16 es un operando de memoria de tipo palabra. Por ejemplo

    LEA DX,[BX+SI+20]

transfiere a DX el desplazamiento BX+SI+20. La variante LDS es si-
milar. Su sintaxis es

    LDS reg16,mem32

Lee una palabra doble de 32 bits de la memoria y carga los 16 bits
de orden mas bajo en el registro especificado, y los mas altos  en
DS. Similar a la anterior es LES: funciona exactamente igual salvo
que los 16 bits mas altos los guarda en ES.
Supongamos que queremos cargar el segmento de Tabla_, definida co-
mo  Etiqueta DD Tabla_ en DX, y su desplazamiento en BX. Una forma
de hacerlo es mediante la serie

    MOV BX,DESPL Etiqueta
    MOV AX,SEG   Etiqueta
    MOV DX,AX

La mejor forma de hacerlo es mediante

    LDS BX,Etiqueta

4.2. Instrucciones aritméticas.
Las instrucciones aritméticas mas simples son INC y DEC. La prime-
ra incrementa el operando en una unidad. La segunda lo  decrementa
también  en una unidad. El resulta se trunca al número de bits del
operando. Así, si todos los bits del operando están a 1, el opera-
dor INC los pasará a todos a cero. El bit adicional necesario para
representar el resultado correcto se pierde. De esta forma, INC AL
nunca modificará AH.

Las instrucciones suma y resta son ADD y SUB, y su sintaxis es

        ADD destino,fuente
        SUB destino,fuente

En el primer caso, se suma fuente a destino, y el resultado  queda
almacenado en destino. En el segundo, fuente se resta de  destino,
y el resultado queda almacenado en destino. En principio, los ope-
randos fuente y destino han de ser del mismo tamaño, y al tratarse
de aritmética sobre número fijo de bits, los acarreos se pierden.
A veces sin embargo es necesario operar sobre números de  distinto
tamaño en bits. Por ejemplo, no  es posible escribir  directamente
ADD BL,AX. En estos casos se  procede a la extensión del  operando
de menor tamaño. En este caso pondríamos a  cero BH  mediante  una
instrucción MOV o mediante un XOR. Pero esto es válido solo cuando
estamos tratando con números sin signo.  Para extender  un  número
con signo se recurre a los operadores CBW (Convert Byte to Word) y
CWD (Convert Word to Double word).

Las instrucciones  de  multiplicación y división son  MUL y DIV, y
usan el acumulador en todos los casos. La multiplicación de 8 bits
se hace entre AL y un operando cualquiera de ocho bits, registro o
memoria, y el resultado es de 16 bits, quedando almacenado en  AX.
¿Que ocurre cuando se multiplica AX por otro operando de 16 bits ?
El resultado es de 32 bits, de los cuales los 16 menos  significa-
tivos se almacenan en AX y los mas significativos en DX.
Cuando se divide un operando de 16 bits entre otro de 8, se obtie-
ne un cociente de 8 bits y un resto también de 8 bits. El dividen-
do se ha de encontrar en AX, mientras que el divisor se indica  en
el operando, y puede ser un registro de 8  bits o un  operando  de
memoria. El cociente se devuelve en AL y el resto en AH. En  forma
simbólica podríamos poner

        AX ¦ Divisor
           +------
        AH   AL

Cuando se divide un número de 32 bits entre otro de 16 bits,   co-
ciente y resto son de 16 bits. Los 16 bits mas significativos  han
de encontrarse en DX, y los menos significativos en AX. El divisor
se indica en el operando, y al igual que antes, puede ser  un  re-
gistro de 16 bits o un operando de memoria de 16 bits. El cociente
de 16 bits se devuelve en AX, y el resto en DX. Simbólicamente:

        DX¦AX ¦ Divisor
              +------
           DX   AX


Existen en realidad dos juegos de instrucciones para la  multipli-
cación y división. Uno para operandos con signo y  otro para  ope-
randos sin signo. Es el programador quien decide si los  operandos
representan números con signo o sin signo, y por tanto el juego de
instrucciones a utilizar. La sintaxis es:

    sin signo   MUL operando
                DIV operando
    con signo  IMUL operando
               IDIV operando

Otro conjunto de instrucciones son las de  manipulación  de  bits,
que a su vez puede dividirse en operaciones lógicas, de  desplaza-
miento  y de  rotación. Expondremos sólo las primeras. Estas   son
AND, OR y XOR y efectúan las mismas operaciones que las  funciones
lógicas del mismo tamaño. La sintaxis es

        AND destino,fuente
         OR destino,fuente
        XOR destino,fuente

La instrucción XOR es especialmente útil para poner  a cero  cual-
quier registro: XOR reg,reg es igual que MOV reg,0 , pero  es  mas
rápida.
La instrucción AND pone a cero ciertos bits, de forma que se puede
efectuar una determinada operación con el resto de los  bits.  Por
ejemplo, para comprobar si el bit n-simo de un registro se encuen-
tra a 1, basta hacer AND reg,mascara, donde mascara  es un  número
binario donde todos los bits son cero, menos el n-simo, que vale 1.
Si el resultado es distinto de cero, el bit n-simo de reg se esta-
ba a uno.

4.3. Instrucciones de transferencia de control
Las instrucciones que conforman un programa se almacenan en  orden
correlativo en la memoria, pero, salvo en los casos mas sencillos,
no se ejecutan linealmente, sino que el  control se  transfiere  a
distintas partes de la secuencia. Las instrucciones de control  se
pueden dividir en:

    . Transferencia incondicional
    . Transferencia condicional
    . Bucles

Analizaremos cada uno de estos grupos, presentando las instruccio-
nes mas frecuentes.

4.3.1. Transferencia incondicional
Dentro del primer grupo contamos con las instrucciones CALL, RET y
JMP. La primera se usa para llamar a un procedimiento, esto es, un
conjunto de instrucciones que se ejecutan mas de una vez pero  que
se escriben una sola. Este conjunto de instrucciones se  almacenan
en memoria a partir de una posición dada, y cada vez que se  nece-
sita ejecutarlas, se transfiere el control  a la primera  instruc-
ción de la serie mediante  la  instrucción  CALL [destino],  donde
[destino] es la dirección de comienzo. La última instrucción de un
procedimiento es RET, que devuelve el control al programa  princi-
pal. Cuando se llama a CALL, la primera tarea que realiza es guar-
dar en la pila la dirección de la siguiente instrucción, para  re-
cuperarla y continuar el programa cuando se termine de ejecutar el
procedimiento. El operando de CALL es habitualmente una etiqueta ,
indicando la dirección origen del procedimiento, pero también pue-
den hacerse llamadas indirectas a través de un registro o un  ope-
rando de memoria. No entraremos en este punto.  El esquema general
de CALL y RET es:

        CALL etiqueta
        instrucciones siguientes
        .
        .
        etiqueta:
        MIPROC PROC
            instrucciones siguientes
            .
            RET
        MIPROC ENDP

Es posible anidar procedimientos, de forma  que  un  procedimiento
pueda llamar a otro, y así sucesivamente. El nivel de  anidamiento
solo está limitado por el tamaño de la pila, donde se guardan  las
direcciones de retorno. Por ejemplo:

        CALL etiqueta1
        instrucciones siguientes
        .
        .
        etiqueta1:
        MIPROC1 PROC
            instrucciones siguientes
            .
            CALL etiqueta2
            .
            RET
        MIPROC1 ENDP
        etiqueta2:
        MIPROC2 PROC
            instrucciones siguientes
            .
            .
            RET
        MIPROC2 ENDP

La instrucción JMP (jump, saltar) se usa para  transferir el  con-
trol al punto del programa que se desee. Cuando la dirección  des-
tino se encuentra en el rango -128,127 bytes de la instrucción  de
salto, este se llama corto. En caso contrario, cercano, si destino
se encuentra dentro del mismo segmento y lejano si se encuentra en
otro segmento. Los saltos lejanos se generan especificando el seg-
mento y desplazamiento del destino.

4.3.2. Transferencia condicional
El juego de instrucciones para transferencia condicional es  espe-
cialmente rico. El ensamblador reconoce algunas  instrucciones  de
transferencia condicional con varios mnemónicos, con objeto de fa-
cilitar la tarea al  programador. Es un buen hábito  construir los
programas de forma que, siempre que sea posible, se ejecute el ca-
so esperado si no se efectúa el salto. La razón es que, si  no  se
efectúa el salto, se consumen solo tres ciclos de reloj. Los  sal-
tos condicionales son siempre cortos, y su sintaxis es

        J[mnemonico] destino_corto

donde [mnemonico] es de una a tres letras y  destino_corto es  una
etiqueta situada en el rango -128,127  bytes desde la  instrucción
de salto. Las únicas condiciones que pueden comprobarse son el es-
tado de los flags, lo que equivale a decir que los saltos condicio-
nales se refieren a la última operación efectuada. Existen  tantas
instrucciones de salto como flags, pero las mas utilizadas son

        JZ      salta si cero
        JNZ     salta si no cero

La limitación de los saltos condicionales de que sean saltos  cor-
tos se puede superar usando la instrucción  JMP. Consideremos  las
instrucciones J[x],JN[x], donde J[x] salta si el flag "x" está   a
uno y JN[x] salta si el flag "x" está a cero. La técnica  consiste
en generar un salto con la  condición contraria  por encima de  un
JMP a la dirección deseada. Por ejemplo, para implementar

        J[x] etiqueta_lejana

escribimos

        JN[x] etiqueta
        JMP etiqueta_lejana
        etiqueta:

Casi todas las veces, las condiciones de salto en un programa  son
del tipo A]B , A[B , A=B , A[]B , A]=B y A[=B.  Por exigencias  de
la aritmética de complemento a dos, existen en realidad dos juegos
de instrucciones, según que se comparen números con signo o sin él.
Para comparaciones de números sin signo, se emplean en los  mnemó-
nicos las letras A (de Above, por encima) y B (de Below, por deba-
jo),  y para las comparaciones de números con signo las  letras  G
(de Greater, mayor) y L (de Less, menor). Las instrucciones resul-
tantes comprueban los flags de cero, acarreo y desbordamiento, que
han sido previamente establecidos mediante una instrucción CMP. La
sintaxis de esta instrucción es

        CMP destino,fuente

y  compara  destino con fuente. Por ejemplo, si queremos  comparar
los números sin signo contenidos en AX y BX y saltar a etiqueta si
BX[AX, las instrucciones adecuadas son

        CMP BX,AX
        JB  etiqueta

y si los números tienen signo, la secuencia es:

        CMP BX,AX
        JL  etiqueta

Los mnemónicos mas usados son

.para números sin signo
    JA      destino]fuente
    JE      destino=fuente
    JNE     destino[]fuente
    JB      destino[fuente
    JBE     destino[=fuente
    JAE     destino]=fuente

.para números con signo
    JG      destino]fuente
    JE      destino=fuente
    JNE     destino[]fuente
    JL      destino[fuente
    JLE     destino[=fuente
    JGE     destino]=fuente

4.3.3. Instrucciones de bucle
Las instrucciones de bucle hacen que se repitan fragmentos de  có-
digo un número prefijado de veces. Una forma de hacerlo es median-
te el contador y un salto condicional al final del bucle

        MOV CX,repeticiones
        comienzo:
            .
            .
            .
        DEC CX
        JNZ comienzo

Este fragmento puede compactarse usando la instrucción  LOOP,  di-
señada para este propósito:

        MOV CX,repeticiones
        comienzo:
            .
            .
            .
        LOOP comienzo  ; si CX[]0, salta a comienzo
                       ; de lo contrario, sigue en esta línea


4.4. Instrucciones de cadena.
Las instrucciones de cadena se utilizan habitualmente para inicia-
lizar una zona de memoria a un mismo  valor, para mover un  bloque
contiguo de datos de una zona a otra de memoria, para comparar ca-
denas o para buscar un valor concreto en una zona de memoria.
El  funcionamiento de este  conjunto de instrucciones  depende del
"flag de dirección", cuyo valor puede ser alterado por las dos ins-
trucciones siguientes

        CLD: CLear Direction flag
        STD: SeT Direction flag

Las instrucciones de cadena tienen mnemónicos que terminan con  la
letra "S", y operan sobre los registros que se especificarán en ca-
da caso. Ya que pueden actuar sobre bytes o palabras, es  necesario
indicar este extremo, lo que se conseguirá escribiendo la letra "B"
o "W" tras la "S" del mnemónico.
La instrucción LODS (LOaD String), en sus versiones LODSB y LODSW,
se  usa  para cargar un byte o palabra desde la dirección  indica-
da por  DS:SI  en AL o AX.  Si el flag de dirección se encuentra a
cero, SI se incrementa en 1 o 2, y si está a 1 se decrementa en  1
o 2. De esta forma puede recorrerse la memoria en uno u otro  sen-
tido. La instrucción complementaria es STOS (STOre String). Se usa
de  la misma forma que LODS  y almacena el contenido del  registro
AL o AX en la  dirección de memoria especificada por ES:DI. El re-
gistro DI se altera según la  misma lógica que el SI  con la  ins-
trucción LODS. La instrucción mas usada es MOVS (MOVe String),  la
cual sirve para trasladar uno (MOVSB) o dos bytes (MOVSW).
Esta instrucción lee un dato de DS:SI y lo almacena en ES:DI. Des-
pués, los registro SI  y DI se modifican según la misma lógica que
para las instrucciones LODS y STOS.
Para efectuar operaciones repetitivas con estas funciones, podemos
implementar un bucle que repitiera un número prefijado de veces la
operacion a realizar con  estas instrucciones.  No sería necesario
gestionar explícitamente los registro SI y DI porque estos se  ac-
tualizan  automáticamente.  Sin  embargo, el  lenguaje ensamblador
permite hacer  esto mismo  de forma mas  compacta mediante la ins-
trucción REP.
Esta instrucción se coloca en la misma línea que la instrucción de
cadena que vayamos a usar, antes de  la misma.  Obsérvese el frag-
mento de código siguiente:

        CLD             ; incrementa DI cada vez
        MOV AX,1234H    ; segmento en que escribe
        MOV ES,AX       ; STOS almacena en ES:DI
        MOV DI,5678H    ; desplazamiento. Dirección de comienzo
        XOR AL,AL       ; pone AL a cero
        MOV CX,200D     ; inicializa CX a 200D
        REP STOSB       ; ejecuta 200D veces la orden STOSB
                        ; ahora, CX=0 y DI=5678H+200D

En resumen:

        +-----------+     STOS
        ¦  AX ¦ AL  ¦ -----------]  ES:DI [----+
        +-----------+                          ¦
                                             MOVS
        +-----------+     LODS                 ¦
        ¦  AX ¦ AL  ¦ [-----------  DS:SI -----+
        +-----------+

Existen dos instrucciones más que permiten examinar una  cadena  y
comparar dos cadenas. Sus mnemónicos son SCAS (SCAn String),y CMPS
(CoMPare String). Ambas admiten las "versiones"  para byte y pala-
bra añadiendo las terminaciones B y W.
La instrucción SCASB es equivalente a CMP AL,ES:[DI], mientras que
SCASW es equivalente a CMP AX,ES:[DI]. El resultado de estas  ope-
ración altera los flags y DI se actualiza según la lógica vista en
las operaciones anteriores.
La sintaxis CMPSB o CMPSW es CMPS mem1,mem2, donde mem1 y mem2 son
operandos de memoria. En concreto, CMPS realiza la comparación en-
tre DS:[SI] y ES:[DI], es decir, CMP DS:[SI],ES:[DI]. Al igual que
en el caso anterior, estas instrucciones alcanzan su máxima utili-
dad al usarlas en combinación con los operadores REPZ (REPeat whi-
le Zero, repetir mientras cero) y REPNZ (REPeat while Not Zero).
REPZ  y  REPNZ  admiten los sinónimos REPE (REPeat while Equal)  y
REPNE (REPeat while Not Equal).
Los usos mas comunes de las instrucciones que hemos presentado son
los siguientes:

    * Encontrar el primer elemento distinto del contenido del acu-
      mulador dentro de un bloque. Para esto se usa REPZ SCAS.
    * Encontrar el primer elemento diferente entre dos cadenas. Se
      usa REPZ CMPS.
    * Encontrar la primera ocurrencia del contenido del acumulador
      dentro de un bloque: REPNZ SCAS.
    * Encontrar el primer elemento igual entre dos cadenas:  REPNZ
      CMPS.

5.- USO DE INTERRUPCIONES

La filosofía del PC es ésta: dejar que la BIOS haga el trabajo. En
efecto, el PC almacena en la ROM BIOS una enorme cantidad de proce-
dimientos que podemos usar en nuestro provecho, simplemente  invo-
cándolos. El lenguaje ensamblador permite pasar el  control a  una
determinada interrupción. Cada interrupción tiene un número y ofre-
ce diversos servicios. El número de servicio se especifica en  AH,
y en otros registros se especifican otros parámetros. La interrup-
ción puede o no devolver valores.
La instrucción en ensamblador que permite ejecutar  interrupciones
es INT, y su sintaxis es INT [nº función]. Por ejemplo, la función
10H tiene muchos servicios útiles para controlar el video. Uno  de
estos servicios consiste en posicionar el cursor en una fila y co-
lumna determinadas. En este caso:

    ENTRADA:  AH=2 (nº de servicio)
              BH=página de video en la que fijar posición
              DH=fila
              DL=columna
    SALIDA:   No tiene

Por ejemplo, para colocar el cursor en la fila 10, columna 15,  el
fragmento de código apropiado sería

    MOV AH,2    ; nº de servicio
    MOV BH,0    ; suponemos que 0 es el nº de página activa
    MOV DH,0AH
    MOV DL,0FH
    INT 10H

Existe abundante bibliografía donde se recogen todas las funciones
y servicios de la ROM BIOS. La lista de funciones es  tan  extensa
que puede  hacer creer que el emsamblador es mas complicado de  lo
que lo es realmente, al  menos  al nivel que será  usado  en  este
curso. Pero el alumno  no  deberá  confundir  la  dificultad  para
escribir un programa en ensamblador con la dificultad para retener
o incluso solo localizar la función que en un momento dado sirva a
nuestros propósitos.
Solo  a  modo  de  ilustración,  se  exponen  a  continuación  las
funciones mas usuales que aparecen cuando se empieza  a  programar
en ensamblador:

- Siempre será preciso terminar un programa, para lo que se recurre
  a la funcion 4CH de INT 21H:

    MOV AH,4CH
    INT 21H

- Casi siempre será preciso mandar mensajes a la pantalla, para lo
  que se usa la función 9:

    MOV DX,OFFSET [MENSAJE]
    MOV AH,9
    INT 21H

  donde [MENSAJE] se ha definido en el segmento de datos  mediante
  una linea del estilo de

    MENSAJE DB 'ESTO ES UN MENSAJE $'

  y $ es el carácter que necesita la función 9 para identificar el
  final de la cadena.
  Cuando hay varios mensajes que mostrar, lo normal es escribir u-
  no en cada línea, y por eso se añaden al mensaje  los  caracteres
  10 y 13, que significan respectivamente salto de línea y salto de
  carro:

    MENSAJE DB 'ESTO ES UN MENSAJE',10,13,'$'

- Cuando se quiere mostrar un mensaje, muchas veces se desea colo-
  carlo en una posición determinada de la pantalla. La función 02H
  de INT 10H realiza el trabajo. Para ello, BH contendrá el  núme-
  ro de la página de pantalla, DH la línea y DL la columna, de 0 a
  24 y de 0 a 79 respectivamente.

- Ocurre casi siempre que al iniciar un programa deseamos  limpiar
  la pantalla. Para ello, sobreescribimos con espacios en blanco y
  los atributos que deseemos la RAM de video, que en adaptadores a
  color comienza en la direccion 0B800H:

    MOV AX,0B800H
    MOV ES,AX
    MOV DI,0      ; apuntamos ES:DI al inicio de la RAM de video
    MOV AL,32
    MOV AH,07H    ; carácter 32, gris sobre negro
    MOV CX,2000   ; 2000 caracteres
    REP STOSW

- Casi siempre será preciso leer un carácter desde teclado, de  lo
  que se encarga la función 1. El resultado queda almacenado en AL:

    MOV AH,1
    INT 21H

- Tambien es frecuente leer una cadena de caractéres de la entrada
  standard:

    MOV AH,3FH
    MOV BX,0
    MOV CX,[LONGITUD MAXIMA]
    MOV DX,[DESTINO DE LA CADENA]
    INT 21H
    AND AX,AX

  3FH es la función del DOS que permite leer cadenas. Si BX=0,  se
  leen desde entrada standard,  que  es  el  teclado.  La longitud
  máxima se encuentra en CX,  y  la  cadena  leida  va  a parar  a
  [DESTINO DE LA CADENA] que es OFFSET [VARIABLE DE  CADENA]. Como
  AX guarda la longitud de la cadena leida, AND AX,AX comprueba si
  se ha leido algún carácter.

- Igualmente frecuente es la escritura de una cadena en la  salida
  standard:

    MOV CX,[LONGITUD CADENA]
    MOV AH,40H
    MOV BX,1
    MOV DX,OFFSET [CADENA]
    INT 21H


En el apéndice A se da una relación de las funciones BIOS.


(Nota: tomado de los apuntes de la asignatura Periféricos, Universidad de Alicante, curso 1995.1996, profesor F.J. Gil-Chica)