Make your own free website on Tripod.com

 

UNEFA Excelencia Educativa

 

 

III.- Creación y depuración de programas en lenguaje ensamblador

                                   1.- Edición.

                                   2.- Ensamblado.

                                   3.- Link.

                                   4.- Ejecución.

                                   5.- Depuración.

                                               5.1.- Uso de DEBUG de DOS I

                                                           5.2.- Uso de DEBUG II: Trazar un Programa

                                                           5.3.- Uso de DEBUG III: Escribir un Programa

                                               6.- La utilería exe2bin  y los archivos .exe y .com.

 


III.- CREACION Y DEPURACION DE PROGRAMAS EN LENGUAJE ENSAMBLADOR

 

            1.- EDICION.

 Los archivos fuente de código ensamblador  deben estar en formato ASCII standard. Para esto puede usarse cualquier editor que permita crear archivos sin formato, e.g. Edlin, Edit, Write, El editor del Turbo Pascal, Works, Word, WordStar, etcétera. Las declaraciones  pueden ser introducidas en mayúsculas y/o minúsculas. Una buena práctica de programación es poner todas las palabras reservadas (directivas e instrucciones) en mayúsculas y todo lo del usuario en minúsculas para fines de facilidad de lectura del código. Las sentencias pueden comenzar en cualquier columna,  no pueden tener más de 128 caracteres, no se permiten lineas múltiples ni códigos de control, y cada línea debe ser terminada con una combinación  de line-feed y carriage-return. Los comentarios se declaran con ; y terminan al final de la línea.

            2.- ENSAMBLADO.

 El ensamblado se lleva a cabo invocando al MASM. Este puede ser invocado, usando una línea de comando,  de la siguiente manera:

MASM archivo [,[objeto][,[listado][,[cross]]]]][opciones][;]

donde:

Archivo

Corresponde al programa fuente. Por default se toma la extensión .ASM.

Objeto

Es el nombre para el archivo objeto.

Listado

Nombre del archivo de listado de ensamblado.

Cross

Es un archivo de referencias cruzadas.

 

Opciones4Pueden ser:

 

/A:

escribe los segmentos en orden alfabético

/S:

escribe los segmentos en orden del fuente

/Bnum:

fija buffer de tamaño num

/C:

especifica un archivo de referencias cruzadas

/L:

especifica un listado de ensamble

/D:

crea listado del paso 1

/Dsym:

define un símbolo que puede usarse en el ensamble

/Ipath:

fija path para búscar archivos a incluir

/ML:

mantiene sensitividad de letras (mayús./minús) en nombres

/MX:

mantiene sensitividad en nombre publicos y externos

/MU:

convierte nombres a mayúsculas

/N:

suprime tablas en listados

/P:

checa por código impuro

/R:

crea código para instrucciones de punto flotante

/E:

crea código para emular instrucciones de punto flotante

/T:

suprime mensajes de ensable exitoso

/V:

despliega estadísticas adicionales en pantalla

/X:

incluir condicionales falsos en pantalla

/Z:

despliega líneas de error en pantalla

     

si el ; al final se omite es necesario poner todas las comas que se indican. Si no se quiere poner algún valor basta con dejar la coma.

La otra forma de invocar al ensamblador es sólo tecleando MASM y respondiendo a la información que se solicita. Para  omitir algún valor sólo basta teclear ENTER si dar ningún valor.

           3.- LINK.

De la misma forma que el ensamblado, la fase de liga se lleva a cabo con el LINK. Este puede ser invocado de la misma forma que el MASM.  Los parámetros que este requiere son:

 LINK objeto [,[ejecutable][,[mapa][,[librería]]]]][opciones][;]

 donde: 

objeto

Es el nombre para el archivo .OBJ

ejecutable

Nombre del archivo .EXE

mapa

Nombre del archivo mapa

librería

Nombre del archivo biblioteca de rutinas

 

