RSS

Kod maszynowy i asembler

Liczba odsłon: 3093

Sercem, a ra­czej móz­giem kom­pu­te­ra jest pro­ce­sor: układ, któ­re­go za­da­niem jest reali­zo­wa­nie prze­ka­zy­wa­nych mu roz­ka­zów. Wszystkie obec­nie pro­jek­to­wa­ne i pro­du­ko­wa­ne kom­pu­te­ry rea­li­zu­ją kon­cep­cję von Neumanna, wed­ług któ­rej kod pro­gra­mu oraz da­ne po­trzeb­ne do je­go dzia­ła­nia zaj­mu­ją ten sam ob­szar pa­mię­ci i są re­pre­zen­to­wa­ne w jed­na­ko­wy spo­sób: ja­ko licz­by o okreś­lo­nej dłu­goś­ci li­czo­nej w bi­tach. Aby pro­gra­mo­wa­niem kom­pu­te­ra mógł się za­jąć czło­wiek, na­le­ża­ło opra­co­wać spo­sób prze­kształ­ca­nia za­pi­su pro­gra­mu na­tu­ral­ne­go dla lu­dzi – a więc zreali­zo­wa­ne­go z wy­ko­rzys­ta­niem li­ter, nazw i sym­bo­li – na je­dy­ny zro­zu­mia­ły dla ma­szy­ny: licz­by.

Pierwsze pro­gra­my mu­sia­ły po­wsta­wać bez­po­śred­nio w ko­dzie ma­szy­no­wym. Wpatrując się w wiel­kie tab­li­ce za­wie­ra­ją­ce li­stę wszyst­kich roz­ka­zów rea­li­zo­wa­nych przez pro­ce­sor (wraz z ich ko­da­mi), pro­gra­miś­ci mu­sie­li za­mie­nić włas­ne pro­gra­my na licz­by, wpro­wa­dzić te licz­by w ja­kiś spo­sób do pa­mię­ci kom­pu­te­ra i uru­cho­mić ma­szy­nę li­cząc na to, że wy­ko­na ona pro­gram bez prob­le­mów.

Na przy­kład, frag­ment pro­gra­mu umoż­li­wia­ją­cy zwięk­sze­nie o je­den licz­by za­pi­sa­nej w ko­mór­ce pa­mię­ci o ad­re­sie 100016, za­pi­sa­ny w ko­dzie ma­szy­no­wym mi­kro­pro­ce­so­ra Zilog Z80 wy­glą­dał­by na­stę­pu­ją­co:

21 00 10 34

Owszem, ma­szy­na wy­ko­nu­je ten pro­gram bez prob­le­mu, jed­nak dla czło­wie­ka – na­wet wy­kwa­li­fi­ko­wa­ne­go pro­gra­mis­ty – roz­szyf­ro­wa­nie frag­men­tu ko­du za­pi­sa­ne­go w tej po­sta­ci jest trud­ne. Aby do­wie­dzieć się, co po­wyż­sze czte­ry baj­ty pro­gra­mu ro­bią, na­le­ża­ło­by do­ko­nać – zno­wu ręcz­nie! – dis­asemb­lac­ji, czy­li za­mia­ny ko­dów na sym­bo­licz­ne ozna­cze­nia in­struk­cji:

21 00 10  LD HL, 1000
34        INC (HL)

Pisanie pro­gra­mu za­zwy­czaj rów­nież za­czy­na­ło się od po­sta­ci sym­bo­licz­nej, do­pie­ro przed pro­gra­mo­wa­niem pa­mię­ci kom­pu­te­ra do­ko­nu­jąc asemb­lac­ji, czy­li za­mia­ny roz­ka­zów za­pi­sa­nych li­te­ra­mi na od­po­wia­da­ją­ce im ko­dy. Benedyktyńska to pra­ca, wpro­wa­dza­ją­ca do­dat­ko­wy etap po­dat­noś­ci na po­mył­ki i prze­o­cze­nia. Nic dziw­ne­go, że szyb­ko po­sta­no­wio­no na­pi­sać pro­gram, któ­ry po­zwo­li przy­go­to­wy­wać na­stęp­ne pro­gra­my w po­sta­ci sym­bo­licz­nej, a na­stęp­nie auto­ma­tycz­nie do­ko­na tłu­ma­cze­nia na kod ma­szy­no­wy.

