Indeks English version English version

Elektronika MK-85 - programowanie w asemblerze

Uruchamianie programów maszynowych okazało się możliwe dzięki lukom w implementacji komendy INPUT.

Pierwszy przykładowy program

Tradycyjnie pierwszym przykładem, który się pisze przy poznawaniu nowego systemu lub języka programowania, jest program wyświetlający napis "Hello world". Tak będzie również w tym przypadku:

        .radix  16              ;wszystkie liczby szesnastkowe

start:  mov     #41,@#8264      ;zmienna systemowa
loop:   jsr     r4,print1
        .asciz  "HELLO WORLD! "
        br      loop

print1: jsr     pc,@#1248       ;procedura ROM wyświetlająca łańcuch
        rts     r4

Niestety, tego programu nie da się wpisać, ponieważ nie wszystkie kody znaków są dostępne z klawiatury, a poza tym program musi trochę wyglądać jak BASIC żeby został zaakceptowany przez edytor. Oto zmodyfikowana wersja programu spełniająca te warunki:

start:
8272:   bmi     82B6

8296:   jsr     r4,@#82B0
829A:   .asciz  "HELLO=WORLD!="
82A8:   jsr     r5,@#82AE
82AC:   sob     r0,8296
82AE:   rts     r5
82B0:   jsr     pc,@#1248
82B4:   rts     r4
82B6:   mov     #8041,@#8264
82BC:   sob     r0,8296

Tak wygląda gotowy program, który należy zapisać jako P0 (w odróżnieniu od pozostałych ma stały adres początkowy):

pierwszy program maszynowy

Zwykłe znaki alfabetu łacińskiego są w kolorze czarnym, cyrylica w kolorze czerwonym, znaki graficzne w kolorze zielonym, słowa kluczowe BASIC w kolorze niebieskim. Trzeba na to bardzo uważać, ponieważ różne znaki z różnych zestawów mogą wyglądać podobnie lub identycznie.

Szesnastkowy wydruk zawartości pamięci RAM przechowującej program

