Volver menú revistas Volver página anterior

El Amiga Me Encanta ha conseguido el permiso por escrito de IDG Comunications España para ofrecer los artículos de la revista Amiga World España.


N° 8 - Marzo 1990 Amiga World

INICIACION AL LENGUAJE

ENSAMBLADOR
SISTEMA DE FICHEROS (II) y 10.ª Parte

Por Fernando G. Terradillos

En el pasado capítulo vimos cómo los ficheros
del Amiga se almacenaban en el disco, en un
complejo pero seguro sistema de bloques.
También vimos como este sistema se
organizaba mediante la estructura de árbol
con el directorio principal cómo base de éste.
En esta segunda parte explicaremos cómo se
carga en memoria un fichero ejecutable (no
confundir con fichero de datos), es decir, el
que se ejecuta automáticamente después de
su carga, bien mediante CLI o bien mediante
Workbench.

Este tema ha sido desde hace muchísimo tiempo como un tabu, pues no había información alguna de como realmente se ejecutaba un fichero.

Antes de insertarnos de lleno en el tema daré algunas nociones básicas para su posterior utilización y entendimiento, en el que resalto el tema de la multitarea, en el cual varios programas se ejecutan independientemente. Aquí juega un papel importantísimo el sistema de ficheros desarrollado para este fin. Nunca antes un ordenador personal había conseguido mayor agilidad a la hora de almacenar y ejecutar varios programas sin que se cruzasen en memoria. Esto es gracias a la relocalización del programa, es decir, la propiedad para cambiar las direcciones de las instrucciones que utilicen posiciones absolutas. Por eso a la hora de programar nunca se aconseja utilizar posiciones absolutas o fijas, sino utilizar la correspondiente etiqueta.

Otro tema a tener en cuenta es el de la memoria. El Amiga chequea las diferentes partes de que consta el programa para reservar la memoria para su posterior lectura, debiendo haber mucha organización, pues cualquier mínimo error a la hora de esta asignación haría aparecer el consiguiente guru. Siempre se recomienda dejar tranquilo al Amiga a la hora de cargar ficheros y no intentar abrir directorios en el Workbench o hacer cosas extrañas. También hemos de ver algunas definiciones que utilizaremos posteriormente, como son las referencias externas, los ficheros objeto, ficheros de lectura o ejecutables, las unidades de programa y los hunks. Las referencias externas son como su nombre indica referencias producidas directamente por el ensamblador, que posteriormente van a ser utilizadas por otra parte del programa y unidas por el Linker. Le aconsejo repasar los programas de los primeros capítulos en los que se insertaban referencias XREF para que la parte principal del programa las utilizara. Simplemente es un medio de comunicación entre las distintas partes de las que puede constrar un programa. Los ficheros objeto son los producidos directamente por el ensamblador o compilador, no por el Linker, pudiendo contener como antes hemos dicho, referencias externas para unirse con más ficheros objetos. Contiene asimismo unidades de programa. Los ficheros de lectura o ejectuables son aquellos que contienen uno o más ficheros objetos que no debe contener referencias externas sin resolver. Es en resumen cualquier programa que se introduce mediante CLI o Workbench.

Las unidades de programa son los elementos más pequeños que puede manejar el Linker. Estos pueden contener uno o más hunks (ahora los veremos) y de referencias externas sin resolver. Quizá el elemento más importante sea el hunk, que traducido diríamos trozo o porción. Consiste en un bloque de código o datos, información para relocalizar o una lista de referencias externas. Puede contener datos inicializados o no inicializados (BBS, no aptos para relocalización). También puede contener símbolos, es decir, el nombrar internamente las mismas etiquetas y subrutinas que utilizamos a la hora de realizar cualquier programa. Esto es valiosísimo para un posterior estudio detallado con el Debugger, un programa destinado a la visualización de la ejecución paso a paso del fichero ejectutable. El Debugger más recomendado es el Metascope Debugger de Metadigm versión 1.11, pero también hay otros incluso de dominio público. Detallándonos en este mismo, permite visualizar la ejecución de un programa mnemónico por mnemónico o por niveles (saltándose las subrutinas). También es posible ver todos los registros cada vez que se hace el seguimiento, así como poner puntos de parada para que al ejecutar el programa se pare en los puntos prefijados.