Program ta­ki, jak i sam ję­zyk sym­bo­licz­ny od­po­wia­da­ją­cy ko­do­wi ma­szy­no­we­mu, na­zwa­no asemb­le­rem (ang. assembler, assembly langu­age). W naj­prost­szej po­sta­ci asem­bler miał za za­da­nie do­ko­ny­wać za­mia­ny ko­lej­nych mne­mo­ni­ków (roz­ka­zów za­pi­sa­nych sym­bo­licz­nie) na od­po­wia­da­ją­ce im ko­dy, re­zer­wu­jąc od­po­wied­nią ilość miej­sca w pa­mię­ci na każ­dą z in­struk­cji. Kolejne, roz­bu­do­wa­ne wer­sje asemb­le­rów wpro­wa­dza­ły dal­sze funk­cje ułat­wia­ją­ce pra­cę pro­gra­mis­ty: moż­li­wość dyna­micz­nej zmia­ny po­ło­że­nia pro­gra­mu w pa­mię­ci, de­kla­ro­wa­nie sta­łych war­toś­ci o uni­ka­to­wych sym­bo­lach, two­rze­nie zmien­nych umiesz­cza­nych w okreś­lo­nym lub do­wol­nym miej­scu pa­mię­ci czy wy­peł­nia­nie blo­ków pa­mię­ci licz­ba­mi lub sek­wenc­ja­mi war­toś­ci. Na przy­kład, pro­gram zwięk­sza­ją­cy o je­den za­war­tość trzech ko­lej­nych ko­mó­rek pa­mię­ci mógł­by wy­glą­dać na­stę­pu­ją­co:

      ORG 900h
ADRES EQU 1000h
START LD HL, ADRES  ; załaduj adres początkowy
      INC (HL)      ; inkrementacja 1
      INC HL        ; następny adres
      INC (HL)      ; inkrementacja 2
      INC HL        ; następny adres
      INC (HL)      ; inkrementacja 3

Zmiana po­ło­że­nia pro­gra­mu wy­ma­ga­ła­by te­raz je­dy­nie zmia­ny wier­sza za­wie­ra­ją­ce­go dy­rek­ty­wę ORG, zaś zmia­na adre­su po­cząt­ko­we­go ob­sza­ru da­nych — zmia­ny wier­sza za­wie­ra­ją­ce­go dy­rek­ty­wę EQU. Tłuma­cze­nie po­wyż­sze­go pro­gra­mu na po­stać ma­szy­no­wą (asemb­lac­ja) poz­wo­li­ło­by uzys­kać na­stę­pu­ją­cy ciąg baj­tów roz­po­czy­na­ją­cy się od adre­su 90016:

0900 21 00 10 34 33 34 33 34

Asembler sta­no­wił ol­brzy­mi krok na­przód je­że­li cho­dzi o two­rze­nie pro­gra­mu, jed­nak tyl­ko tro­chę ułat­wiał pro­ces uru­cha­mia­nia na­pi­sa­ne­go pro­gra­mu. Asembler bo­wiem do­ko­nu­je tłu­ma­cze­nia pro­gra­mu asemb­le­ro­we­go na po­stać ma­szy­no­wą, lecz na tym koń­czy się je­go ro­la: nie po­zwa­la śle­dzić pro­gra­mu, ob­ser­wo­wać go w dzia­ła­niu czy mo­dy­fi­ko­wać; co naj­wy­żej przy­spie­sza pro­ces wpro­wa­dza­nia i wy­pró­bo­wy­wa­nia po­pra­wek.

Aby uspraw­nić uru­cha­mia­nie i diag­no­zo­wa­nie pro­gra­mów, stwo­rzo­no no­wą ka­te­go­rię na­rzę­dzia: moni­tor ję­zy­ka ma­szy­no­we­go. Monitor jest pro­gra­mem pry­mi­tyw­nym, częs­to trud­nym w uży­ciu, mu­si bo­wiem zaj­mo­wać w pa­mię­ci jak naj­mniej miej­sca, prze­zna­czo­ne­go prze­cież na właś­ci­wy, diag­no­zo­wa­ny pro­gram. Oczywiś­cie, im więk­sze moż­li­woś­ci kom­pu­te­ra, tym bar­dziej roz­bu­do­wa­ny mo­że być moni­tor ję­zy­ka ma­szy­no­we­go.