8260:  7B 81 00 00  81 00 00 00  00 08 00 0A  00 DF 38 32  {.............82
8270:  35 36 21 81  30 00 14 00  E7 00 1E 00  21 30 31 32  56!.0.......!012
8280:  33 34 35 36  37 38 39 00  28 00 21 30  31 32 33 34  3456789.(.!01234
8290:  35 36 37 38  39 00 1F 09  B0 82 48 45  4C 4C 4F 3D  56789.....HELLO=
82A0:  57 4F 52 4C  44 21 3D 00  5F 09 AE 82  0C 7E 85 00  WORLD!=._.......
82B0:  DF 09 48 12  84 00 DF 15  41 80 64 82  14 7E 00 00  ..H.....A.d.....

plik mk85fipr.zip - plik obrazu pamięci RAM dla emulatora

Uruchomienie programu

  1. Start programu za pomocą komendy RUN.
  2. Gdy pojawi się znak zapytania wyświetlony przez komendę INPUT, należy wpisać input string i potwierdzić klawiszem [EXE]. Zamiast tego można również po prostu wcisnąć klawisz [AC].
  3. Gdy program się ponownie zatrzyma wyświetlając kursor, należy wcinąć klawisz [EXE].
  4. Na wyświetlaczu powinien się teraz pojawić przesuwający się tekst. Program można zatrzymać klawiszem [STOP].

Jak to działa

Modyfikacja zawartości pamięci

Komenda INPUT traktuje stałą numeryczną w polu argumentu tak jak zmienną łańcuchową $ (w wyniku braku sprawdzenia co rzeczywiście jest zwracane przez procedurę przetwarzającą wyrażenia w okolicy adresu 1C62). Kopiuje ona wpisany łańcuch o długości do 1E znaków pod adres pamięci przeznaczonej dla zmiennej. Adres zmiennej jest pobierany z drugiego słowa danej na stosie. W przypadku stałej numerycznej to słowo zawiera pierwsze cztery cyfry mantysy.

Uruchamianie programów maszynowych

Jest to możliwe przez wykorzystanie instrukcji skoku pośredniego poprzez zmienną 8258, znajdującego się w pamięci ROM pod adresem 0E76. Ten fragment kodu jest między innymi dostępny z głównej pętli obsługi trybu interaktywnego, mniej więcej pod adresem 0BC6. Oryginalnie jest przeznaczony do wznowienia wykonywania programu BASIC po wystąpieniu warunku STOP.

Procedura obsługi komendy INPUT używa zmiennej 8258 do tymczasowego przechowania wskaźnika do interpretowanego programu BASIC. Wynika z tego, że można wykonać jako program maszynowy dane następujące po INPUT, jeżeli udałoby się przejść do pętli trybu interaktywnego przy spełnieniu poniższych warunków:

W takiej sytuacji wciśnięcie klawisza [EXE] jest błędnie interpretowane jako żądanie wznowienia wykonywania programu BASIC, co w efekcie powoduje uruchomienie kodu maszynowego użytkownika wskazywanego przez zmienną 8258.

Aktualnie znane mi są dwie metody przejścia do pętli trybu interaktywnego:

1. Spowodowanie fałszywego stanu STOP

Zwykle stan STOP jest spowodowany przez wciśnięcie klawisza [STOP], wykonaniem instrukcji STOP, lub pracą w trybie TRACE. Obsługą tego stanu zajmuje się procedura 179A, która wyświetla numer programu i wiersza, a następnie przechodzi do trybu interaktywnego.

Można wymusić taki stan przez ustawienie bitu 0 zmiennej 8256 (mode) zmieniając jej zawartość z 8000 na 8001. Odbywa się to przez wpisanie łańcucha input string w odpowiedzi na komendę INPUT 8256 w wierszu 10. Znaku o kodzie 01 wprawdzie nie da się wpisać z klawiatury, ale znak o kodzie 31 ('1') pasuje również. Jednak sam znak '1' zmieniłby również bardziej znaczący bajt zmiennej mode na 00, ponieważ łańcuch zakończony jest zerem. Konieczne jest wpisanie jeszcze trzech dalszych znaków, żeby również zachować stan zmiennej 8258. Końcowy bajt 00 naruszy dopiero zmienną 825A, która nie jest w ważna w tym kontekście.

W celu przejścia to trybu interaktywnego pozostało wykonać jakąś komendę BASIC dozwoloną zarówno w programie jak i w trybie interaktywnym, na przykład VAC (w wierszu 20). Ta grupa komend ma wspólny punkt wyjścia pod adresem 1652, a stamtąd do pętli trybu interaktywnego 0BA0 lub pętli interpretacji programu BASIC, zależnie od stanu bitu 0 zmiennej mode.

2. Wyjście z INPUT klawiszem [AC]

Wciśnięcie klawisza [AC] jest obsługiwane pod adresem 05C0 w ROM. Ta procedura kasuje bufor wejściowy oraz wyświetlacz, a następnie przechodzi do pętli trybu interaktywnego, nie modyfikując zmiennych 8256 i 8258. Przy tej metodzie argument komendy INPUT jest bez znaczenia (nie musi to być stała numeryczna, można użyć dowolnej zmiennej). Również zbędna jest komenda VAC w wierszu 20 (nigdy nie jest wykonywana).


Bardziej zaawansowane programowanie

Pisanie programów maszynowych wyglądających jak BASIC okazało się kłopotliwym i czasochłonnym zajęciem. Poniżej opisana metoda pozwala na uruchamianie dowolnych programów bez żadnych sztucznych ograniczeń.

Przydział pamięci

Ten etap rezerwuje bezpieczne miejsce w pamięci dla programów maszynowych przez modyfikację zmiennej systemowej ramtop pod adresem 8252. Obszar pamięci od ramtop aż do końca pozostanie niedostępny dla systemu BASIC.
Poniższy program modyfikuje zmienną ramtop:

10 INPUT 8252:VAC

Wpisanie do zmiennej ramtop wartości 8680 rezerwuje obszar pamięci o rozmiarze 0180 bajtów rozpoczynający się od adresu 8680 (wszystkie liczby szesnastkowe). Ta wartość odpowiada łańcuchowi input string, który trzeba wpisać w odpowiedzi na INPUT. Program wystarczy uruchomić tylko raz. Nowa konfiguracja pamięci będzie zachowana tak długo, jak nie zostanie wciśnięty przełącznik init na tylnej ściance lub wywołana komenda TEST.

Program ładujący

Następny program kopiuje do przydzielonego obszaru plik binarny zapisany jako ciąg liczb szesnastkowych w wierszach programu BASIC, a następnie wykonuje instrukcję skoku na początek tego obszaru. Tak jak w poprzednim przykładzie, pewne udziwnienia są spowodowane koniecznością dopasowania się do struktury wymuszonej przez język BASIC.

        .radix  16

        .loc    8272
start:  bmi     start1

        .loc    8286
exec:   jmp     (r5)

loop:   tstb    (r2)+           ;separator lub znak końca wiersza
        nop
        bne     byte
        nop
        jsr     r0,@#return     ;wypełniacz bez funkcji
line:   tstb    (r2)+
        cmpb    (r2)+,#2727     ;test czy numer wiersza > 9987
        bcc     exec1
        tstb    (r2)+           ;pominięcie znaku '!'
        nop
        jsr     r0,return       ;wypełniacz bez funkcji
byte:   jsr     pc,@#digit
        nop
        jsr     pc,digit
        inc     r4              ;wypełniacz bez funkcji
        movb    r0,(r3)+
        sob     r1,loop
exec1:  sob     r2,exec

start1: bmi     start2

return: rts     r0

digit:  asl     r0
        asl     r0
        asl     r0
        asl     r0
        nop
        mov     r0,-(sp)
        movb    (r2)+,r0
        cmpb    r0,#4141        ;'A'
        bcs     digit1
        sub     #3737,r0
        add     (sp)+,r0
        rts     pc

start2: mov     #4080,r1
        add     #8800-4080,r1   ;r1 = koniec pamięci RAM
        nop
        mov     #4080,r2
        add     #data+1-4080,r2 ;r2 = adres źródłowy
        nop
        mov     8252,r3         ;adres docelowy = zmienna ramtop
        mov     r3,r5           ;wektor skoku
        sub     r3,r1           ;maksymalna ilość bajtów
        sob     r2,line

digit1: sub     #3030,r0
        add     (sp)+,r0
        rts     pc

data:                           ;tu rozpoczynają się dane

program ładujący

Po programie ładującym oczekiwane są wiersze BASIC zawierające listę bajtów w formacie szesnastkowym poprzedzoną wykrzyknikiem. Poszczególne bajty rozdzielone są pojedynczym znakiem (preferowany przecinek lub spacja). Ilość bajtów w wierszu może być dowolna. Wszystko powinno być zakończone wierszem o numerze 9988 lub większym. Poniższy przykład powstał w wyniku asemblacji pierwotnej wersji programu "Hello world":

9000 !DF,15,41,00,64,82,37,09
9010 !10,00,48,45,4C,4C,4F,20
9020 !57,4F,52,4C,44,21,20,00
9030 !F6,01,DF,09,48,12,84,00
9999 END

Jest to sporo wpisywania i łatwo się pomylić. Wszystko trzeba dwukrotnie sprawdzić!
Program ładujący razem z danymi wyglądają w pamięci tak:

8260:  6D 81 00 80  00 80 00 00  00 00 00 0A  00 DF 38 32  m.............82
8270:  35 36 21 81  30 00 14 00  E7 00 1E 00  21 30 31 32  56!.0.......!012
8280:  33 34 35 36  37 00 4D 00  D2 8B A0 00  0B 02 A0 00  34567.M.........
8290:  1F 08 B8 82  D2 8B 97 A4  27 27 0C 86  D2 8B A0 00  ........''......
82A0:  37 08 14 00  DF 09 BA 82  A0 00 F7 09  0C 00 84 0A  7...............
82B0:  13 90 56 7E  98 7E 0F 81  80 00 C0 0C  C0 0C C0 0C  ..V.............
82C0:  C0 0C A0 00  26 10 80 94  17 A0 41 41  13 87 C0 E5  ....&.....AA....
82D0:  37 37 80 65  87 00 C1 15  80 40 C1 65  80 47 A0 00  77.e.....@.e.G..
82E0:  C2 15 80 40  C2 65 7D 42  A0 00 C3 17  52 82 C5 10  ...@.e}B....R...
82F0:  C1 E0 B0 7E  C0 E5 30 30  80 65 87 00  28 23 21 44  ......00.e..(#!D
8300:  46 2C 31 35  2C 34 31 2C  30 30 2C 36  34 2C 38 32  F,15,41,00,64,82
8310:  2C 33 37 2C  30 39 00 32  23 21 31 30  2C 30 30 2C  ,37,09.2#!10,00,
8320:  34 38 2C 34  35 2C 34 43  2C 34 43 2C  34 46 2C 32  48,45,4C,4C,4F,2
8330:  30 00 3C 23  21 35 37 2C  34 46 2C 35  32 2C 34 43  0.<#!57,4F,52,4C
8340:  2C 34 34 2C  32 31 2C 32  30 2C 30 30  00 46 23 21  ,44,21,20,00.F#!
8350:  46 36 2C 30  31 2C 44 46  2C 30 39 2C  34 38 2C 31  F6,01,DF,09,48,1
8360:  32 2C 38 34  2C 30 30 00  0F 27 E4 00  00 00 00 00  2,84,00..'......

plik mk85load.zip - plik obrazu pamięci RAM dla emulatora

Program uruchamia się w identyczny sposób jak poprzednią wersję "Hello world".

Gdy kod maszynowy zostanie przekopiowany we właściwe miejsce, program ładujący może zostać skasowany. Kod pozostanie w pamięci dopóki nie zostanie wciśnięty przełącznik init na tylnej ściance lub wywołana komenda TEST.

Program startujący

Poniższy krótki program uruchamia załadowany kod maszynowy przy braku programu ładującego:

82B6:   mov     @#8252,r0
82BA:   jmp     (r0)

program startujący

8260:  6D 81 00 80  00 80 00 00  00 00 00 0A  00 DF 38 32  m.............82
8270:  35 36 21 81  30 00 14 00  E7 00 1E 00  21 30 31 32  56!.0.......!012
8280:  33 34 35 36  37 38 39 00  28 00 21 30  31 32 33 34  3456789.(.!01234
8290:  35 36 37 38  39 00 32 00  21 30 31 32  33 34 35 36  56789.2.!0123456
82A0:  37 38 39 00  3C 00 21 30  31 32 33 34  35 36 37 38  789.<.!012345678
82B0:  39 30 31 32  33 00 C0 17  52 82 48 00  00 00 00 00  90123...R.H.....

plik mk85strt.zip - plik obrazu pamięci RAM dla emulatora