Capítulo VI: EL ENSAMBLADOR EN ENTORNO DOS
6.3 EJEMPLO DE PROGRAMA DE TIPO EXE.
Los programas EXE (listado en la página siguiente) requieren algo más de elaboración. En primer lugar, es necesario definir una pila y reservar espacio para la misma. Al contrario que los programas COM (cuya pila se sitúa al final del segmento compartido también con el código y los datos) esta característica obliga a definir un tamaño prudente en función de las necesidades del programa. Téngase en cuenta que en la pila se almacenan las direcciones de retorno de las subrutinas y al llamar a una función de la BIOS la pila es usada con intensidad. En general, con medio kilobyte basta para programas tan sencillos como el del ejemplo, e incluso para otros mucho más complejos. El límite máximo está en 64 Kb. El segmento de pila se nombra siempre STACK y con el TLINK de Borland es necesario indicar también la clase 'STACK'.
Como se ve, son definidos por separado el segmento de código, pila y datos, lo que también ayuda a estructurar más el programa. El segmento de código se define como procedimiento FAR, entre otras razones para que el ensamblador ensamble el RET del final (con el que se vuelve al DOS) como un RETF. La directiva ASSUME asocia cada registro de segmento con su correspondiente segmento. Como puede observarse al principio del programa, es necesario preparar «a mano» la dirección de retorno al sistema. El PUSH DS del
principio coloca el segmento del PSP en la pila; el XOR AX,AX coloca un cero en AX (esta instrucción gasta un byte menos que MOV AX,0) y el PUSH AX mete ese 0 en la pila. Con ello, al volver al DOS con RET (RETF en realidad) el control pasará a DS:0, esto es, a la primera instrucción del PSP (INT 20h). Aunque pueda parecer un tanto lioso, es un juego de niños y estas tres instrucciones consecutivas (PUSH DS / XOR AX,AX / PUSH AX) son la manera de empezar de cientos de programas EXE, que después acaban con RET. En general, a partir del DOS 2.0 es más aconsejable terminar el programa con la función 4Ch del DOS, que no requiere que CS apunte al PSP ni precisa de preparación alguna en la pila y además permite retornar un código de ERRORLEVEL en AL: en los programas futuros esto se hará con bastante frecuencia.
También debe observarse cómo se inicializa DS, ya que en los programas EXE por defecto no apunta a los datos. Ahora puede preguntarse el lector, por curiosidad, ¿qué valdrá «datos»?: datos tiene un valor relativo asignado por el ensamblador; cuando el programa sea cargado en memoria, en el proceso de montaje y en función de cuál sea la primera posición de memoria libre, se le asignará un valor determinado por el montador del sistema operativo.
┌───────────────────────────────────────────────────────────────────────┐ │ │ │ cr EQU 13 ├─┐ │ lf EQU 10 │ │ │ │ │ │ ; Segmento de datos │ │ │ │ │ │ datos SEGMENT │ │
│ texto DB cr,lf,"Texto a imprimir",cr,lf,"$" │ │
│ datos ENDS │ │
│ │ │
│ ; Segmento de pila │ │
│ │ │
│ pila SEGMENT STACK 'STACK' ; poner STACK es obligatorio │ │
│ DB 128 dup ('pila') ; reservados 512 bytes │ │
│ pila ENDS │ │
│ │ │
│ ; Segmento de código │ │
│ │ │
│ codigo SEGMENT │ │
│ ejemplo PROC FAR │ │
│ ASSUME CS:codigo, DS:datos, SS:pila │ │
│ │ │
│ ; poner dirección de retorno al DOS en la pila: │ │
│ │ │
│ PUSH DS ; segmento del PSP │ │
│ XOR AX,AX ; AX = 0 │ │
│ PUSH AX ; desplazamiento 0 al PSP │ │
│ │ │
│ ; direccionar segmento de datos con DS │ │
│ │ │
│ MOV AX,datos ; AX = dirección del segmento de datos │ │
│ MOV DS,AX ; inicializar DS │ │
│ │ │
│ ; escribir texto │ │
│ │ │
│ LEA DX,texto ; DS:DX = dirección del texto │ │
│ MOV AH,9 │ │
│ INT 21h │ │
│ │ │
│ ; volver al DOS │ │
│ │ │
│ │ │
│ ejemplo ENDP │ │
│ │ │
│ codigo ENDS ; fin del código │ │
│ END ejemplo ; punto de arranque del programa │ │
│ │ │ └─┬─────────────────────────────────────────────────────────────────────┘ │ │ Programa EXE │ └───────────────────────────────────────────────────────────────────────┘ 6.4. - PROCESO DE ENSAMBLAJE. 6.4.1. - TASM/MASM.
Es el programa que convierte nuestro listado fuente en código objeto, es decir, lenguaje máquina en el que sólo faltan las referencias a rutinas externas. Permite la obtención de listados de código y de referencias cruzadas (símbolos, etiquetas, variables). En general, bastará con hacer TASM nombre_programa (se supone la extensión .ASM por defecto). El fichero final tiene extensión OBJ. En general, la sintaxis del TASM y MASM es más o menos equivalente: en el primero se obtiene ayuda con /H y en el segundo con /HELP. Con TASM, cuando se va a obtener la versión definitiva del programa, o si éste es corto -o el ordenador rápido- merece la pena utilizar el parámetro /m3, con objeto de que de dos/tres pasadas y optimize más el código. Por su lado, MASM presenta estadísticas adicionales si se indica /v y se puede cambiar con /Btamaño el nº de Kb de memoria que destina al fichero fuente, entre 1 y 63. La sintaxis es (tanto para TASM como MASM):
TASM fichero_fuente, fichero_listado, fichero_referencias_cruzadas
Se puede omitir el fichero de listado y el de referencias cruzadas. Cuando se emplea MASM 6.X, para ensamblar los listados de este libro hay que indicar la opción /Zm para mantener la compatibilidad con las versiones anteriores del ensamblador, siendo además obligatorio indicar la extensión; como se genera directamente el fichero EXE hay que indicar /c si se desea evitar esto (si no se quiere que linke). La sintaxis quedaría:
ML /Zm fihero_fuente.asm
A continuación se listan los parámetros comunes a TASM 2.0 (y posterior) y MASM 4.0/5.0 (NO la 6.X): /a y /s Seleccionan un orden alfabético o secuencial de los segmentos.
/cGenera un listado de referencias cruzadas en un fichero de extensión CRF listo para ser procesado por CREF (MASM) añadiendo además números de línea al listado, o bien incluye el listado de referencias cruzadas directamente dentro del listado del programa (caso de TASM). Las referencias cruzadas son un listado de todos los símbolos del programa, indicando los números de línea del mismo en que son definidos y referenciados.
/DDe la manera /Dsímbolo[=valor] permite crear el símbolo indicado, cuya presencia puede comprobarse en el programa con una directiva IF (es útil para definir externamente un símbolo que indique que el programa está en fase de depuración, de cara a ensamblar cierto código adicional). Aunque /d (en minúsculas) es un obsoleto parámetro de MASM para obtener un listado de la primera pasada del ensamblador, MASM 4.0 es capaz de darse cuenta de que se pretende definir un símbolo con /d a menos que se indique solo /d.
/e Emula las instrucciones de punto flotante del 80x87, apoyándose en una librería al efecto.
/IrutaPermite indicar el directorio donde el ensamblador debe de buscar los ficheros indicados en el programa fuente con INCLUDE. /l[a] Con /l se genera un listado de ensamblaje y con /la un listado expandido.
/mCon /m se indica el nivel de preservación del sentido de mayúsculas y minúsculas en los símbolos: /ml hace que se consideres diferentes mayúsculas de minúsculas en todos los símbolos, /mx sólo con los símbolos globales y /mu hace que se mayusculicen todos los símbolos globales. Al ensamblar módulos para usar desde lenguaje C hay que indicar por lo menos /mx. En MASM 6.X se emplea /Cx en lugar de /mx, /Cp en lugar de /ml y /Cu en vez de /mu.
/n Suprime las tablas de símbolos en el listado.
/pVerifica que el código generado para el modo protegido es correcto (al emplear la directiva para generar instrucciones de modo protegido).
/t Suprime los mensajes si el ensamblaje es correcto.
/X Lista las condiciones falsas (ensamblaje condicional). /z Visualiza la línea del error y no sólo el número de la misma. /Zi Genera información simbólica para los depuradores de código. /Zd Incluye sólo la información del número de línea.