Oto funk­cje, ja­kie ofe­ru­ją za­zwy­czaj użyt­kow­ni­ko­wi moni­to­ry:

Niektóre moni­to­ry zna­ją jesz­cze cie­kaw­sze sztucz­ki. Możliwe jest na przy­kład ciąg­łe śle­dze­nie frag­men­tu pa­mię­ci w wy­dzie­lo­nym ob­sza­rze ekra­nu, ciąg­łe wy­świet­la­nie adre­su aktu­al­nie wy­ko­ny­wa­nej in­struk­cji lub de­fi­nio­wa­nie adre­sów pa­mię­ci, spod któ­rych pró­ba wy­ko­na­nia in­struk­cji ma spo­wo­do­wać prze­rwa­nie dzia­ła­nia pro­gra­mu i prze­ka­za­nie ste­ro­wa­nia mo­ni­to­ro­wi (tak zwa­na pu­łap­ka).

Ponie­waż przy­dat­ność moni­to­ra dla pro­gra­mis­ty jest ol­brzy­mia, znaj­do­wał się on częs­to w pa­mię­ci sta­łej kom­pu­te­ra na­wet, je­że­li da­ny mo­del nie był prze­zna­czo­ny wy­łącz­nie dla pro­gra­mis­tów. Z kolei kom­pu­te­ry jed­no­płyt­ko­we, prze­zna­czo­ne głów­nie do pro­gra­mo­wa­nia, częs­to za­wie­ra­ły w pa­mię­ci sta­łej za­rów­no moni­tor, jak i asem­bler, oszczę­dza­jąc pro­gra­mi­ście cza­su po­trzeb­ne­go na za­ła­do­wa­nie nie­za­leż­nych pro­gra­mów i chro­niąc kod moni­to­ra przed usz­ko­dze­niem przez dzia­ła­ją­cy pro­gram.

Ciągły wzrost stop­nia skom­pli­ko­wa­nia pro­gra­mów oraz ich obję­toś­ci spo­wo­do­wał, że pi­sa­nie pro­gra­mów asemb­le­ro­wych sta­ło się mę­czar­nią. Utrzyma­nie ła­du i po­rząd­ku w pro­gra­mie li­czą­cym set­ki ty­się­cy wier­szy za­kra­wa­ło na cud, a plik z ko­dem źród­ło­wym osią­gał roz­mia­ry li­czo­ne w set­kach ki­lo­baj­tów. Rozwią­za­niem by­ło po­dzie­le­nie du­żych pro­gra­mów na frag­men­ty i łą­cze­nie ich w mo­men­cie two­rze­nia pli­ku wy­ko­ny­wal­ne­go pro­gra­mu. Taki po­dział na asemb­lac­ję i kon­so­li­dac­ję (ang. linking) był ko­rzyst­ny jesz­cze pod dwo­ma wzglę­da­mi: zmia­ny wpro­wa­dzo­ne w jed­nym frag­men­cie wy­ma­ga­ły po­now­nej asemb­lac­ji tyl­ko te­go frag­men­tu, a częs­to uży­wa­ne pod­pro­gra­my i zbio­ry da­nych moż­na by­ło umieś­cić w jed­nym frag­men­cie (bi­blio­te­ce) współ­dzie­lo­nym przez wie­le pro­gra­mów.

Progra­my asemb­le­ro­we częs­to za­wie­ra­ją też pow­ta­rza­ją­ce się frag­men­ty ko­du. Na przy­kład wy­wo­ła­nie pro­ce­du­ry wy­pi­su­ją­cej na ekra­nie tekst za­pi­sa­ny we frag­men­cie pa­mię­ci mo­że wy­ma­gać za­pi­sa­nia w okreś­lo­nych re­jest­rach pro­ce­so­ra nu­me­ru funk­cji, adre­su ko­mór­ki pa­mię­ci za­wie­ra­ją­cej pierw­szy znak tek­stu oraz sko­ku pod pe­wien sta­ły adres. Takie pow­ta­rza­ją­ce się frag­men­ty ko­du naj­wy­god­niej jest za­pi­sać ja­ko mak­ro­pro­ce­du­ry, auto­ma­tycz­nie prze­kształ­ca­ne przez asem­bler na właś­ci­wy ciąg in­struk­cji asemb­le­ro­wych. Na przy­kład na­stę­pu­ją­cy wiersz pro­gra­mu:

PRINT("To jest wiersz tekstu")

mo­że zo­stać auto­ma­tycz­nie prze­kształ­co­ny na wier­sze:

SYSTEM EQU 1234h
TEKST  DB "To jest wiersz tekstu", 0
       LD A, 10h         ; numer funkcji wypisania tekstu
       LD HL, TEKST      ; adres tekstu przeznaczonego do druku
       CALL SYSTEM       ; wywołanie usługi systemowej nr 10h

Asembler wy­po­sa­żo­ny w ta­ką moż­li­wość na­zy­wa się mak­ro­asemb­le­rem. Pisanie pro­gra­mów z wy­ko­rzys­ta­niem go ma­ło już przy­po­mi­na kla­sycz­ne pro­gra­mo­wa­nie w asem­ble­rze, zbli­ża­jąc się wy­go­dą i po­zio­mem ab­strak­cji do nie­któ­rych mniej skom­pli­ko­wa­nych ję­zy­ków wy­so­kie­go po­zio­mu.

Dzisiaj ma­ło kto pro­gra­mu­je w asem­ble­rze. Ten naj­bliż­szy pro­ce­so­ro­wi ję­zyk wciąż jest naj­lep­szym wy­bo­rem, gdy cho­dzi o za­pro­gra­mo­wa­nie ste­row­ni­ka mik­ro­pro­ce­so­ro­we­go lub na­pi­sa­nie wy­so­ko­wy­daj­ne­go ste­row­ni­ka urzą­dze­nia. O ile jed­nak daw­niej ja­kość ko­du ge­ne­ro­wa­ne­go przez kom­pi­la­to­ry ję­zy­ków wy­so­kie­go po­zio­mu znacz­nie ustę­po­wa­ła pro­gra­mom na­pi­sa­nym bez­po­śred­nio w asem­ble­rze, dzi­siaj częs­to jest od­wrot­nie: pro­gram na­pi­sa­ny na przy­kład w ję­zy­ku C mo­że zo­stać znacz­nie spryt­niej zopty­ma­li­zo­wa­ny (częs­to pod ką­tem reali­zac­ji na kon­kret­nym mo­de­lu mi­kro­pro­ce­so­ra), niż pro­gram na­pi­sa­ny przez śred­nio za­awan­so­wa­ne­go pro­gra­mis­tę asemb­le­ro­we­go. Mimo to, war­to od cza­su do cza­su zerk­nąć na asemb­le­ro­wą po­stać swo­ich pro­gra­mów, bo tyl­ko w ten spo­sób moż­na do­wie­dzieć się, czy wy­myś­lo­ny właś­nie algo­rytm tłu­ma­czo­ny jest przez kom­pi­la­tor na krót­ką sek­wen­cję in­struk­cji, czy wie­lu­set­wier­szo­we mon­strum.


Mniej więcej o to mi chodziło XXD. Ale to i tak Pan przeskoczył dużo.

Dziękuję serdecznie za ten artykuł!
W razie czego proszę zgłaszać kolejne propozycje opisania jakichś konkretnych szczegółów ;) Temat asemblera jest niezwykle szeroki, starałem się opisać jak najwięcej w artykule o sensownej dla początkujących długości i nie przesadzonym stopniu zaawansowania.
Bardzo dobry artykul ,choc jak Pan zaznaczyl nie wyczerpuje tematu.Prosilbam o rozwiniecie w kwestii programownia maszyn sterowanych komputerami,z zaznaczeniem programowania Wyjsc i Wejsc obrabiarki.
Dziekuje jeszcze raz za ciekawy artykul!

Serdecznie pozdrawiam!
Zdzislaw Mathias
Ciekawy artykuł – dzięki ;)