1 ; definimos un par de constantes FF01 2 fin .equ 0xFF01 FF00 3 pantalla .equ 0xFF00 4 5 .globl programa 6 0000 00 7 temp: .byte 0 ; una variable temporal 0001 30 78 00 8 prefijo:.asciz "0x" 9 0004 10 programa: 0004 86 1C [ 2] 11 lda #28 ; pongamos este nUmero como prueba 0006 BD 00 12 [ 8] 12 jsr imprimir_byte_hexa 13 ; imprimamos un salto de lInea al final 0009 86 0A [ 2] 14 lda #'\n 000B B7 FF 00 [ 5] 15 sta pantalla 16 17 ; el programa acaba 000E 4F [ 2] 18 clra 000F B7 FF 01 [ 5] 19 sta fin 20 21 0012 22 imprimir_byte_hexa: 0012 8E 00 01 [ 6] 23 ldx #prefijo 0015 BD 00 40 [ 8] 24 jsr imprime_cadena 25 26 ; primero imprimamos la primera cifra hexadecimal 0018 B7 00 00 [ 5] 27 sta temp ; guardamos A en temp 001B 44 [ 2] 28 lsra 001C 44 [ 2] 29 lsra 001D 44 [ 2] 30 lsra 001E 44 [ 2] 31 lsra ; en temp estA la primera cifra, de 0 a 15 001F 27 0B [ 3] 32 beq segunda 0021 81 0A [ 2] 33 cmpa #10 0023 25 02 [ 3] 34 blo menor10 0025 8B 07 [ 2] 35 adda #7 0027 8B 30 [ 2] 36 menor10:adda #'0 0029 B7 FF 00 [ 5] 37 sta pantalla 38 002C 39 segunda: 002C B6 00 00 [ 5] 40 lda temp ; recuperamos el valor original 002F 84 0F [ 2] 41 anda #0b1111 0031 81 0A [ 2] 42 cmpa #10 0033 25 02 [ 3] 43 blo menor10bis 0035 8B 07 [ 2] 44 adda #7 0037 45 menor10bis: 0037 8B 30 [ 2] 46 adda #'0 0039 B7 FF 00 [ 5] 47 sta pantalla 48 003C B6 00 00 [ 5] 49 lda temp 003F 39 [ 5] 50 rts 51 0040 52 imprime_cadena: 0040 34 02 [ 6] 53 pshs a 0042 A6 80 [ 6] 54 sgte: lda ,x+ 0044 27 06 [ 3] 55 beq ret_imprime_cadena 0046 B7 FF 00 [ 5] 56 sta pantalla 0049 7E 00 42 [ 4] 57 jmp sgte 004C 58 ret_imprime_cadena: 004C 35 02 [ 6] 59 puls a 004E 39 [ 5] 60 rtsEl código no está completo, pues falta el vector de RESET que apunta a una rutina previa que establece la pila y salta al comienzo del programa. Pero esto no importa para nuestra exposición. Lo vamos a enlazar para situarlo en la dirección 0x100:
aslink -s -b _CODE=0x100 hex4.relEl código máquina que se almacena a partir de la dirección 0x100 es el siguiente:
00307800861CBD0112860AB7FF004FB7FF018E0101BD0140B7010044444444270B810A25028B078B30B7FF00B60100840F810A25028B078B30B7FF00B60100393402A6802706B7FF007E0142350239Es tan largo que se nos acaba hasta el papel perforado... Vamos a enlazarlo ahora para que se cargue en la dirección 0x1234:
aslink -s -b _CODE=0x1234 hex4.relEl código máquina en la dirección 0x1234 es ahora:
00307800861CBD1246860AB7FF004FB7FF018E1235BD1274B7123444444444270B810A25028B078B30B7FF00B61234840F810A25028B078B30B7FF00B61234393402A6802706B7FF007E1276350239Es muy parecido, ¿verdad? Pero, ¿es igual? Pongámoslos juntos para comparar:
00307800861CBD0112860AB7FF004FB7FF018E0101BD0140B7010044444444270B810A25028B078B30B7FF00B60100840F810A25028B078B30B7FF00B60100393402A6802706B7FF007E0142350239 00307800861CBD1246860AB7FF004FB7FF018E1235BD1274B7123444444444270B810A25028B078B30B7FF00B61234840F810A25028B078B30B7FF00B61234393402A6802706B7FF007E1276350239Son casi idénticos, pero no iguales. Marquemos en rojo las diferencias para que se vea más claro:
00307800861CBD0112860AB7FF004FB7FF018E0101BD0140B7010044444444270B810A25028B078B30B7FF00B60100840F810A25028B078B30B7FF00B60100393402A6802706B7FF007E0142350239 00307800861CBD1246860AB7FF004FB7FF018E1235BD1274B7123444444444270B810A25028B078B30B7FF00B61234840F810A25028B078B30B7FF00B61234393402A6802706B7FF007E1276350239El código que hemos programado cambia según cambiemos la dirección en que se carga. Este código no es reubicable tal cual. Lo podemos cambiar de dirección de carga porque el enlazador
aslink
se encarga de modificarlo para que funcione en la
dirección que le digamos.
El objetivo de esta sesión es mostrar las técnicas que nos permiten generar código máquina que pueda ser cargado en cualquier posición de memoria, pero sin modificar ni uno solo de sus bytes.
No se trata de un mero pasatiempo. Cuantas menos modificaciones tenga que hacer nuestro enlazador, más rápido será el enlazado. En otras ocasiones, un cógido ya cargado y ejecutándose en la memoria debe moverse para dejar paso a otro programa que se va a ejecutar a la vez que el primero. A veces, tenemos código máquina almacenado en una ROM y queremos instalar esa ROM en otras posiciones de memoria, sin cambiarla. Si el código es reubicable, es posible en el último caso y muy fácil en los anteriores. Si no, no.
Transformemos, pues, este programa para que sea código máquina reubicable. Donde primero podemos atacar es en los saltos y saltos a subrutinas absolutos. En el código hay varios:[...] 0004 10 programa: 0004 86 1C [ 2] 11 lda #28 ; pongamos este nUmero como prueba 0006 BD 00 12 [ 8] 12 jsr imprimir_byte_hexa 13 ; imprimamos un salto de lInea al final [...] 0012 22 imprimir_byte_hexa: 0012 8E 00 01 [ 6] 23 ldx #prefijo 0015 BD 00 40 [ 8] 24 jsr imprime_cadena 25 [...] 0046 B7 FF 00 [ 5] 56 sta pantalla 0049 7E 00 42 [ 4] 57 jmp sgte 004C 58 ret_imprime_cadena: 004C 35 02 [ 6] 59 puls a [...]Si usamos JMP o JSR, la dirección de memoria a la que voy a saltar es la que aparece en el código máquina y esa dirección varía según donde lo almacenemos. Por ejemplo, la subrutina
imprimir_byte_hexa
está
en la dirección 0x112 cuando el programa se almacena a partir
de la 0x100 y en la direccción 0x1246 cuando el programa se
almacena en la dirección 0x1234. Cambiemos estas instrucciones
por (L)BRA y (L)BSR y veamos el resultado:
[...] 0004 10 programa: 0004 86 1C [ 2] 11 lda #28 ; pongamos este nUmero como prueba 0006 8D 09 [ 7] 12 bsr imprimir_byte_hexa 13 ; imprimamos un salto de lInea al final [...] 0011 22 imprimir_byte_hexa: 0011 8E 00 01 [ 6] 23 ldx #prefijo 0014 8D 28 [ 7] 24 bsr imprime_cadena 25 [...] 0044 B7 FF 00 [ 5] 56 sta pantalla 0047 20 F7 [ 3] 57 bra sgte 0049 58 ret_imprime_cadena: 0049 35 02 [ 6] 59 puls a [...]Al usar BRA o BSR, la dirección a la que hay que saltar se expresa como un desplazamiento sobre el valor del PC en esos momentos. Al cargar el programa en direcciones diferentes, el valor del PC cambia, pero el desplazamiento o la distancia a la que se encuentra la subrutina a la que queremos saltar es la misma. Lo comprobamos viendo cómo se apagan esas tres diferencias en la comparación de códigos máquinas:
00307800861C8D09860AB7FF004FB7FF018E01018D28B7010044444444270B810A25028B078B30B7FF00B60100840F810A25028B078B30B7FF00B60100393402A6802705B7FF0020F7350239 00307800861C8D09860AB7FF004FB7FF018E12358D28B7123444444444270B810A25028B078B30B7FF00B61234840F810A25028B078B30B7FF00B61234393402A6802705B7FF0020F7350239Lo siguiente que vamos a hacer, seguro que ya se os ha ocurrido. Tenemos una variable temporal
temp
que, como todas las variables locales y
temporales, es muy buena candidata para almacenarse en la pila. Veamos
dónde aparece:
[...] 6 0000 00 7 temp: .byte 0 ; una variable temporal 0001 30 78 00 8 prefijo:.asciz "0x" [...] 26 ; primero imprimamos la primera cifra hexadecimal 0016 B7 00 00 [ 5] 27 sta temp ; guardamos A en temp 0019 44 [ 2] 28 lsra [...] 002A 39 segunda: 002A B6 00 00 [ 5] 40 lda temp ; recuperamos el valor original 002D 84 0F [ 2] 41 anda #0b1111 [...] 48 003A B6 00 00 [ 5] 49 lda temp 003D 39 [ 5] 50 rts [...]Sustituyamos este fragmento de código por este otro que hace uso de la pila:
[...] 26 ; primero imprimamos la primera cifra hexadecimal 0015 34 02 [ 6] 27 pshs a ; guardamos A en en la pila 0017 44 [ 2] 28 lsra [...] 0028 39 segunda: 0028 A6 E4 [ 4] 40 lda ,s ; recuperamos el valor original 002A 84 0F [ 2] 41 anda #0b1111 [...] 48 0037 35 02 [ 6] 49 puls a 0039 39 [ 5] 50 rts [...]El código que usa la pila no depende de dónde se cargue el programa. La cosa pinta bien... A ver cuánto nos queda por arreglar:
307800861C8D09860AB7FF004FB7FF018E01008D25340244444444270B810A25028B078B30B7FF00A6E4840F810A25028B078B30B7FF003502393402A6802705B7FF0020F7350239 307800861C8D09860AB7FF004FB7FF018E12348D25340244444444270B810A25028B078B30B7FF00A6E4840F810A25028B078B30B7FF003502393402A6802705B7FF0020F7350239Parece que queda una pequeña resistencia numantina. Veamos a qué parte del código corresponde:
[...] 7 0000 30 78 00 8 prefijo:.asciz "0x" 9 [...] 0010 22 imprimir_byte_hexa: 0010 8E 00 00 [ 6] 23 ldx #prefijo 0013 8D 25 [ 7] 24 bsr imprime_cadena [...]Antes de que cojáis el martillo pilón y la aplastéis escribiendo primero un
0
y luego una x
, trataremos
de ser más sutiles, pues habrá veces en que no podamos
deshacernos tan sencillamente de una variable almacenada en la memoria.
En estos momentos podemos caer en que, en el fondo, el problema es
el mismo que el que ocurría con los saltos JMP y JSR. Si yo fuera
capaz de expresar la dirección de acceso de la variable no de un
modo absoluto, sino como un desplazamiento respecto a la posición
actual del PC, todo estaría arreglado. La buena noticia es que
se puede hacer en el ensamblador del 6809. La mala (o buena, según
se mire) es que hay que controlar muy bien los modos de direccionamiento
para saber qué ocurre entre bambalinas:
[...] 7 0000 30 78 00 8 prefijo:.asciz "0x" 9 [...] 0010 22 imprimir_byte_hexa: 0010 30 8C ED [ 5] 23 leax prefijo,pcr 0013 8D 25 [ 7] 24 bsr imprime_cadena [...]Partamos primero de lo que sería una aproximación ingenua al problema:
ldx prefijo,pcEsto no haría lo que nosotros queremos que haga. Recordemos que la etiqueta
prefijo
tiene un valor, el que sea,
correspondiente con la dirección de memoria donde se almacene
su variable. Al escribir prefijo,pc
estaríamos
pidiendo que se sumara al PC el valor de esa dirección de memoria.
Eso no es lo que queremos. Queremos que el ensamblador, considerando
dónde estará el PC cuando se ejecute la instrucción,
calcule qué hay que sumarle para dar con la variable a la que
apunta prefijo
. Supongamos que el programa se cargara en
la dirección 0x100. En ese caso, prefijo
vale
0x100 pues es lo primero que aparece en el programa. El PC, cuando se
ejecute la instrucción estará en 0x113 (apuntando ya
a la siguiente instrucción), luego el desplazamiento que debe
calcular el ensamblador es -0x13 o, lo que es lo mismo, 0xED, que es
el que aparece en el código máquina.
Luego, la sintaxis correcta consiste en usar PCR en lugar de PC. ¿Y por qué se usa LEAX en lugar de LDX?
Si ponemos LDX, el registro X se va a cargar con el contenido de la variable, es decir, con 0x3078. Nosotros queremos que X se cargue con la dirección donde está la variable. Por eso usamos LEAX.
Para ver si lo comprendéis, pensad y luego verificad con qué valor se cargaría X en estas dos instrucciones: LDX [prefijo,PCR] y LEAX [prefijo,PCR].
El resultado del enlazado de este programa vemos que no depende de la dirección de memoria donde se vaya a cargar:
307800861C8D09860AB7FF004FB7FF01308CED8D25340244444444270B810A25028B078B30B7FF00A6E4840F810A25028B078B30B7FF003502393402A6802705B7FF0020F7350239 307800861C8D09860AB7FF004FB7FF01308CED8D25340244444444270B810A25028B078B30B7FF00A6E4840F810A25028B078B30B7FF003502393402A6802705B7FF0020F7350239
Esta ristra de código la podemos escribir en cualquier posición de la memoria, que siempre funcionará como debe. Podemos quemar un chip de ROM con el código sin saber todavía dónde irá a parar en el mapa de memoria final.
Prestad atención, por último, que ciertos accesos a memoria no se deben hacer nunca relativos al PC. Por ejemplo, la dirección de memoria donde se escriben los caracteres que saldrán por la pantalla es, en nuestro sistema, siempre la 0xFF00, independientemente de dónde se cargue el código.
Todas las instrucciones que admiten el modo indizado/indirecto, admiten la indización sobre el PC. En particular, las instrucciones JMP y JSR.
IRQ y FIRQ se pueden enmascarar. Significa esto que, si están enmascaradas, aunque se active su señal, no se producirá el salto a la rutina de servicio. Las máscaras se encuentran en dos bits del registro CC, el I y el F, respectivamente.
FIRQ es especial por cuanto, en un interrupción normal, antes de saltar a la rutina de servicio, se almacenan en la pila todos los registros automáticamente. En una FIRQ, solamente se almacena el PC y es responsabilidad de la rutina de servicio guardar y restaurar los registros que vaya a usar.
Existe una tabla de salto al final de la memoria para que el procesador sepa dónde debe saltar cuando se produce cada tipo de interrupción. Las direcciones de los vectores de salto son: 0xFFFE (RESET), 0xFFFC (NMI), 0xFFFA (SWI), 0xFFF8 (IRQ), 0xFFF6 (FIRQ), 0xFFF4 (SWI2) y 0xFFF2 (SWI3).
Al final de una rutina de tratamiento de interrupciones, se debe ejecutar la instrucción RTI (ReTurn from Interrupt). Esta instrucción consulta el flag E del registro CC para ver si tiene que restaurar todos los registros o solamente el PC.
Primero se medirá cuál es el periodo del ciclo de reloj
(o la frecuencia, tanto da). Para ello, se hace un programa de
bastantes ciclos. Con la orden del sistema operativo time
se ejecuta en el simulador:
time m6809-run periodo.s19Dividiendo el tiempo que nos indica el sistema operativo entre el número de ciclos de nuestro programa, obtenemos el periodo del reloj del simulador.
Con la opción del simulador -I 1000
, hacemos que
el simulador produzca una interrupción IRQ cada 1000 ciclos de
reloj. Basándonos en el número calculado más arriba
programaremos una rutina de servicio y colocaremos el valor adecuado
en la máscara, para que se produzca la cuenta atrás
con la cadencia correcta.
Notas: como las interrupciones suceden mientras se está ejecutando el programa "principal", debéis dejar al procesador ocupado en algo (un bucle infinito, por ejemplo). También debéis imprimir un número por línea:
10 9 8 7 6 5 4 3 2 1 0
En la terminología del 6809 se denomina página de memoria a cualquiera de las zonas de memoria cuya dirección esté comprendida entre 0xnn00 y 0xnnFF. Así, la primera página es la comprendida entre las direcciones 0x0000 y 0x00FF. La segunda página, entre las direcciones 0x0100 y 0x01FF, y así sucesivamente.
Existe una página especial denominada página de acceso directo, que es la especificada por el registro DP. Si DP vale 7, por ejemplo, la página de acceso directo es la comprendida entre las direcciones 0x0700 y 0x07FF.
El modo de direccionamiento directo accede a los bytes de de la página de acceso directo. La ventaja fundamental que tiene es que no hace falta especificar la parte alta de la dirección en la instrucción, pues está en el registro DP. Esto ahora tamaño de código y hace que se ejecute más rápido. Para marcar que un acceso es directo, nuestro ensamblador utiliza el asterisco. Observad la diferencia:
0005 0F 03 [ 6] 17 clr *bandera 0007 7F 07 03 [ 7] 18 clr bandera
La página directa se ha puesto en la dirección 0x700
y siguientes. Hay una variable, cuyo nombre es bandera
,
que está en 0x703. El acceso extendido de la segunda línea
incluye la dirección completa 0x7F,0x07,0x03. El acceso
directo solamente necesita incluir el byte bajo de la dirección
y ocupa, por tanto, un byte menos: 0x0F,0x03. Además consume
también un ciclo menos del procesador.
Para que la pareja de ensamblador y enlazador, se pongan de acuerdo y se coordinen cuando se usa el modo directo, se debe seguir el siguiente esquema:
.globl
.setdp 0,DIRECTA
ldd #DIRECTA tfr a,dp
aslink -s -g DIRECTA=0x700 -b DIRECTA=0x700 programa.rel
SWI
, SWI2
, SWI3
RTI
.SETDP d,a
ls
cd
rm
man
cat
echo $?
time