opciones     ®      Pueden ser:

 

/HELP

muestra lista de opciones

/PAUSE

pausa en el proceso

/EXEPACK

empaca archivo ejecutable

/MAP

crea mapa se símbolos públicos

/LINENUMBERS

copia número de lineas al mapa

/NOIGNORECASE

mantiene sensitividad en nombres

/NODEFAULTLIBRARYSEARCH

no usa bibliotecas por default

/STACK:size

fija el tamaño del stack a usar

/CPARMAXALLOC:número

fija alojación máxima de espacio

/HIGH

fija la dirección de carga más alta

/DSALLOCATE

aloja grupo de datos

/NOGROUPASSOCIATION

ignora asociaciones para direcciones

/OVERLAYINTERRUPT:número

asigan nuevo número a la INT 03Fh

/SEGMENTS:número

procesa un número de segmentos

/DOSSEG

sigue la convención de orden de DOS

     

 4.- EJECUCION.

 Para la ejecución del programa simplemente basta teclear su nombre en el prompt de MS-DOS y teclear ENTER. Con esto el programa será cargado en memoria y el  sistema procederá a  ejecutarlo. El proceso completo para poder crear un programa ejecutable con el Microsoft Macro Assembler se muestra abajo.

 

 

 

 

 y lo que se vería en pantalla sería lo siguiente:

C:\DATA\PROGRAMS\ASM>masm main

Microsoft (R) Macro Assembler  Version 4.00

Copyright (C) Microsoft Corp 1981, 1983, 1984, 1985.  All rights reserved.

Object filename [main.OBJ]:

Source listing  [NUL.LST]:

Cross-reference [NUL.CRF]:

  50966 Bytes symbol space free

      0 Warning Errors

      0 Severe  Errors

C:\DATA\PROGRAMS\ASM>masm task

Microsoft (R) Macro Assembler  Version 4.00

Copyright (C) Microsoft Corp 1981, 1983, 1984, 1985.  All rights reserved.

Object filename [task.OBJ]:

Source listing  [NUL.LST]:

Cross-reference [NUL.CRF]:

  51034 Bytes symbol space free

      0 Warning Errors

      0 Severe  Errors

C:\DATA\PROGRAMS\ASM>link main+task

Microsoft (R) 8086 Object Linker  Version 3.05

Copyright (C) Microsoft Corp 1983, 1984, 1985.  All rights reserved.

Run File [MAIN.EXE]:

List File [NUL.MAP]:

Libraries [.LIB]:

 C:\DATA\PROGRAMS\ASM>main

Entrando a un submodulo....

.......saliendo del submodulo.

 C:\DATA\PROGRAMS\ASM>

             5.- DEPURACION.

 Para la depuración de un programa en ensamblador tenemos disponibles dos herramientas. Por un lado tenemos el debuger que nos proporciona MS-DOS (DEBUG.EXE) y por otro lado tenemos el que nos proporciona Microsoft (SYMDEB.EXE).  Este último trabaja igual que el de MS-DOS pero nos proporciona muchas ventajas más. Una de ellas es la facilidad de desplegar el código fuente correspondiente a la instrucción que se esta ejecutando (si el programa ejecutable fue ensamblado o compilado con un ensamblador o compilador compatible), nos permite ejecutar comandos del S.O. y nos permite obtener información de las interrupciones de manera simbólica.

 5.1.- Uso de DEBUG de DOS I

 DOS y la cónsola de Windows incluyen un programa llamado DEBUG.  La palabra  "debug"   podríamos  traducirla   por  depurador,   aunque realmente ésta no  sea  su traducción  literal.  Supuestamente,  un depurador está destinado a la eliminación de  "bichos",  cucarachas en nuestros programas.  Es una especie de insecticida. En  terminos de programación, podemos decir que se trata de un programa para  la localización de errores que afectan sensiblemente el funcionamiento de nuestros programas.

 Con el depurador podemos revisar paso  por  paso  la  ejecución  de nuestro programa, revisar como va modificándose el contenido de los registros hasta ubicar donde está el "bicho" y aplastarlo. Imagino  que el  nombre  se debe a que los primeros desperfectos en las primeras máquinas de cómputo electrónicas se debía generalmente a la presencia de cucarachas u otros insectos en  los circuitos  de tubos. El  debug de  DOS  permite,  entre  otras  cosas,  editar  archivos ejecutables y hasta escribir programas en lenguaje ensamblador.

 5.2.- Uso de DEBUG II: Trazar un Programa

 DEBUG,  como  muchos  otros programas auxiliares de la programación, trabaja con el sistema hexadecimal. Para  ejecutar DEBUG escribimos DEBUG en el puntero de DOS o en  el cuadro de ejecución (Inicio-Ejecutar) de Windows. Aparecerá sólo un guión que es el apuntador del DEBUG.

 Las diversas operaciones del DEBUG se indican con letras: 