El amiga chequea las diferentes partes
de que consta el programa para reservar la
memoria para su posterior lectura,
debiendo haber mucha organización, pues
cualquier mínimo error de esta asignación
haría aparecer el consiguiente guru.

Volviendo a los Hunks, hayq ue decir que existen diferentes y variados tipos, unos dentro de los ficheros objeto y otros ya dentro de los ficheros ejecutables o de lectura. Cada uno de estos hunks lleva una larga palabra como identificación. En el cuadro 1 está la relación de hunks con sus códigos.

Para que toda esta teoría pueda resultar monótona hemos ilustrado la estructura de los ficheros objetos y ejecutables con un ejemplo a seguir por cualquier persona. Antes de repasar todos los tipos de hunks hemos de decir que todo su manejo de números se hace en largas palabras.

CUADRO 1
FICHEROS OBJETO
---------------
DECIMAL  HEXADECIMAL
--------------------
999      $3E7       hunk unidad
1000     $3E8       hunk nombre
1001     $3E9       hunk código
1002     $3EA       hunk datos
1003     $3EB       hunk bbs
1004     $3EC       hunk relocalización 32 bits
1005     $3ED       hunk relocalización 16 bits
1006     $3EE       hunk relocalización 8 bits
1007     $3EF       hunk referencia externa
1008     $3F0       hunk símbolo
1009     $3F1       hunk información debugger
1010     $3F2       hunk final
FICHEROS EJECUTABLES
--------------------
1011     $3F3       hunk cabecera fichero
1013     $3F5       hunk overlay
1014     $3F6       hunk parada o break
 
CUADRO 2
0000: 000003F3 00000000 00000002 00000000     ................
0010: 00000001 00000018 0000000F 000003E9     ................
0020: 00000018 2C780004 23CE0000 004A43F9     ....,x..#....JC.
0030: 00000052 4EAEFE68 23C00000 004A43F9     ...RN..h#....N,y
0040: 0000004E 4EAEFFCA 23C00000 00424EAE     ...NN...#....BN.
0050: FFC423C0 00000046 2C790000 004A4EB9     ..#....F,y...JN.
0060: 00000000 4E750000 00000000 00000000     ....Nu..........
0070: 00000000 0000646F 732E6C69 62726172     ......dos.librar
0080: 79000000 000003EC 00000007 00000000     y...............
0090: 00000036 00000030 00000026 0000001C     ...6...0...&....
00A0: 00000016 0000000C 00000006 00000001     ................
00B0: 00000001 0000003C 00000000 000003F2     .......<........
00C0: 000003E9 0000000F 243C0000 0026263C     ........$<...&&<
00D0: 00000014 22390000 00422C79 0000004E     ...."9...B,y...N
00E0: 4EAEFFD0 13FC0002 0000003a 4E750A45     N..........:Nu.E
00F0: 73746F20 65732065 6C206D65 6E73616A     sto es el mensaj
0100: 650A0000 000003EC 00000002 00000001     e...............
0110: 00000020 00000002 00000002 00000000     ................
0120: 0000000E 00000014 00000000 000003F2     ................
OFFSET        CONTENIDO   COMENTARIO
------------------------------------
$0000         $000003F3   hunk cabecera del fichero
$0004         $00000000   no hay librerias
$0008         $00000002   numero de hunks = 2
$000C         $00000000   primer hunk
$0010         $00000001   segundo y ultimo hunk
$0014         $00000018   tamaño del hunk 0 en largas palabras
$0018         $0000000F   tamaño del hunk 1
$001C         $000003E9   código hunk 0
$0020         $00000018   longitud hunk
$0024-$0083   .........   código correspondiente al hunk 0
$0084         $000003EC   hunk relocalización 32 bits hunk 0
$0088         $00000007   numero de offsets
$008C         $00000000   hunk 0
$0090-$00A7   .........   offsets a relocalizar
$00AC         $00000001   número de offsets
$00B0         $00000001   en hunk 1
$00B4         $0000003C   offset 1
$00B8         $00000000   fin de la relocalización
$00BC         $000003F2   final del hunk 0
$00C0         $000003E9   código del hunk 0
$00C4         $0000000F   longitud del hunk (largas palabras)
$00C8-$0103   .........   código hunk 2
$0104         $000003EC   relocalización 32 bits
$0108         $00000002   número de offsets
$010C         $00000001   en hunk 1
$0110         $00000020   offset 1
$0114         $00000002   offset 2
$0118         $00000002   número de offsets
$011C         $00000000   en hunk 0
$0120         $0000000F   offset 1
$0124         $00000000   offset 2
$0128         $00000000   fin de lista
$012C         $000003F2   final hunk relocalización y fichero


