Amiga 1200

Amiga, asembler i system

Wy­ko­rzy­sta­nie sys­te­mu ope­ra­cyj­ne­go Amigi w pro­gra­mach w asem­ble­rze
Grzegorz Kraszewski, 15 kwietnia 2016

Więk­szość kur­sów pro­gra­mo­wa­nia w asem­ble­rze na Ami­dze zu­peł­nie po­mi­ja sys­tem ope­ra­cyj­ny kom­pu­te­ra i spo­sób ko­rzy­sta­nia z niego. Au­to­rzy sku­pia­ją się na samym pro­ce­so­rze, oraz wy­ko­rzy­sta­niu ukła­dów spe­cja­li­zo­wa­nych bez po­śred­nic­twa sys­te­mu, po­przez bez­po­śred­nie pro­gra­mo­wa­nie ich re­je­strów. Takie po­dej­ście jest czę­sto nie­unik­nio­ne przy pro­gra­mo­wa­niu gier i dem sce­no­wych, bo daje naj­więk­szą szyb­kość. Z dru­giej stro­ny w pro­gra­mach użyt­ko­wych sys­tem ope­ra­cyj­ny od­da­je nie­oce­nio­ne usłu­gi, z czę­ści z nich można też sko­rzy­stać pi­sząc grę czy demo.

Biblioteki systemu

Funk­cje sys­te­mu ope­ra­cyj­ne­go Amigi po­gru­po­wa­ne są w bi­blio­te­ki we­dług za­sto­so­wa­nia. Więk­szość pod­sta­wo­wych bi­blio­tek znaj­du­je się w pa­mię­ci ROM Amigi, czyli Kick­star­cie. Po­zo­sta­łe znaj­du­ją się na dysku twar­dym lub dys­kiet­ce, w za­leż­no­ści od tego, z ja­kie­go urzą­dze­nia star­tu­je kom­pu­ter. Na po­czą­tek uży­je­my dwóch naj­waż­niej­szych bi­blio­tek sys­te­mo­wych: exec.librarydos.library. Pierw­sza z nich jest rdze­niem sys­te­mu. Kon­tro­lu­je wie­lo­za­da­nio­wość, uru­cha­mia­nie pro­ce­sów, ko­mu­ni­ka­cję mię­dzy pro­ce­sa­mi, za­rzą­dza­nie pa­mię­cią i tak dalej. Druga zaś od­po­wia­da za ope­ra­cje wej­ścia/wyj­ścia, a więc do­stęp do pli­ków i urzą­dzeń.

Baza biblioteki

W każ­dym ję­zy­ku pro­gra­mo­wa­nia do­stęp do funk­cji bi­blio­te­ki uzy­sku­je­my przez adres w pa­mię­ci, pod któ­rym znaj­du­je się struk­tu­ra da­nych zwana bazą bi­blio­te­ki. Adres jest ten dy­na­micz­ny, nie tylko różny w róż­nych mo­de­lach Amig, ale zmie­niać się może nawet przy ko­lej­nych uru­cho­mie­niach sys­te­mu na tej samej Ami­dze. Adres bazy bi­blio­te­ki uzy­sku­je­my w mo­men­cie jej otwie­ra­nia. Każdy pro­gram przed uży­ciem bi­blio­te­ki musi ją naj­pierw otwo­rzyć. Robi się to przy po­mo­cy funk­cji Open­Li­bra­ry() z bi­blio­te­ki exec.library. Wy­ni­kiem tej funk­cji jest wła­śnie baza bi­blio­te­ki.

Wy­jąt­kiem jest tu sama exec.library. Po­nie­waż to ona służy do otwie­ra­nia in­nych bi­blio­tek, sama musi być au­to­ma­tycz­nie otwar­ta przez sys­tem przy star­cie. Ina­czej prze­cież nie mo­gli­by­śmy sko­rzy­stać z funk­cji Open­Li­bra­ry(). Zatem adres bazy exec.library znaj­du­je się za­wsze pod ad­re­sem $00000004 w pa­mię­ci.

Baza bi­blio­te­ki roz­cią­ga się w pa­mię­ci za­rów­no w górę (w stro­nę mniej­szych ad­re­sów), jak i w dół (w stro­nę ad­re­sów więk­szych) od swo­je­go ad­re­su. Idąc w dół na­po­tka­my dane bi­blio­te­ki. Nie­któ­re z nich mogą być pu­blicz­ne, do wy­ko­rzy­sta­nia w pro­gra­mach, nie­któ­re zaś są pry­wat­ne i nie na­le­ży w nich grze­bać. Idąc zaś w górę na­po­tka­my ta­bli­cę sko­ków do funk­cji bi­blio­te­ki. W ta­bli­cy tej znaj­du­ją się rze­czy­wi­ste ad­re­sy w pa­mię­ci po­szcze­gól­nych funk­cji. Dzię­ki ta­bli­com sko­ków, w róż­nych wer­sjach Kick­star­tu funk­cje mogą się znaj­do­wać pod róż­ny­mi ad­re­sa­mi, ale pro­gra­my dzia­ła­ją na wszyst­kich wer­sjach bez mo­dy­fi­ka­cji.