A:

assemble (ensamblar)

C:

compare (comparar)

D:

display (desplegar)

E:

enter (ingresar)

F:

full (llenar)

G:

go (ir)

H:

Hexadecimal

I:

in (entrada)

L:

load (cargar)

M:

move (mover)

N:

name (nombrar)

O:

out (salida)

P:

proceed (proceder)

Q:

quit (quitar)

R:

register (registro)

S:

search (buscar)

T:

trace (trazar o rastrear)

U:

unassemble (desensamblar)

W:

write (escribir)

                                 

Ahora no las usaremos todas. Antes de escribir un programa con DEBUG, primero revisemos  el  que escribimos al comienzo. Cargamos el programa en el DEBUG:

                      C:\tasm\works\primer>DEBUG primer.exe

 Ahora ingresamos la orden T, y obtendremos:

 -t

 AX=0E00  BX=0000  CX=0020  DX=0000  SP=0040  BP=0000  SI=0000  DI=0000

DS=0DEF  ES=0DEF  SS=0E01  CS=0DFF  IP=0003   NV UP EI PL NZ NA PO NC

0DFF:0003 8ED8          MOV     DS,AX

 Se  trata  de  la  segunda  instrucción  de  nuestro  programa.  La instrucción MOV  es  una instrucción  de  asignación,  parecida  al operador "=" de C.  Lo que hace es mover el contenido  del  segundo operando,  aquí AX,  que  para  el momento  tiene la  dirección del segmento de datos, al primer operando, DS. Antes de la ejecución de la instrucción DS=0DEF y AX=0E00; son valores hexadecimales.

 Ingresemos de nuevo t:

 -t

 AX=0E00  BX=0000  CX=0020  DX=0000  SP=0040  BP=0000  SI=0000  DI=0000

DS=0E00  ES=0DEF  SS=0E01  CS=0DFF  IP=0005   NV UP EI PL NZ NA PO NC

0DFF:0005 BA0400        MOV     DX,0004

 Ahora DS=0E00. Se ha movido lo que estaba en AX a DS.

 La siguiente instrucción mueve a DX la dirección de nuestra cadena. Habíamos escrito "LEA DX, string";  DEBUG  lo ha interpretado  como "mover  a DX lo que está 4 bytes por encima del inicio del segmento de datos".

Si se quiere verificar escribimos:  D DS:4, desplegar lo que está 4 bytes después del inicio del segmento de datos:

 -d ds:4

0E00:0000              48 6F 6C 61-20 67 65 6E 74 65 21 24       Hola gente!$

0E00:0010  C7 06 0E 96 3E 2B 2E C7-06 10 96 3D 3B E8 83 09   ....>+.....=;...