Estructuras de ficheros objeto

Como hemos dicho antes, un fichero objeto es el producido por un ensamblador o un compilador de cualquier lenguaje. Consiste en una o más unidades de programa y puede contener referencias externas que posteriormente las resolverá el Linker. Cada una de estas unidades comienza con una cabecera (hunk unidad) continuada por una serie de hunks, y cada uno de éstos contienen un número de bloques de varios tipos. Cada uno de estos bloques comienza con una larga palabra que lo define. Estos hunks tienen la siguiente estructura de bloques: bloque hunk nombre - bloque relocalizable de código - bloque de referencias externas - tabla de símbolos - bloque del debugger - bloque de fin. No es necesario que aparezcan todos estos tipos de bloques, exceptó el de fin. Ahora pasemos a repasar cada tipo de los bloques descritos anteriormente, pero con una aclaración que vale de referencia al resto de las explicaciones: Todos los números que indiquen el tipo de bloque o hunk, así como los que indiquen longitudes ya sea de nombres como de bloques de datos, offsets o tamaños, se encuentran siempre en el formato de larga palabra, es decir cuatro bytes. Por ejemplo el tipo que define el hunk final se representa en $000003F2, lo mismo ocurre para todos los tipos. También he de decir que para todos los valores (longitudes, offsets, etc.) hay que multiplicar por 4 ya que su representación como hemos dicho es en largas palabras. Por ejemplo, si la longitud del bloque de código contiene $00000010 su valor real es el de $40 (más tarde los vreremos en los ejemplos).

Hunk unidad
Es el comienzo de una unidad de programa, consiste en una larga palabra conteniendo el valor $3E7, de la longitud del nombre de la unidad y el nombre del bloque.

Hunk nombre
Define el nombre de un hunk, siendo opcional. Comienza por el valor $3E8 y continúa de la misma forma que continuaba el hunk de unidad.

Hunk código
Contiene bloque de código que va a ser leído en memoria para su posterior u opcional relocalización. El formato comienza por el tipo de hunk ($3E9) seguido de la longitud en largas palabras y del código a leer.

Hunk data
Bloque de datos inicializados para ser leídos en memoria con posterior relocalización y pueden ser parte de un nudo de overlay que más tarde explicaremos. Lleva exactamente la misma estructura que el anterior tipo de hunk.

Hunk BSS
Aquí se especifica un bloque de datos no inicializados, es decir no se puede relocalizar, el cual reservará una porción de memoria (rellena de ceros) indicando por el único dato que hay en su estructura.

Hunk relocalización 32 bits
Este tipo de bloques especifica una relocalización normal de 32 bits o larga palabra dentro de un tipo de bloque que lo permite (ya hemos visto alguno). Cada hunk dentro de la unidad es numerado desde cero, para que posteriormente el Linker calcule la posición a partir de la cual se encuentre cada uno de los elementos relocalizables, es decir, partes de nuestro programa que contengan posiciones absolutas de memoria. Todas estas posiciones se denominan off-sets y son agrupadas para su posterior agrupación a cada hunk dentro de la unidad.

Cada una de estas
unidades comienzan con
una cabecera
(hunk unidad) continuada
por una serie de hunks, y
cada uno de estos
bloques comienza con
una larga palabra que lo
define.

Hunk relocalización 16 bits
Tiene la misma estructura que el anterior tipo de hunk, con la simple diferencia que el contador es en 16 bits y no en 32.

Hunk relocalización 8 bits
Con la misma estructura que los dos anteriores tipos.

