Make your own free website on Tripod.com

 

 

 

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.

 

 


 

II.- EL LENGUAJE ENSAMBLADOR.

 

              1.- UN EJEMPLO CLASICO.

En esta parte se describe lo que es el lenguaje ensamblador, no al ensamblador o al proceso de ensamblado. Aquí se tratará todo lo concerniente con el lenguaje ensamblador y el conjunto de directivas del Microsoft Macro Assembler v4.0. Si bien esto puede resultar bastante extenso y complejo, aquí sólo se describirán las instrucciones y directivas básicas.

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

             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

La descripción del programa es como sigue:

 1.- Las declaraciones SEGMENT y ENDS definen los segmentos a usar.

 2.- La variable SALUDO en el segmento DATA, define la cadena a ser desplegada. El signo del dolar 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 cargado 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.

 2.- EL FORMATO DEL ENSAMBLADOR.

 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



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:

                            [nombre]  mnemónico  [operandos] [;comentarios]

 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 la definición de datos y declaración de instrucciones y  operandos el  MASM reconoce el conjunto de caracteres formado por letras mayúsculas, letras minúsculas (excluyendo caracteres acentuados, ñ, Ñ), números, y los símbolos: ? @ _ $ : . [ ] ( ) ‹ › { } + - / * & % ! ´ ~ ¦  \  = #  ˆ ; , " ‘

 La declaración de números requiere tener presente ciertas consideraciones. En el MASM un entero  se refiere a un número entero: combinación de dígitos hexadecimales, octales, decimales o binarios,  más una raíz opcional. La raíz se especifica con B,  Q u O, D, o H. El ensamblador usará siempre la raíz decimal por defecto, si se omite la especificación de la raíz (la cual se puede cambiar con la directiva .RADIX).  Así nosotros podemos especificar un entero de la siguiente manera: digitos, digitosB, digitosQ  o digitosO, digitosD, digitosH. Si una D o B aparecen al final de un número, éstas siempre se considerarán un especificador de raíz, e.g. 11B será tratado como 112 (210), mientras que si se trata del número 11B16 debe introducirse como 11Bh.

 Para los números reales tenemos al designador R, que sólo puede ser usado con  números hexadecimales de 8, 16, ó 20 digitos de la forma digitosR. También puede usarse una de las directivas DD, DQ, y DT con el formato [+¦-]digitos.digitos[E[+¦-]digitos]. 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.

 El MASM posee un conjunto de instrucciones que no pertenecen  al lenguaje ensamblador propiamente sino que son instrucciones que únicamente son reconocidas por el ensamblador y que han sido agregadas para facilitar la tarea de ensablamblado, tanto para el programador como para el programa que lo lleva a cabo. Dichas instrucciones son denominadas directivas. En general, las directivas son usadas para especificar  la organización de memoria,  realizar ensamblado condicional, definir macros,  entrada, salida, control de archivos,  listados, cross-reference, direcciones e información acerca de la estructura de un  programa y  las declaraciones de datos. El apéndice D proporciona una lista completa de estas 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:

                  nombre SEGMENT [alineación] [combinación] [´clase´]

                              nombre ENDS

 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 parráfos, 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]

 la opción expresión  permite definir la dirección en la cual el programa iniciará.

 Asignación de segmentos: La directiva ASSUME permite indicar cuales serán los valores por default que asimirán los registros de segmento. Existen dos formas de hacer esto:

                                        ASSUME registrosegmento:nombre,,,

                                        ASSUME NOTHING

 NOTHING cancela valores previos.

 Etiquetas: Las etiquetas son declaradas

                                                            nombre:

 donde nombre constituye una cadena de caracteres.

 Declaración de datos:  Estos se declaran según el tipo, mediante la regla

                                              [nombre] directiva  valor,,,

 donde directiva puede ser DB (bytes), DW (palabras), DD (palabra doble),  DQ (palabra cuádruple), 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 = expresion

                                               nombre EQU expresión

                                               nombre LABEL tipo

 donde tipo puede ser BYTE,  WORD,  DWORD,  QWORD,  TBYTE,  NEAR,  FAR.

 Declaración de estructuras: Para la declaración de estructuras de datos se emplea la directiva STRUC. Su sintaxis es:

                                                          nombre STRUC

                                                                    campos

                                                                     nombre ENDS

 4.- CONJUNTO DE INSTRUCCIONES.

 El juego completo de instrucciones reconocidas por los procesadores intel 8086 a 80286, junto con los coprocesadores 8087 y 80287, se enlistan en el  apendice 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.

 Los operandos permitidos se enlistan a continuación:

 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.

 Directos:  Aquí se debe especificar la dirección de memoria a accesar en la forma segmento:offset.

                                                            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