0E00:0020  73 13 B8 FF FF 53 26 8B-1D 26 3A 0F 73 03 B8 02   s....S&..&:.s...

 En  0E00+4  vemos el código hexadecimal correspondiente  a  nuestra cadena, que en la columna derecha podemos ver en ASCII. Podemos ver que el símbolo "$", que originalmente habíamos introducido como 36, y que en la columna del medio aparece en hexadecimal: 24.

 Sigamos trazando:

 -t

 AX=0E00  BX=0000  CX=0020  DX=0004  SP=0040  BP=0000  SI=0000  DI=0000

DS=0E00  ES=0DEF  SS=0E01  CS=0DFF  IP=0008   NV UP EI PL NZ NA PO NC

0DFF:0008 B409          MOV     AH,09

 Ahora moveremos a la parte superior de AX un nueve:

 -t

 AX=0900  BX=0000  CX=0020  DX=0004  SP=0040  BP=0000  SI=0000  DI=0000

DS=0E00  ES=0DEF  SS=0E01  CS=0DFF  IP=000A   NV UP EI PL NZ NA PO NC

0DFF:000A CD21          INT     21

 Vemos  ahora  que  "AX=0900",  que confirma que hemos movido 9 a la parte alta de AX. Se va a ejecutar la interrupción 21h, servicio  9;  recuérdese  que anteriormente  habíamos   escrito  "int  33".   DEBUG   trabaja  en hexadecimal, así que despliega la versión  hexadecimal  de  lo  que escribamos. Para trazar por encima de la interrupción,  no es bueno usar T,  ya que esto nos llevará a la rutina de la interrupción.  Así que mejor ejecutamos P:

 -p

Hola gente!

AX=0924  BX=0000  CX=0020  DX=0004  SP=0040  BP=0000  SI=0000  DI=0000

DS=0E00  ES=0DEF  SS=0E01  CS=0DFF  IP=000C   NV UP EI PL NZ NA PO NC

0DFF:000C B410          MOV     AH,10

 Esto despliega nuestra cadena en la cónsola.  Observa que todos los registros se han mantenido  iguales  después  de  la  interrupción, excepto AX; vemos que en la parte baja de AX, que es AL, aparece un 24h. Se trata del caracter "$".  Quiere decir que el servicio  9 de la interrupción 21h usa el registro AL para revisar la cadena.

 Ahora moveremos 16 a AH. DEBUG lo presenta como "MOV AH, 10"; 10h = 16 decimal.

 Hacemos T:

 AX=1024  BX=0000  CX=0020  DX=0004  SP=0040  BP=0000  SI=0000  DI=0000

DS=0E00  ES=0DEF  SS=0E01  CS=0DFF  IP=000E   NV UP EI PL NZ NA PO NC

0DFF:000E CD16          INT     16

 Se va a ejecutar el servicio 16 o 10h de la interrupción 22 o  16h. Lo  que  hace  este  servicio es esperar a que el usuario  pulse un tecla, cuyo código devuelve en AH el código  de rastreo y en  AL el código de la tecla.  Para  teclas de función  extendida  (F1 - F12) más importante es el código de rastreo, ya que como código de tecla se pasará 00, para las teclas F1-F12, E0h para para otras teclas de control como Inicio o RePag. Hacemos P y se ejecuta nuestra pausa;  pulsamos cualquier tecla, en mi caso "enter":

 -p

 AX=1C0D  BX=0000  CX=0020  DX=0004  SP=0040  BP=0000  SI=0000  DI=0000

DS=0E00  ES=0DEF  SS=0E01  CS=0DFF  IP=0010   NV UP EI PL NZ NA PO NC

0DFF:0010 B80000        MOV     AX,0000

 Código de rastreo en AH=1Ch y código de tecla en AL=0Dh. Ahora vamos a salir. Devolvemos el valor FALSE (=0) en AX a DOS. Un simple gesto de educación.

 AX=0000  BX=0000  CX=0020  DX=0004  SP=0040  BP=0000  SI=0000  DI=0000