Wywołanie funkcji z biblioteki

Przed wy­wo­ła­niem funk­cji na­le­ży w od­po­wied­nich re­je­strach pro­ce­so­ra umie­ścić jej ar­gu­men­ty. Roz­miesz­cze­nie to jest opi­sa­ne w do­ku­men­ta­cji sys­te­mu, dla każ­dej funk­cji od­dziel­nie. Naj­czę­ściej pa­ra­me­try licz­bo­we umiesz­cza­ne są w ko­lej­nych re­je­strach da­nych pro­ce­so­ra, pa­ra­me­try bę­dą­ce ad­re­sa­mi – w re­je­strach ad­re­so­wych. Na­stęp­nie w re­je­strze A6 umiesz­cza­my adres bazy bi­blio­te­ki. Musi to być za­wsze A6, po­nie­waż więk­szość funk­cji ko­rzy­sta w swym wnę­trzu z tego ad­re­su. Skok do funk­cji wy­ko­nu­je­my po­przez ta­bli­cę sko­ków, wy­ko­rzy­stu­jąc roz­kaz JSR z ad­re­so­wa­niem z prze­su­nię­ciem wzglę­dem re­je­stru A6. Oto przy­kład po­ka­zu­ją­cy wy­wo­ła­nie funk­cji Open­Li­bra­ry:

            MOVEQ   #34,D0
            LEA     DosName,A1
            MOVEA.L $00000004,A6
            JSR     -552(A6)

            ...

DosName:    DC.B    "dos.library",0

Zgod­nie z do­ku­men­ta­cją, funk­cja Open­Li­bra­ry() przyj­mu­je dwa ar­gu­men­ty. Mi­ni­mal­ną wer­sję bi­blio­te­ki umiesz­cza­my w re­je­strze D0, zaś adres nazwy tej bi­blio­te­ki w re­je­strze A1. Adres jest zwy­kłym łań­cu­chem zna­ków za­koń­czo­nym zerem, umie­ści­łem go w ko­dzie pro­gra­mu ko­rzy­sta­jąc z pseu­do­in­struk­cji DC.B. Potem, po­nie­waż funk­cja jest z exec.library, ła­du­ję adres bazy spod sta­łe­go ad­re­su $00000004. Ostat­nim kro­kiem jest wy­ko­na­nie skoku do ta­bli­cy sko­ków. O 552 bajty przed ad­re­sem bazy znaj­du­je się in­struk­cja JMP skoku pod rze­czy­wi­sty adres kodu Open­Li­bra­ry(). Wy­ko­na­nie funk­cji od­by­wa się więc na­stę­pu­ją­co:

Definiowanie przesunięć

Frag­ment kodu w stylu „JSR -552(A6)” jest mało czy­tel­ny. Dość oczy­wi­stym kro­kiem jest zde­fi­nio­wa­nie prze­su­nięć od bazy do sko­ków do po­szcze­gól­nych funk­cji (zwa­nych żar­go­no­wo „of­f­se­ta­mi funk­cji”) jako sta­łych, np. w taki spo­sób:

SysBase     = 4
OpenLibrary = -552

            MOVEA.L SysBase,A6
            JSR     OpenLibrary(A6)

Ręcz­ne de­kla­ro­wa­nie prze­su­nięć i in­nych uży­wa­nych sta­łych jest wy­god­ne w ma­łych pro­gra­mach, uży­wa­ją­cych rap­tem kilku funk­cji. W więk­szych pro­jek­tach można sko­rzy­stać z sys­te­mo­wych pli­ków na­głów­ko­wych dla asem­ble­ra (z roz­sze­rze­niem *.i) za­wie­ra­ją­cych de­fi­ni­cje prze­su­nięć funk­cji, pól sys­te­mo­wych struk­tur i inne sys­te­mo­we stałe.

Zachowywanie zawartości rejestrów

Przy­ję­ta w Ami­ga­OS kon­wen­cja trak­tu­je re­je­stry D0, D1, A0 i A1 jako scratch re­gi­sters, a więc każda funk­cja sys­te­mo­wa może zmie­nić ich za­war­tość i po­zo­sta­wić je w sta­nie do­wol­nym. D0 jest za­zwy­czaj uży­wa­ny jako wynik funk­cji. Po­zo­sta­łe re­je­stry są nie­zmie­nio­ne po wyj­ściu z funk­cji sys­te­mo­wej. Je­że­li funk­cja ich używa, prze­cho­wu­je ich za­war­tość na sto­sie pro­ce­so­ra i od­twa­rza przed wyj­ściem.

„Hello World”