Hunk referencia externa
Este bloque contiene referencias externas que posteriormente resuelve el Linker. Para recordarlo de nuevo estas referencias eran producidas por las directivas del ensamblador XREF y XDEF destinadas a producir una unión de rutinas y datos con los programas que realizábamos. La estructura de este hunk es la siguiente: Una larga palabra con el primer byte indicativo del tipo de directiva utilizado. Los restantes tres bytes contienen la longitud del nombre, el nombre de la referencia o definición, offset de la definición. 0 en larga palabra indicativo del fin del hunk. El byte indicativo del tipo de directiva siendo: 1 para la definición externa XDEF 129 para la definición externa.

Xref Hunk simbolo
Se utiliza para insertar una tabla de símbolos que posteriormente utilizará el debugger para su investigación más profunda del programa. Estos símbolos corresponden al etiquetado que se ha llevado a cabo en la realización del mismo programa, así tendrá constancia de esto mismo cuando ejecute poco a poco el mismo.

Hunk información Debugger
También se utiliza para su posterior visualiación con el debugger, permitiéndose insertar información adicional (REM en Basic) en las complicadas estructuras del Amiga. Esto no hace que el Amigados lo cargue en memoria a la hora de ejecturar el programa, simplemente es el debugger el que lo inserta.

Hunk final
Indica el final de un hunk, consistiendo simplemente es una doble palabra conteniendo el valor $3F2.

CUADRO 3
HUNK 0
------
$40000:   2C780004 23CE0000 004A43F9 00000052
$40010:   4EAEFE68 23C00000 004E2C79 0000004E
$40020:   4EAEFFCA 23C00000 00424EAE FFC423C0
$40030:   00000046 2c790000 004A4EB9 00000000
$40040:   4E750000 00000000 00000000 00000000
$40050:   0000646F 732E6C69 62726172 79000000
HUNK 1
------
$41000:   243C0000 0026263C 00000014 22390000
$41010:   00422C79 0000004E 4EAEFFD0 13FC0002
$41020:   0000003A 4E750A45 73746F20 65732065
$41030:   6C206D65 6E73616A 650A0000
 
CUADRO 4
HUNK 0
------
$40000:   2C780004 23CE0004 004A43F9 00040052
$40010:   4EAEFE68 23C00004 004E2C79 0004004E
$40020:   4EAEFFCA 23C00004 00424EAE FFC423C0
$40030:   00040046 2C790004 004A4EB9 00041000
$40040:   4E750000 00000000 00000000 00000000
$40050:   0000646F 732E6C69 62726172 79000000
HUNK 1
------
$41000:   243C0004 1026263C 00000014 22390004
$41010:   00422C79 0004004E 4EAEFFD0 13FC0002
$41020:   0004103a 4E750A45 73746F20 65732065
$41030:   6C206D65 6E73616A 650A0000
 
LISTADO 1
            IDNT    TEST
            XDEF    STARTUP
            XDEF    _stdin
            XDEF    _stdout
            XDEF    _SysBase
            XDEF    _DOSBase
            XREF    START
STARTUP     MOVE.L  4,A6         obtiene dirección SysBase
            MOVE.L  A6,_SysBase  la salva
            LEA     DOSName,A1   apunta al nombre DOS'
            JSR     -$198(A6)    abre libreria
            MOVE.L  D0,_DOSBase  salva la dirección base
            move.L  DOSBase,A6   direción de base DOS'
            JSR     -$36(A6)     Entrada manejo de fichero
            MOVE.L  D0,_stdin    salvarlo
            JSR     -$3C(A6)     Salida manejo de fichero
            MOVE.L  D0,_stdout   salvarlo
            MOVE.L  _SysBase,A6  obtener dirección SysBase
            JSR     START        ir inicio prog.principal
            RTS
_stdin      DS.L    1
_stdout     DS.L    1
_SysBase    DS.L    1
_DOSBase    DS.L    1
DOSName     DC.B    0dos.library',0
            END