DS=0E00  ES=0DEF  SS=0E01  CS=0DFF  IP=0013   NV UP EI PL NZ NA PO NC

0DFF:0013 CB            RETF

 Por  último regresamos a DOS.  La instrucción RETF es una operación ceroaria (sin operandos) que significa RETURN FAR,  regresar lejos. Recordemos que hemos declarado nuestro procedimiento así:

 main                               proc far

 "main" es el nombre del procedimiento;  proc  es  una directiva que indica inicio de una rutina o  función (en  ensamblador  se  llaman procedimientos a las funciones) y far indica  que el  procedimiento puede ser accedido desde otros  segmentos.  La instrucción RETF nos saca del procedimiento a la línea de órdenes.

 Hay otras formas más limpias de salir de un programa, pero con esta basta por ahora. Nota  también  que  a  la  derecha  de cada  instrucción aparece la dirección en memoria, en sistema hexadecimal, de la instrucción,  y luego es seguida por otra cifra en hexadecimal.  Ésta  última es la versión en hexadecimal del código de operación  de la  instrucción. Si no fuera por el sistema hexadecimal,  ahí  aparecerían una serie de unos y ceros. Con DEBUG también  podemos obtener el código fuente en  ensamblador de un fichero ejecutable.  Esto  se  hace  cargando  el  fichero  y ejecutando la orden U,  seguida por la dirección  a  partir  de  la cual queremos que se inicie el desensamblado y el número de  líneas a desensablar:

                             -u cs:00

                           0CE2:0000 B8E30C        MOV     AX,0CE3

                            0CE2:0003 8ED8          MOV     DS,AX

                            0CE2:0005 BA0400        MOV     DX,0004

                            2:0008 B409          MOV     AH,09

                            0CE2:000A CD21          INT     21

                            0CE2:000C B410          MOV     AH,10

                           0CE2:000E CD16          INT     16

                            0CE2:0010 B80000        MOV               AX,0000

                           0CE2:0013 CB            RETF

 Tememos aquí un desensamblado de nuestro  código.  También  podemos obtener este desensamblado en un archivo de texto aparte:

 DEBUG primer.exe<U CS:00 14>prim.asm

 Esta orden volcará en prim.asm  un desensablado de  los primeros 20 (14h) bytes de primer.exe, que podemos revisar con un editor.

 Podríamos escribir un fichero disasm.bat:

                            cls

                            @echo off

                            @echo U CS:00 13>t_$

                             @echo.>>t_$

                            @echo Q>>t_$

                            2echo.>>t_$

                            @echo Desensamblando....

                            DEBUG %1.exe<t_$>%1_.asm

                            EDIT %1_.asm

 Luego ejecutamos:

                            disasm primer

 y ya! Podríamos mejorarlo  para  que  desensamble  exactamente  las instruccciones del segmento de código;  e incluso hacer que nuestro .BAT cambie algunos valores hexadecimales en el .EXE,  pero  no  es este el lugar para explicarlo.

                    

