PRÁCTICAS DE COMPUTADORES II

OCTAVA SESIÓN


  1. Subrutinas

    Hasta ahora, cuando hemos tenido que realizar una operación varias veces, no nos ha quedado más remedio que repetir el código cada vez que lo necesitábamos. Esto no es práctico, por varias razones:
    1. El código ocupa mucho más
    2. El código es difícilmente mantenible: si se descubre un error en ese código, hay que corregirlo en todos los lugares donde esté repetido y se pierde mucho tiempo y es propenso a fallos
    3. No se fomenta la modularidad, pilar de la buena programación. Los programas complejos se deben dividir en subtareas más simples, autocontenidas y con interfaces bien definidas

    Por todo esto, aparece el concepto de subrutina en código máquina. Se trata de un salto a un fragmento de código donde se va a realizar una tarea para, posteriormente, regresar a la siguiente instrucción desde donde se llamó. Veámoslo modificando el código de nuestro viejo amigo que imprime el contenido de un registro por la pantalla en hexadecimal:

    
    	.area PROG (ABS)
    
            ; definimos un par de constantes
    fin     .equ 0xFF01
    pantalla .equ 0xFF00
    
    	.org 0x100
    	.globl programa
    
    prefijo:.asciz "0x" ; prefijo
    
    programa:
            lds #0xF000 ; usaremos la pila.  Le damos un valor seguro
    	lda #28 ; pongamos este nUmero como prueba
    
            ; imprimamos 0x
            ldx #prefijo
            bsr imprime_cadena
    
            ; primero imprimamos la primera cifra hexadecimal
            tsta
            beq segunda_cifra
            tfr a,b
            lsrb
            lsrb
            lsrb
            lsrb ; en B estA la primera cifra, de 0 a 15
            bsr imprime_cifra_hexa
    
            ; ahora imprimimos la segunda cifra hexadecimal
    segunda_cifra:
            tfr a,b
            andb #0b1111 ; en B estA la segunda cifra, de 0 a 15
            bsr imprime_cifra_hexa
    
            ; imprimamos un salto de lInea al final
            ldb #'\n
            stb pantalla
    
            ; el programa acaba
            clra
    	sta fin
    
    imprime_cadena:
            pshs a
    sgte:   lda ,x+
            beq ret_imprime_cadena
            sta pantalla
            bra sgte
    ret_imprime_cadena:
            puls a
            rts
    
    imprime_cifra_hexa:
            cmpb #10
            blo decimal
            addb #'A-'9-1
    decimal:addb #'0
            stb pantalla
            rts
    
    	.org 0xFFFE	; vector de RESET
    	.word programa
    
    

    Lo primero que se observa es que el programa es mucho más legible. Las subrutinas le dotan de un significado más próximo al programador. Es recomendable, por el mismo motivo, que las etiquetas también tengan un significado que propicie la lectura del programa.

    Observad cómo, en la subrutina imprime_cadena, se mete en la pila el valor de A para recuperarlo al final. Si no se hiciera así, el programa no funcionaría porque la subrutina machacaría el contenido de A, donde a esas alturas ya está el número que se quiere imprimir.

    Por las razones expuestas, es muy conveniente documentar bien las subrutinas con una cabecera que debe, al menos, especificar: su nombre, su función, registros o variables de entrada, registros o variables de salida y registros que modifica la subrutina. Hagámoslo para nuestras dos subrutinas:

    
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ; imprime_cadena                                                   ;
    ;     saca por la pantalla la cadena acabada en '\0 apuntada por X ;
    ;                                                                  ;
    ;   Entrada: X-direcciOn de comienzo de la cadena                  ;
    ;   Salida:  ninguna                                               ;
    ;   Registros afectados: X, CC.                                    ;
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
     
    imprime_cadena:
            pshs a
    sgte:   lda ,x+
            beq ret_imprime_cadena
            sta pantalla
            bra sgte
    ret_imprime_cadena:
            puls a
            rts
    
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    ; imprime_cifra_hexa                                               ;
    ;     saca por la pantalla la cifra hexadecimal contenida en B     ;
    ;                                                                  ;
    ;   Entrada: B-cifra hexadecimal, del 0 al 15                      ;
    ;   Salida:  ninguna                                               ;
    ;   Registros afectados: B, CC.                                    ;
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
    
    imprime_cifra_hexa:
            cmpb #10
            blo decimal
            addb #'A-'9-1
    decimal:addb #'0
            stb pantalla
            rts
    
    

    El funcionamiento, a bajo nivel, de las llamadas a subrutinas es ingenioso. Dispone el 6809 de tres instrucciones para realizarlas: JSR, BSR y LBSR. Su funcionamiento en cuanto al salto que hay que hacer es equivalente a las ya vistas: JMP, BRA y LBRA. En esencia, consiste en cargar el PC con la dirección de inicio de la subrutina, pero, justo antes de eso, aunque una vez leída la instrucción y con el PC ya apuntando a la instrucción siguiente, el procesador guarda el PC en la pila S:

    Salto a subrutina

    La jugada es clara. Cuando acabe de ejecutar la subrutina y se encuentre con la instrucción RTS (ReTurn from Subroutine), solamente tiene que sacar el valor de la pila y cargar el PC. Por arte de magia, la ejecución continúa en la instrucción siguiente desde donde se realizó el salto:

    Retorno de subrutina

    Con lo que ya sabemos, podemos decir que la instrucción RTS es completamente equivalente a PULS PC, solo que, como RTS ocupa un byte y PULS PC dos, es más rápida y casi siempre preferible. Las tornas cambian cuando, a la vez que volver de una subrutina, queremos sacar de la pila algún registro, cosa que ocurría al final de imprime_cadena, más arriba:

    imprime_cadena:
            pshs a
            [...]
            puls a
            rts
    
    En esa circunstancia, es más rápido ejecutar una sola orden PULS, como es obvio:
    imprime_cadena:
            pshs a
            [...]
            puls a,pc
    
    No hay que preocuparse por el orden en que se realiza la extracción. Es el correcto: el PC siempre es el último registro que se saca, como vimos en la sesión anterior. Y es que los ingenieros que diseñaron el 6809 no daban puntada sin hilo...

  2. Ejercicio

    Codificad y documentad con su cabecera las siguientes subrutinas basándoos en el código que ya habéis realizado:
    1. imprime_cadena: saca por la pantalla la cadena apuntada por X, que ha de acabar en \0. Registros afectados: X y CC
    2. imprime_decimal: saca por la pantalla, en decimal, el número contenido en D, interpretado como sin signo. Registros afectados: D y CC
  3. Anidamiento

    Se habla de anidamiento en programación cuando una estructura se repite dentro de sí misma. Así, existen bucles anidados cuando uno está en su cuerpo del otro o subrutinas anidadas, cuando una es llamada dentro de la otra. Veámoslo modificando, por enésima vez, el programa de impresión en hexadecimal de un registro:
    
                                  1 	.area PROG (ABS)
                                  2 
                                  3         ; definimos una constante
                         FF01     4 fin     .equ 0xFF01
                         FF00     5 pantalla .equ 0xFF00
                                  6 
       0100                       7 	.org 0x100
                                  8 	.globl programa
                                  9 
       0100                      10 programa:
       0100 10 CE F0 00   [ 4]   11         lds #0xF000 ; usaremos la pila.  Le damos un valor seguro
       0104 86 1C         [ 2]   12 	lda #28 ; pongamos este nUmero como prueba
                                 13 
       0106 8D 1A         [ 7]   14         bsr imprime_byte_hexa
                                 15 
                                 16         ; imprimamos un salto de lInea al final
       0108 C6 0A         [ 2]   17         ldb #'\n
       010A F7 FF 00      [ 5]   18         stb pantalla
                                 19 
                                 20         ; el programa acaba
       010D 4F            [ 2]   21         clra
       010E B7 FF 01      [ 5]   22 	sta fin
                                 23 
                                 24 
                                 25 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
                                 26 ; imprime_cadena                                                   ;
                                 27 ;     saca por la pantalla la cadena acabada en '\0 apuntada por X ;
                                 28 ;                                                                  ;
                                 29 ;   Entrada: X-direcciOn de comienzo de la cadena                  ;
                                 30 ;   Salida:  ninguna                                               ;
                                 31 ;   Registros afectados: X, CC.                                    ;
                                 32 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
                                 33  
       0111                      34 imprime_cadena:
       0111 34 02         [ 6]   35         pshs a
       0113 A6 80         [ 6]   36 sgte:   lda ,x+
       0115 27 05         [ 3]   37         beq ret_imprime_cadena
       0117 B7 FF 00      [ 5]   38         sta pantalla
       011A 20 F7         [ 3]   39         bra sgte
       011C                      40 ret_imprime_cadena:
       011C 35 02         [ 6]   41         puls a
       011E 39            [ 5]   42         rts
                                 43 
                                 44 
                                 45 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
                                 46 ; imprime_byte_hexa                                                ;
                                 47 ;     saca por la pantalla, en hexadecimal, el byte contenido en A ;
                                 48 ;                                                                  ;
                                 49 ;   Entrada: A-byte para imprimir                                  ;
                                 50 ;   Salida:  ninguna                                               ;
                                 51 ;   Registros afectados: CC.                                       ;
                                 52 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
                                 53 
       011F 30 78 00             54 prefijo:.asciz "0x" ; prefijo
       0122                      55 imprime_byte_hexa:
       0122 34 16         [ 8]   56         pshs x,d
                                 57         ; imprimamos 0x
       0124 8E 01 1F      [ 3]   58         ldx #prefijo
       0127 8D E8         [ 7]   59         bsr imprime_cadena
                                 60 
                                 61         ; primero imprimamos la primera cifra hexadecimal
       0129 4D            [ 2]   62         tsta
       012A 27 08         [ 3]   63         beq segunda_cifra
       012C 1F 89         [ 6]   64         tfr a,b
       012E 54            [ 2]   65         lsrb
       012F 54            [ 2]   66         lsrb
       0130 54            [ 2]   67         lsrb
       0131 54            [ 2]   68         lsrb ; en B estA la primera cifra, de 0 a 15
       0132 8D 08         [ 7]   69         bsr imprime_cifra_hexa
                                 70 
                                 71         ; ahora imprimimos la segunda cifra hexadecimal
       0134                      72 segunda_cifra:
       0134 1F 89         [ 6]   73         tfr a,b
       0136 C4 0F         [ 2]   74         andb #0b1111
       0138 8D 02         [ 7]   75         bsr imprime_cifra_hexa
       013A 35 96         [ 9]   76         puls x,d,pc
                                 77 
                                 78 
                                 79 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
                                 80 ; imprime_cifra_hexa                                               ;
                                 81 ;     saca por la pantalla la cifra hexadecimal contenida en B     ;
                                 82 ;                                                                  ;
                                 83 ;   Entrada: B-cifra hexadecimal, del 0 al 15                      ;
                                 84 ;   Salida:  ninguna                                               ;
                                 85 ;   Registros afectados: B, CC.                                    ;
                                 86 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
                                 87 
       013C                      88 imprime_cifra_hexa:
       013C C1 0A         [ 2]   89         cmpb #10
       013E 25 02         [ 3]   90         blo decimal
       0140 CB 07         [ 2]   91         addb #'A-'9-1
       0142 CB 30         [ 2]   92 decimal:addb #'0
       0144 F7 FF 00      [ 5]   93         stb pantalla
       0147 39            [ 5]   94         rts
                                 95 
       FFFE                      96 	.org 0xFFFE	; vector de RESET
       FFFE 01 00                97 	.word programa
                                 98 
    
    

    Observad cómo ha aparecido una nueva subrutina imprime_byte_hexa que, a su vez, realiza dos llamadas a la vieja subrutina imprime_cifra_hexa para imprimir cada una de las cifras del número hexadecimal.

    imprime_cifra_hexa promete en su contrato que solamente resultará afectado el registro CC, por lo que hace una cosa muy habitual en las subrutinas, guardar en la pila el valor de los registros que modifica en el cuerpo pero que quiere devolver intactos. Fijaos también que, en la línea 76 vuelve de la subrutina sacando el PC directamente de la pila, en lugar de usar RTS.

    Observad cómo se va a producir la cadena de llamadas y cómo entran y salen las direcciones de retorno de la pila en el siguiente gráfico. Como se puede ver, para hacer llamadas anidadas, es necesario utilizar una pila, pues la dirección a la que se retorna (la que se saca de la pila) es la última que entró:

    Subrutinas anidadas
  4. Ejercicio

    Haced lo mismo que en el ejercicio anterior con estas funciones:
    1. lee_cadena: lee del teclado y escribe, a partir de la posición marcada por X, caracteres hasta que el usuario pulse intro (\n). El carácter \n no se almacena en la memoria y la cadena se acaba en \0 en la memoria. A la salida X apunta a la posición donde se almacenó el carácter \0. Registros afectados: X y CC
    2. lee_cadena_n: igual que el caso anterior, pero admite una entrada más en el registro A que será el número máximo de caracteres que se almacenarán en la memoria contando el \0 del final, que siempre se escribe. En A devuelve el número de caracteres leídos. Registros afectados: X y CC
    3. lee_decimal: igual que el segundo caso, pero lo que se lee se interpreta como un número decimal. El número se devuelve en D. En caso de error, por haber caracteres que no sean números ni espacios o por pasarse de la capacidad del registro D, se devuelve en D cero y se activa el bit de acarreo. Si hay espacios al principio, se ignoran. Si aparece un espacio después de haber leído una varias cifras, se acaba la lectura, pero no es un error. Si no hay error, el bit de acarreo vuelve desactivado. Registros afectados: CC
  5. Biblioteca de subrutinas

    Una vez se conoce y se usan las subrutinas, enseguida se hace uno con un conjunto de ellas que usa a menudo: una biblioteca de subrutinas. Es lo que se conoce como reutilización del código y es muy ventajoso, pues ahorra tiempo en el desarrollo de los programas. Para poder reutilizar el código es muy importante que esté bien documentado, bien organizado y tener un mecanismo sistemático para poder incluirlo en los programas nuevos.

    El mecanismo más elemental consiste en copiar y pegar las subrutinas que necesitamos. Un mecanismo un poco más avanzado es usar una directiva del ensamblador para que incluya, cuando ensamble, el código que se encuentra en otro fichero.

    Una directiva es muy similar a un nemónico salvo que la orden que se da se le da al propio programa ensamblador. Las directivas, aunque siguen un estilo similar, son propias de cada ensamblador y pueden cambiar de uno a otro. Los nemónicos son propios del procesador y no cambian. Las directivas comienzan por el carácter punto (.). Ya hemos usado una (.ORG). En esta sesión aprenderemos algunas más.

    La directiva que incluye otro fichero, cuando se ensamble, en un punto de nuestro programa es .INCLUDE:

            ;; AquI irIa el cOdigo propio del programa
    
            ;; Incluimos el fichero print.asm
            ;; Cuando se ensamble sucede como si lo hubiEramos pegado aquI
            .include "print.asm"
    

    Aunque interesante, la inclusión de ficheros tal cual presenta algunos inconvenientes:

    1. A veces, si nos venden código, no nos quieren proporcionar el código fuente, por motivos comerciales
    2. Sería interesante que pudiéramos mezclar subrutinas en ensamblador con otras generadas en otros lenguajes de programación

    A consecuencia de esto, se impone un enfoque más sistemático, que permita guardar, organizar y usar subrutinas directamente en código máquina (código objeto)

  6. Ejemplo

    Vamos a construir un programa que use subrutinas de otros ficheros en código objeto (.rel). Este es el programa principal. Escribidlo en un fichero que se llame prueba.asm:
    	.module prueba
    
    	.area CODE1 (ABS)
    	.org 0x100
    	.globl programa
            .globl print_a
            .globl print_b
            .globl print_c
    
    programa:
            lds #0xF000
            jsr print_a
            jsr print_b
            jsr print_c
    	clra
    	sta 0xFF01
    
    	.org 0xFFFE
    	.word programa
    
    
    Prestad atención a lo siguiente:
    1. Hemos encabezado el programa con una directiva .MODULE. Pondremos una de estas en cada .asm que forme parte del proyecto. Sirve para que, cuando el enlazador una todas las partes para formar el ejecutable final, si surge algún problema, sepa decirnos en qué fichero está el problema
    2. Se puede dividir nuestro proyecto en áreas. Un área estará formada por fragmentos de código que aparecerán juntos en la memoria en el ejecutable final. En el ejemplo, al área le hemos dado el nombre CODE1. A las áreas se les pueden dar los siguientes atributos entre paréntesis:
      • REL/ABS: el área se puede reubicar (RELocatable) o va en una posición fija (ABSolute)
      • CON/OVR: de haber varias áreas, al enlazarlas, se deben concatenar (CONcatenate) o superponer (OVeRlay). Las áreas absolutas no pueden sino superponerse
      • NOPAG/PAG: se trata de un área paginada o no (veremos de qué se trata en la última sesión)
      De no especificar nada, el valor es el que aparece el primero de cada par en la lista anterior. Si un área, como es natural, aparece en varios ficheros .asm, debe tener los mismos atributos en todos.
    3. Las áreas absolutas son aquellas que, ya en ensamblador, se sabe dónde se van a poner en la memoria. Para indicar dónde va cada una de sus partes, se usa la directiva .ORG. En nuestro ejemplo, el área CODE1 tiene una primera parte a partir de la dirección 0x100 y una última para establecer el valor del vector RESET, a partir de la dirección 0xFFFE
    4. Las etiquetas que son compartidas por todos los módulos deben declararse globales, tanto donde se definen, como donde se usan. Se usa la directiva .GLOBL. Hemos declarado como globales las etiquetas programa (se define en nuestro código y vale 0x100) y las etiquetas print_a, print_b y print_c (se usan en nuestro código, pero no se definen)
    5. Finalmente, y como no sabemos dónde decidirá el enlazador poner los códigos de print_a, print_b y print_c, hemos hecho un salto con JSR y no con BSR, pues este último tiene la limitación de no poder satar más de 120 bytes adelante o atrás del punto actual, aproximadamente.

    Compilemos nuestro programa, como haríamos habitualmente y obtengamos también un listado:

    as6809 -o prueba.asm
    as6809 -l prueba.asm
    

    Observemos el listado que se produce:

                                  1         .module prueba
                                  2 
                                  3         .area CODE1 (ABS)
       0100                       4         .org 0x100
                                  5         .globl programa
                                  6         .globl print_a
                                  7         .globl print_b
                                  8         .globl print_c
                                  9 
       0100                      10 programa:
       0100 10 CE F0 00   [ 4]   11         lds #0xF000
       0104 BD 00 00      [ 8]   12         jsr print_a
       0107 BD 00 00      [ 8]   13         jsr print_b
       010A BD 00 00      [ 8]   14         jsr print_c
       010D 4F            [ 2]   15         clra
       010E B7 FF 01      [ 5]   16         sta 0xFF01
                                 17 
       FFFE                      18         .org 0xFFFE
       FFFE 01 00                19         .word programa
                                 20 
    
    [...]
    
    Symbol Table
    
        .__.$$$.       =   2710 L   |     .__.ABS.       =   0000 G
        .__.CPU.       =   0000 L   |     .__.H$L.       =   0001 L
        print_a            **** GX  |     print_b            **** GX
        print_c            **** GX  |   2 programa           0100 GR
    

    Vemos que el código y los dos últimos bytes se sitúan (al ser un área absoluta) en posiciones fijas, que son las marcadas por las directivas .ORG. Observad, además, que en las líneas 12, 13 y 14, el ensamblador no conoce en qué direcciones estarán las subrutinas print_a, print_b y print_c. Es por eso, que las direcciones de salto (los dos bytes a continuación del código de operación 0xBD) son 0 y en la tabla de símbolos aparecen los valores de las etiquetas como asteriscos.

    Escribimos ahora el primero de los módulos con subrutinas, al que vamos a llamar printab.asm:

    	.module printab
    
    	.globl print_a
            .globl print_b
    print_a:lda #'A
    	sta 0xFF00
            rts
    print_b:lda #'B
            sta 0xFF00
            rts
    

    Y el segundo, cuyo nombre será printc.asm:

    	.module printc
    
    	.globl print_c
    print_c:lda #'C
    	sta 0xFF00
            rts
    
    Los módulos no son de buenos como para tirar cohetes, pero nos sirven para el ejemplo. Notad algunas diferencias respecto del módulo principal:
    1. No aparece ninguna directiva .AREA. En realidad no son obligatorias. Podríamos haber hecho un área nueva o no. Lo importante es que el código tiene los valores por defecto al no poner ninguna (REL, CON, NOPAG)
    2. No aparece directiva .ORG. Pues al ser un fragmento reubicable no tiene sentido especificar una dirección fija
    3. Se exportan (se hacen globales) las etiquetas print_, pues queremos que las puedan usar el resto de módulos

    Pasemos, a continuación, a ensamblarlos y a generar sus listados:

    as6809 -o printab.asm
    as6809 -l printab.asm
    as6809 -o printc.asm
    as6809 -l printc.asm
    

    El listado del ensamblado de los dos ficheros es este:

                          *****************
                          *  printab.lst  *
                          *****************
    
                                  1 	.module printab
                                  2 
                                  3 	.globl print_a
                                  4         .globl print_b
       0000 86 41         [ 2]    5 print_a:lda #'A
       0002 B7 FF 00      [ 5]    6 	sta 0xFF00
       0005 39            [ 5]    7         rts
       0006 86 42         [ 2]    8 print_b:lda #'B
       0008 B7 FF 00      [ 5]    9         sta 0xFF00
       000B 39            [ 5]   10         rts
                                 11 
    [...]
    Symbol Table
    
        .__.$$$.       =   2710 L   |     .__.ABS.       =   0000 G
        .__.CPU.       =   0000 L   |     .__.H$L.       =   0001 L
      0 print_a            0000 GR  |   0 print_b            0006 GR
    
    
    
                          *****************
                          *  printc.lst   *
                          *****************
    
                                  1 	.module printc
                                  2 
                                  3 	.globl print_c
       0000 86 43         [ 2]    4 print_c:lda #'C
       0002 B7 FF 00      [ 5]    5 	sta 0xFF00
       0005 39            [ 5]    6         rts
                                  7 
    [...]
    Symbol Table
    
        .__.$$$.       =   2710 L   |     .__.ABS.       =   0000 G
        .__.CPU.       =   0000 L   |     .__.H$L.       =   0001 L
      0 print_c            0000 GR
    
  7. Observamos que, al ser módulos reubicables, se ensamblan todos a partir de la dirección 0.

    Resumiendo, al final de la fase de ensamblado, tenemos tres módulos de código objeto: prueba.rel (absoluto), printab.rel (reubicable) y printc.rel (reubicable). Pensad que, en un caso real, puede que alguno de ellos fuera comprado y ni siquiera tuviéramos el código en ensamblador.

    Ahora toca componer este rompecabezas para formar un único fichero ejecutable. Esto es tarea del enlazador aslink, al que antes ya hemos acudido, pero al que ahora venimos con un problema algo más complicadillo. La tarea del enlazador será:

    1. Construir un mapa de memoria y situar en él cada uno de los módulos y/o áreas. Las áreas de tipo ABS, van en sus posiciones, las de tipo REL, pueden ser acomodadas en otras posiciones libres
    2. Si varias áreas aparecen en distintos sitios, hacer que en el mapa final estén contiguas
    3. Completar las referencias que se dejaron a cero porque no se conocían en tiempo del ensamblado
    4. Producir información como mapas de memoria si se le solicita
    5. Producir el ejecutable en el formato que se le solicite

    La sintaxis de la orden aslink es un poco desafortunada. Cuando se le pasa más de un fichero, el primero es donde va a ir el ejecutable de salida y los siguientes son los módulos que queremos que lo compongan. Demos la orden más compleja posible de las que aprenderemos (-s, salida en formato Motorola; -m -w, queremos mapa de símbolos; -u, queremos listados actualizados con el valor que han tomado las etiquetas de cada módulo después del enlazado):

    aslink -s -m -w -u prueba.s19 prueba.rel printab.rel printc.rel
    

    Observemos parte del mapa de símbolos generado:

    Area                       Addr        Size        Decimal Bytes (Attributes)
    --------------------       ----        ----        ------- ----- ------------
    CODE1                      0000        0000 =           0. bytes (ABS,OVR,CSEG)
    
             Value  Global                             Global Defined In Module
             -----  --------------------------------   ------------------------
              0100  programa                           prueba
    [...]
    Area                       Addr        Size        Decimal Bytes (Attributes)
    --------------------       ----        ----        ------- ----- ------------
    _CODE                      0000        0012 =          18. bytes (REL,CON,CSEG)
    
             Value  Global                             Global Defined In Module
             -----  --------------------------------   ------------------------
              0000  print_a                            printab
              0006  print_b                            printab
              000C  print_c                            printc
    
    Mapa del ejecutable generado

    Prestad atención a que el módulo printab ha encontrado sitio en su posición original. Por ese motivo, el módulo printc se ha encontrado con la posición ocupada y el enlazador no ha tenido más remedio que reubicarlo en la dirección 0x000C. Una prueba más de que ha ocurrido así son los listados que nos ha generado el enlazador con todos los símbolos ya resueltos (extensión .rst) por dar la orden -u:

    
                          *****************
                          *  prueba.rst   *
                          *****************
    
                                  1 	.module prueba
                                  2 
                                  3 	.area CODE1 (ABS)
       0100                       4 	.org 0x100
                                  5 	.globl programa
                                  6         .globl print_a
                                  7         .globl print_b
                                  8         .globl print_c
                                  9 
       0100                      10 programa:
       0100 10 CE F0 00   [ 4]   11         lds #0xF000
       0104 BD 00 00      [ 8]   12         jsr print_a
       0107 BD 00 06      [ 8]   13         jsr print_b
       010A BD 00 0C      [ 8]   14         jsr print_c
       010D 4F            [ 2]   15 	clra
       010E B7 FF 01      [ 5]   16 	sta 0xFF01
                                 17 
       FFFE                      18 	.org 0xFFFE
       FFFE 01 00                19 	.word programa
                                 20 
    
    
                          *****************
                          *  printab.rst  *
                          *****************
    
                                  1 	.module printab
                                  2 
                                  3 	.globl print_a
                                  4         .globl print_b
       0000 86 41         [ 2]    5 print_a:lda #'A
       0002 B7 FF 00      [ 5]    6 	sta 0xFF00
       0005 39            [ 5]    7         rts
       0006 86 42         [ 2]    8 print_b:lda #'B
       0008 B7 FF 00      [ 5]    9         sta 0xFF00
       000B 39            [ 5]   10         rts
                                 11 
    
    
                          *****************
                          *  printc.rst   *
                          *****************
    
                                  1 	.module printc
                                  2 
                                  3 	.globl print_c
       000C 86 43         [ 2]    4 print_c:lda #'C
       000E B7 FF 00      [ 5]    5 	sta 0xFF00
       0011 39            [ 5]    6         rts
                                  7 
    
  8. Ejercicio

    Construid ficheros .asm para cada una de las funciones pedidas en los ejercicios anteriores de esta sesión y generad sus ficheros objeto .rel de modo que puedan ser enlazados en proyectos posteriores.
  9. Mejorando lo presente

    En el esquema anterior resulta extraño que todos los módulos sean reubicables salvo el módulo principal, que está atado a la dirección 0x100. Esto no es deseable. Leyendo dicho módulo caeremos en la cuenta de que lo único que realmente está atado a una dirección fija de memoria es el vector de RESET. No es necesario incluir en el área ABS el resto del código. Hagamos una nueva versión prueba2.asm con estas consideraciones:
    	.module prueba2
    
    	.globl programa
            .globl print_a
            .globl print_b
            .globl print_c
    
    programa:
            lds #0xF000
            jsr print_a
            jsr print_b
            jsr print_c
    	clra
    	sta 0xFF01
    
            .area FIJA (ABS)
    	.org 0xFFFE
    	.word programa
    
    

    Repitiendo todo el proceso anterior, sin necesidad de volver a ensamblar ni printab.asm ni printc.asm, pues de eso se trata, obtenemos el ejecutable.

    Todo el código que no pertenece a ninguna área es asignado por el ensamblador a un área por defecto que se denomina _CODE. Esta área, como bien supondréis, es de tipo REL.

    Puede molestar que el código final comience en la dirección 0. Podemos dar órdenes al enlazador para que coloque las áreas REL que queramos en determinados sitios de la memoria. Por ejemplo, si queremos que el código empiece en la dirección 0x100:

    aslink -s -m -w -b _CODE=0x100 prueba2.s19 prueba2.rel printab.rel printc.rel
    
  10. Ejercicio

    Apoyándose en los módulos objeto del ejercicio anterior y con la técnica aprendida en el último apartado, hágase un programa que pida dos números enteros por el teclado, los sume y saque el resultado de la suma por la pantalla. El código debe empezar en la dirección 0x400.
  11. Bibliotecas

    Es común en los sistemas operativos el tener un formato para poder agrupar módulos objeto en un único fichero para mejor organización. Son las bibliotecas de enlazado estático. Su nombre proviene porque el enlazado (resolución de los símbolos desconocidos) lo hace el propio programa enlazador al construir el ejecutable, es decir, con el programa parado, sin que aún se haya ejecutado.

    El sistema simple con el que estamos trabajando tiene un método para proporcionar una función similar, aunque mucho más limitada. Creemos una biblioteca de módulos objeto con nuestros dos módulos. Es muy sencillo. Solamente hay que crear un fichero, lo llamaremos print.lib con los nombres de los módulos que forman parte de la biblioteca, uno por línea:

    printab.rel
    printc.rel
    
    

    Ahora podemos enlazar haciendo referencia a la biblioteca, en lugar de de a los módulos directamente:

    aslink -s -m -w -b _CODE=0x100 prueba2.s19 prueba2.rel -l print
    

    La opción es -l y del nombre de la biblioteca se puede omitir la extensión .lib.

    No es un sistema muy completo porque aún necesitamos tener presentes los módulos en sí. En los sistemas normales es el propio fichero de la biblioteca el que contiene el código objeto de sus módulos, de modo que es autosuficiente.

  12. Ejercicio

    Contruid dos bibliotecas: imprime.lib y lee.lib con las correspondientes funciones del primer ejercicio. Compilad el segundo ejercicio enlazando mediante las bibliotecas.
  13. Otras directivas de ensamblado

    Para acabar esta sesión, veamos algunas directivas más de nuestro ensamblador:
  14. Órdenes de ensamblador vistas.

    JSR x
    salta a una subrutina: almacena el PC en la pila S y lo carga con la dirección de memoria x o con la dirección apuntada por la etiqueta x. x puede ser una dirección o una etiqueta
    Flags afectados: ninguno
    BSR x
    salta a una subrutina: almacena el PC en la pila S y salta a la dirección de memoria x o a la dirección apuntada por la etiqueta x. La dirección debe estar en el rango [PC-128,PC+127]. x puede ser una dirección o una etiqueta.
    Flags afectados: ninguno
    LBSR
    misma operación que la anterior, pero permitiendo desplazamientos respecto del PC de 16 bits con signo
    Flags afectados: ninguno
    RTS
    vuelve de una subrutina: saca el PC de la pila S
    Flags afectados: ninguno
    .INCLUDE "x"
    incluye, a la hora de ensamblar, el fichero x con los mismos efectos que si se hubiera copiado su contenido en ese punto de nuestro programa
    Flags afectados: es una directiva del ensamblador
    .MODULE x
    declara el nombre del módulo actual
    Flags afectados: es una directiva del ensamblador
    .AREA x (f)
    declara el nombre del área donde debe ir el código que sigue. f son los flags de ese área, que deben coincidir en todas las apariciones del área en el proyecto. Los flags pueden ser:
    • REL/ABS: reubicable o absoluta
    • CON/OVR: concatenable o superponible
    • NOPAG/PAG: no página o página
    Solamente puede aparecer una de las parejas y, de no aparecer, se considera la primera. ABS implica forzosamente OVR.
    Flags afectados: es una directiva del ensamblador
    .ORG x
    en las áreas ABS, establece el punto de ensamblado en la dirección indicada como operando
    Flags afectados: es una directiva del ensamblador
    .GLOBL x1, x2, ... xn
    establece que las etiquetas xs son etiquetas exportables o compartidas entre los módulos (y definida cada una en un módulo, o en varios si coincide la definición)
    Flags afectados: es una directiva del ensamblador
    .LOCAL x1, x2, ... xn
    establece que las etiquetas xs son etiquetas locales. Solamente conoce de cada una el módulo donde se ha definido y usado. Puede haber varias etiquetas locales en distintos módulos con el mismo nombre y no interfieren entre sí.
    Flags afectados: es una directiva del ensamblador
    e .EQU x
    da a la etiqueta e el valor arbitrario x
    Flags afectados: es una directiva del ensamblador
    .BYTE x1, x2, ... xn
    almacena la lista de bytes en la posición actual de ensamblado
    Flags afectados: es una directiva del ensamblador
    .WORD x1, x2, ... xn
    almacena la lista de palabras en la posición actual de ensamblado
    Flags afectados: es una directiva del ensamblador
    .ASCII "x"
    almacena la cadena x en la posición actual de ensamblado
    Flags afectados: es una directiva del ensamblador
    .ASCIZ "x"
    almacena la cadena x en la posición actual de ensamblado, añadiendo un carácter nulo al final de ella
    Flags afectados: es una directiva del ensamblador
    .DS x
    avanza la posición de ensamblado x bytes
    Flags afectados: es una directiva del ensamblador


  15. Órdenes de la shell relacionadas.

    ls
    lista el contenido de un directorio
    cd
    cambia el directorio de trabajo
    rm
    borra un fichero
    man
    muestra la página de manual de una orden
    cat
    muestra el contenido de un fichero
    echo $?
    muestra el código devuelto por el último programa ejecutado


  16. LPEs.


© 2025 Guillermo González Talaván.