count sólo será válido si fue declarado con la directiva DW.

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

Registros: Cuando se hace referencia a cualquiera de los registros de propósito general, apuntadores, índices, o de segmento.

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:

                                                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]

Base-indexados: Un operador base-indexado representa una dirección de memoria relativa a la combinación de los registros de base e índice. Su sintaxis es:

                                               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]

 Estructuras:  Su sintaxis es variable.campo.  variable es el nombre con que se declaró la estructura, y campo es el nombre del campo dentro de la estructura.

                                                date STRUC

                                                        mes    DW  ?

                                                        dia      DW  ?

                                                         aa       DW ?

                                               date ENDS

                                               actual    date  ‹´ja´,´01´,´84´›

                                               mov      ax,actual.dia

                                               mov      actual.aa, ´85´

          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:

                                   expresión1  SHR contador

                                   expresión1  SHL contador

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

P 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  PTR expresión

 

tipo puede ser BYTE ó 1, WORD ó 2, DWORD ó 4, QWORD ó 8,  TBYTE ó 10, NEAR ó 0FFFFh, FAR ó 0FFFEh. Ejemplos:

                                                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:

 OFFSET expresión:  Regresa el desplazamiento del operando

                                                            mov   bx, OFFSET dgroup:array

                                                           mov   bx, offset subrout3

SHORT etiqueta: Para un salto de menos de 128 bytes

                                                  jmp      SHORT  loop

R LENGTH variable: Regresa el número de elementos de variable según su tipo

                                                   mov   cx,length array

R SIZE variable: Regresa el tamaño en bytes alojados para variable.

                                                   mov   cx,size array

R SEG expresión: Regresa el valor del segmento para expresión

                                                 mov   ax, SEG saludo

         5.- MACROS Y PROCEDIMIENTOS.

 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.

 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.

 MODULO PRINCIPAL: MAIN.ASM

             NAME      main

             PUBLIC    exit

             EXTRN     print:near

 stack    SEGMENT   word stack 'STACK'

             DW        64 DUP(?)

stack    ENDS

data      SEGMENT   word public 'DATA'

data      ENDS

code     SEGMENT byte public 'CODE'

             ASSUME  cs:code, ds:data

start:

             mov  ax,data             ; carga localizacion del segmento

             mov  ds,ax                ;   en el registro DS

             jmp   print                  ; va a PRINT en el otro modulo

exit:

            mov  ah,4ch

            int     21h

code    ENDS

            END  start

SUBMODULO: TASK.ASM

               NAME      task

               PUBLIC    print

               EXTRN     exit:near

data       SEGMENT   word public 'DATA'

entrada  DB        "Entrando a un submodulo....",13,10,"$"

salida     DB        ".......saliendo del submodulo.",01,07,13,10,"$"

data       ENDS

code      SEGMENT   byte public 'CODE'

              ASSUME    cs:code, ds:data

print:

              mov       ah,06h             ; Funcion para borrar pantalla

              mov       al,0                  ; todas las lineas

              mov       cx,0                 ; de 0,0

              mov       dh,24d

              mov       dl,79d

              mov       bh,0                 ; atributo en lineas vacias

              int          10h                  ; Servicio de e/s video

              mov       dx, OFFSET entrada

              mov       ah,09h

              int          21h

              mov       dx, OFFSET salida

              int          21h

              jmp        exit                 ; Regresa al otro modulo

code      ENDS

              END

La declaración de macros se hace a través de  las directivas MACRO y ENDM. Su sintaxis es:

 

                                   nombre MACRO [parámetros,,,]

                                                declaraciones

                                    ENDM

             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 cual 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 corresponden 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.