5.3.- Uso de DEBUG III: Escribir un Programa

 Escribamos ahora un programa con el DEBUG.  Antes  una  observación sobre el formato de los ejecutables en DOS.  Existen dos  formatos: .COM y .EXE.  El primero, .COM,  es el formato  original.  En  este formato, todo, código y datos, es puesto en un único segmento  cuyo tamaño no debe exceder los 64KB. En el formato .EXE,  que es posterior,  se reserva un segmento para datos, uno para código y uno para la pila. Nuestro programa fue escrito en  formato  .EXE.  Con  el  DEBUG  se pueden  escribir  programas en formato .COM,  que  son bastante más pequeños. El programa debe comenzar a ejecutarse en la dirección 256  [100h], ya que los ejecutables de DOS reservan los primeros 256 bytes  para colocar  ahí una estructura de datos conocida como PSP,  cuando  es cargado en la memoria. El PSP (Program Segment Prefije:  Prefijo de Segmento del Programa)  contine  información que será utilizada por el cargador de DOS.

 Así que comenzamos escribiendo A 100 [enter]

 

                              -a 100

                              0DAB:0100 jmp 108

                              0DAB:0102 db "hola$"

                             0DAB:0107 nop

                              0DAB:0108 mov dx,102

                              0DAB:010B mov ah,9

                             0DAB:010D int 21

                             0DAB:010F mov ah,10

                              0DAB:0111 int 16

                             0DAB:0113 int 20

                              0DAB:0115

 La  primera  instrucción ,"jmp 108",  es  saltar a la localidad 108. Necesitamos un espacio para  nuestra  cadena.  Como  no  sabemos  el tamaño del programa y no podemos determinar al comienzo donde estará la  cadena si la ponemos  al  final  del  programa,  la  ponemos  al comienzo, y para evitar  que el programa comience a ejecutarse en la cadena (lo que daría error)  le pasamos por encima.  La  instrucción jmp 108 tiene dos bytes,  reservamos  6  bytes  para  la cadena. Nos sobra un byte, así que escribimos "nop" en 107, un operador ceroario que significa No operación. Vemos  que  la  dirección de la cadena es 102,  así que ponemos este valor en  DX;  luego  llamamos  al  servicio  9  (mov ah, 9)  de  la interrupción 21h (int 21) para desplegar la cadena. Luego ejecutamos el servicio 10h de la interrupción 16h (que detiene la ejecución del programa) y por último regresamos a DOS con la interrupción 20h. Pulsamos dos veces enter para salir de la orden A.

 Podemos probar de inmediato nuestro programa con la orden G:

 -g

hola

 Debes pulsar ENTER para salir del programa. Se desplegará:

 "El programa ha finalizado con normalidad"

 Ahora debemos escribir nuestro programa a un archivo .COM en  disco. Para ello empleamos la orden W [write]. Para realizar esta escritura correctamente,  primero  elegimos  un  nombre para el programa, como pr.com. La extensión debe ser .COM, porque W no puede crear ejecutables con formato .EXE; la orden es:

-n pr.com

También debemos especificar el tamaño de nuestro ejecutable. W creará un archivo con el tamaño indicado en  el  registro  CX;  así  que deberíamos  poner en CX el tamaño de nuestro ejecutable empleando la orden R:

-r cx

CX 0000

:15

Finalmente, escribimos el archivo en disco:

-w

Escribiendo 00015 bytes

Eso es todo. Luego lo ejecutamos. Salimos de DEBUG:

-q

y ejectamos pr.com:

hola

¿Todo bien?

Observa el tamaño del archivo: 21 bytes (15h = 21 decimal)

           6.- LA UTILERIA EXE2BIN  Y LOS ARCHIVOS .EXE Y .COM.

Para MS-DOS sólo existen dos tipo de archivos ejecutables los .COM y .EXE. Ambos archivos difieren en algunas cosas. Primero, las ventajas de los .EXE son dobles, nos permiten tener archivos reubicables y el uso de hasta cuatro segmentos (STACK, DATA, EXTRA y CODE) de hasta  64KB cada uno. Un archivo .COM sólo puede tener un segmento de 64KB, en el que se tiene tanto código como pila,  y datos. La desventaja de los .EXE es que agregan 512 bytes como cabecera con información para la reubicación del código. Un .COM no es reubicable, siempre inicia en la dirección 0100H.

   Si nuestro programa no es muy grande  64KB son mas que suficientes. Por lo que conviene crearlo como .COM, para esto se cuenta con la utilería  EXE2BIN.EXE que nos proporciona el sistema operativo. y que nos permite crear .COM a partir de .EXE . Las restricciones para esto son las suguientes: el archivo a convertir no debe estar empacado, no debe tener segmento de stack, debe tener sólo segmento de código y su tamaño debe ser menor a 64KB.