Ko­rzy­sta­nie z sys­te­mu roz­pocz­nie­my pi­sząc kla­sycz­ny pro­gram wy­pi­su­ją­cy w kon­so­li tek­sto­wej po­wyż­sze zda­nie. Do jego wy­pi­sa­nia uży­je­my funk­cji PutStr() wy­pi­su­ją­cej wska­za­ny tekst na stan­dar­do­we wyj­ście pro­gra­mu. Funk­cja ta znaj­du­je się w dos.library, więc doj­dzie do tego jej otwar­cie i za­mknię­cie. Zde­fi­niuj­my prze­su­nię­cia uży­wa­nych funk­cji:

SysBase         = 4
OpenLibrary     = -552
CloseLibrary    = -414
PutStr          = -948

A oto i pro­gram w całej oka­za­ło­ści, liczy sobie 14 roz­ka­zów pro­ce­so­ra.

            LEA     DosName,A1
            MOVEQ   #36,D0
            MOVEA.L SysBase,A6
            JSR     OpenLibrary(A6)

            TST.L   D0
            BEQ.S   NoDos

            MOVE.L  #Hello,D1
            MOVEA.L D0,A6
            JSR     PutStr(A6)

            MOVEA.L A6,A1
            MOVEA.L SysBase,A6
            JSR     CloseLibrary(A6)

NoDos:      CLR.L   D0
            RTS

DosName     DC.B    "dos.library",0
Hello       DC.B    "Hello World!",10,0

Pierw­sze 4 roz­ka­zy to otwar­cie dos.library. Potem spraw­dzam, czy otwar­cie się udało. Je­że­li nie (zero w D0), prze­ska­ku­ję od razu do wyj­ścia z pro­gra­mu. Jako mi­ni­mal­ną wer­sję bi­blio­te­ki po­da­ję 36 (Kick­start 2.0), bo w tej wer­sji po­ja­wi­ła się funk­cja PutStr(). Próba uru­cho­mie­nia na Ami­gach z wcze­śniej­szym Kic­kiem, spo­wo­du­je po pro­stu wyj­ście z pro­gra­mu po prze­sko­cze­niu do ety­kie­ty NoDos.

Ko­lej­ny blok to wy­wo­ła­nie PutStr(). Bi­blio­te­ka dos.library jest nieco nie­ty­po­wa, bo nawet ar­gu­men­ty bę­dą­ce ad­re­sa­mi (tu adres tek­stu do wy­pi­sa­nia) są prze­ka­zy­wa­ne w re­je­strach da­nych. Bazę bi­blio­te­ki dos.library po pro­stu prze­miesz­czam z D0 do A6.

Blok trze­ci to zwol­nie­nie za­so­bów, czyli za­mknię­cie dos.library. Bi­blio­te­ki exec.library nie trze­ba za­my­kać, bo i też nie mu­sie­li­śmy jej otwie­rać. Ostat­nim kro­kiem jest wy­ze­ro­wa­nie D0. To co po­zo­sta­wi­my w tym re­je­strze przy wyj­ściu z pro­gra­mu, zo­sta­nie przez sys­tem zin­ter­pre­to­wa­ne jako wynik wy­ko­na­nia pro­gra­mu, war­tość zero ozna­cza wy­ko­na­nie bez błę­dów.

Po­nie­waż sko­rzy­sta­li­śmy do wy­dru­ko­wa­nia tek­stu ze stan­dar­do­we­go wyj­ścia, pro­gram po­praw­nie współ­pra­cu­je np. z prze­kie­ro­wa­niem wyj­ścia do pliku. Np. wy­wo­ła­nie go w taki spo­sób

hello >RAM:pff.txt

umieści nam tekst „Hello World!” w pli­ku RAM:pff.txt.

Słów kilka na temat łań­cu­chów tek­sto­wych. Sys­tem Ami­ga­OS używa (w więk­szo­ści) łań­cu­chów za­koń­czo­nych baj­tem $00, tak jak język C. Jed­nak w prze­ci­wień­stwie do C, koń­czą­ce zero mu­si­my w de­kla­ra­cji DC.B wpi­sać jaw­nie. W łań­cu­chu wy­pi­sy­wa­nym na wyj­ście do­dat­ko­wo do­da­łem znak końca linii (kod ASCII 10).

Uwaga na koniec

Po­wyż­szy pro­gram co praw­da po­praw­nie wy­ko­na się w oknie CLI, ale gdy do­da­my mu ikonę i spró­bu­je­my uru­cho­mić spod Work­ben­cha, praw­do­po­dob­nie za­wie­si się. Bra­ku­je mu bo­wiem tak zwa­ne­go kodu star­to­we­go, czyli po­praw­nej ob­słu­gi uru­cho­mie­nia z WB. Gdy­bym ją dodał, pro­gram stał­by się kil­ka­krot­nie dłuż­szy i trud­niej­szy do prze­ana­li­zo­wa­nia.

Oma­wia­ny kod na GitHubie