0000: 000003E7 00000001 54455354 000003E9   ........TEST....
0010: 00000018 2C780004 23CE0000 004A43F9   ....,x..#....JC.
0020: 00000052 4EAEFE68 23C00000 004E2C79   ...RN..h#....N,y
0030: 0000004E 4EAEFFCA 23C00000 00424EAE   ...NN...#....BN.
0040: FFC423C0 00000046 2C790000 004A4EB9   ..#....F,y...JN.
0050: 00000000 4E750000 00000000 00000000   ....Nu..........
0060: 00000000 0000646F 732E6C69 62726172   ......dos.librar
0070: 79000000 000003EC 00000007 00000000   y...............
0080: 00000036 00000030 00000026 0000001C   ...6...0...&....
0090: 00000016 0000000C 00000006 00000000   ................
00A0: 000003EF 01000002 5F444F53 42617365   ........_DOSBase
00B0: 0000004E 01000002 5F537973 42617365   ...N...._SysBase
00C0: 0000004A 01000002 5F737464 6F757400   ...J...._stdout.
00D0: 00000046 01000002 5F737464 696E0000   ...F...._stdin..
00E0: 00000042 01000002 53544152 54555000   ...B....STARTUP.
00F0: 00000000 81000002 53544152 54000000   ........START...
0100: 00000001 0000003C 00000000 000003F2   .......<........
OFFSET        CONTENIDO   COMENTARIO
------------------------------------
$0000         $000003E7   hunk unidad
$0004         $00000001   tamaño nombre unidad
$0008         $54455354   nombre de la unidad en ASCII = TEST
$000C         $000003E9   hunk código
$0010         $00000018   tamaño hunk
$0014-$0073   .........   código de la unidad
$0074         $000003EC   hunk relocalización 32 bits
$0078         $00000007   número de hunks a relocalizar
$007C         $00000000   hunk a relocalizar comenzando de 0
$0080-$0098   .........   offset a partir base código anterior
$009C         $00000000   0 para marcar fin de relocalización
$00A0         $000003EF   hunk de referencias externas
$00A4         $01000002   tipo de referencia 1 (XDEF) y longitud del nombre
$00A8-$00AF   .........   nombre de la definición = _DOSBase
$00B0         $0000004E   offset de la definición
$00B4         $01000002   mismo caso anterior
$00B8-$00BF   .........   nombre de la defición = _SysBase
.....         .........   continuación del resto de las definiciones
$00F4         $81000002   tipo de referencia $81 = 121(XREF)
$00F8-$00FF   .........   nombre de la referencia = START
$0100         $00000001   número de referencias
$0104         $0000003C   offset de referencia
$0108         $00000000   fin bloque de referencias externas
$010C         $000003F2   hunk fin
 
LISTADO 2
          TTL      'Imprime'
          XREF     _DOSBase
          XREF     _stdin
          XDEF     START
START     MOVE.L   #MSG,D2      apunta comienzo mensaje
          MOVE.L   #20,D3       salva longitud mensaje
          MOVE.L   _stdin,D1    donde va el mensaje (CLI)
          MOVE.L   _DOSBase,A6  apunta a Libreria Base DOS
          JSR      -$30(A6)     escribe
          MOVE.B   #02,POS1     prueba relocalización
          RTS                   retorna a CLI
MSG       DC.B     $A,'Esto es el mensaje',$A
POS1      DS.L     00
          END


Estructuras de Hunks en ficheros ejecutables

El formato de un fichero de lectura o ejecutable producido por el Linker es parecido al fichero objeto, con la única diferencia de que el fichero ejecutable no contiene definiciones ni referencias externas. En el caso común de un fichero, comienza por el bloque de cabecera que le informa de cuantos hunks consta el fichero con sus longitudes. A continuación le siguen los hunks referidos y que corresponden a los del fichero objeto en parte. Se puede dar otro caso, el de los fichero de overlay, es decir, aquella parte del programa que puede ser leído posteriormente por el programa principal, y que contiene simplemente información y rutinas de segundo orden, es decir optativas. Esto ocurre con programas muy largos, para que sean parcialmente utilizados por los ordenadores de configuración basica. Como es bastante difícil agotar la memoria del Amiga con nuestros cortos programas, veremos el único tipo de hunk indicativo de los ficheros ejecutables, el de la cabecera.

Hunk cabecera
Este hunk es digamos el hunk más importante de todos los vistos. Contiene información sobre el fichero que tiene que leer, como es el número de hunks y sus longitudes. La estructura es la siguiente: valor del tipo de hunk: $000003F3, librerías residentes, opción muy poco utilizada, a 0, número de hunks (P), primer hunk, generalmente hunk 0 (U), último hunk que viene dado por la fórmula U-P+1, longitud de los hunks, primer hunk, hunk final. Ahora veamos todo lo dicho anteriormente mediante dos ejemplos, uno con ficheros objetos para observar las referencias externas y otro ejecutable para ver la estructura de la cabecera y relocalizaicón. Para el primer ejemplo utilizaremos el programa del listado 1. (sálvelo en su disco ensamblador como test): Ahora que ya hemos visto que todo concuerda con lo dicho anteriormente voy a explicar algunos detalles. Todos los valores utilizados son en largas palabras y para obtener su valor en bytes hay que multiplicar por cuatro.

Antes de realizar el ensamblado copia el fichero del listado 2 a RAM: por ejemplo, con el nombre JUNTA mediante el comando copy OBJETO RAM:JUNTA. Este nos servirá, si recuerdas el capítulo 1, para unir las referencias externas que ahora necesita este último programa, como por ejemplo la base de la librería DOS. Ahora salva el programa principal anterior como test y ejecuta los siguientes comandos:

c/assem test -o OBJETO
c/link RAM:JUNTA,OBJETO to PRG

Un fichero objeto es el
producido por un
ensamblador o un
compilador de cualquier
lenguaje, y puede
contener referencias
externas que
posteriormente las
resolverá el Linker.

el programa final se llama PRG, puedes ejecutarlo, en una simple visualiación en pantalla de una frase. Ahora pasemos a realizar el proceso anterior para visualizar los códigos hexadecimales y ASCII mediante el comando: type >RAM:hex PRG opt h. El resultado está en el cuadro 2. Ahora pasemos a explicar detenidamente cómo realiza la lectura en memoria de nuestro fichero ejecutable. Primero carga la cabecera para enterarse de cuantos hunks y de sus longitudes para hacer la reserva de la memoria de 96 y 60 bytes respectivamente por los hunks 0 y 1, a continuación realiza la lectura en esos espacios de memoria reservados de los hunks. Por último queda la fase más importante, relocalización. Supongamos que los dos hunks se han insertado en las posiciones de memoria $40000 y $41000. La visualización sería como la del cuadro 3. Para realizar la relocalización en el primer hunk el Amigados suma el valor $40000 a los 7 offsets que componen la lista dentro del hunk. Utilicemos por ejemplo los valores de los offsets 6 y 7 ($6 y $C), sumando $6 al $40000 (ya que es donde se cargó el hunk) nos da la posición de memoria $40006. Ahora sumamos $40000 a la palabra larga que corresponde en es aposición de memoria ($0000004A + $40000 = $4004A). También lo hacemos al offset con el valor $C, en donde habrá que sumar $40000 a lo que haya en la posición $4000C ($00000052 + $40000 = $400052), así consecutivamente con todos los offsets del hunk 0. Pero todavía falta un valor que insertar en el hunk 0 por parte del hunk 1 con el valor $41000 (ver línea en $00B4 de la estructura del fichero). Este único offset contiene el valor $3C. Aplicando la fórmula será en la posición de memoria $4003C la que habrá que sumarle el valor $41000. Ahora pasamos a la relocalización del hunk 1, habiendo 2 por parte del mismo hunk y otros dos por parte del otro hunk, el offset 1 con valor $20, la conclusión será sumar $41000 a la posición $40000 + $20 (en esa posición se encuentra el valor $0000003A pasando a ser $4103A). Lo mismo ocurre en el offset 2. Por último queda los dos offsets por parte del hunk 0. Estos dos contienen los valores $E y $14, por lo tanto habrá que sumar $40000 a lo que haya en las posiciones de memoria $4100E y $41014. El resultado final de toda esta operación se muestra en el cuadro 4. Este mismo proceso lo puedes llevar a cabo para cualquier fichero ejecutables del Amiga. Aunque parece un poco complicado una vez que haya resuelto el enigma una vez el resto te parece igual. También con estos diez capítulos puede realizar un programa que visualice los hunks para poder editarlos.

Y nada más, este es el final del cursillo de lenguaje ensamblador, dedicado exclusivamente al interior de nuestro Amiga. Hasta el próximo cursillo, probablemente sobre el C.


Volver menú revistas Volver página anterior