Varúð! Það er nokkuð af enskuslettum þegar ég kann ekki íslensku heitin, eða þegar enskan á betur við.
Þið gætuð spurt: Hvað er eiginlega Assembly ?
Assembly er mjög low-level forritunarmál (vs C/C++ sem eru high-level), þar sem maður er að forrita örgjörvan sjálfan, og býður þess málið þessvegna upp á mesta hraða sem hægt er að ná fram. Þess vegna hentar Assmebly mjög vel í algorithmum sem krefjast mjög margra útreikninga á sekúndu. Sem dæmi má nefna, realtime-raytracers ,3d kernela t.d. opengl og dulkóðun. Assembly er einnig mikið notað í svokölluðum Embedded systems, t.d. PIC örgjörvar, þar sem stærð skiptir öllu máli.
Margir gera sér ekki grein fyrir því að það er einnig hægt að gera Windows, Unix og Mac forrit í hreinu Assembly. Ég ætla þó að fjalla að mestu um Assembly í Windows.
Einsog fyrr segir er Assembly hratt. Og vegna þess að það er low-level, þá gefur það mikið betri stjórn og sveigjanleika yfir tölvunni. Assembly er einnig eina aðferðin til að nýta nýustu tækni sem örgjörvar hafa uppá að bjóða. Þá er ég að tala um SIMD tæknina, SSE, SSE2, SSE3 og SSE4 (sem er enn ekki komin út) og x86-64 (64bit-a intel örrarnir).
Annar kostur er að maður getur sýnt öllum hversu Uber 1337 tölvu-njörður maður er.
Áður en við byrjum þá þarf aðeins að kynna örgjörvan. Ég er einungis að fjalla um x86 örgjörvana en það eru örgjörvarnir sem eru notaðir í PC tölvum en það eru örgjörvar frá Intel, AMD og Cyrix (þeir eru farnir á hausinn, en þó eru en til nokkrir). Þetta á því ekki við Mac og PowerPC örgjörvana.
x86 örgörvarnir innihalda 8 “general purpose registers” þ.e. 8 hólf sem við getum geymt hvað sem er í. En þeir eru samansettir svona:
| EAX | | EBX | 32-bit registerar ------------------------------------------------------ | ax | | bx | 16-bit ------------------------------------------------------ | ah | al | | bh | bl | 8-bit | ECX | | EDX | 32-bit ------------------------------------------------------ | cx | | dx | 16-bit ------------------------------------------------------ | ch | cl | | dh | dl | 8-bit | EDI | | ESI | 32-bit ------------------------------------------------------- | di | | si | | EBP | | ESP | 32-bit ------------------------------------------------------- | bp | | sp | 16-bit
Það sem þessi mynd á að sýna er ax er beintengdur við neðstu 16 bit eax, og er saman settur úr tveimur 8-bita registerum, ah (a-high) og al (a-low). En þetta er afleiðing af því hvernig örgjörvarnir þróuðust (intel bætti alltaf nýjungum ofaná það gamla). Ax stendur tildæmis fyrir a-extended, og eax fyrir extended-ax.
Samsetning registerana olli mér þó nokkrum ruglingi til að byrja með, en ég ætla að reyna að útskýra þetta hérna. Auðveldast er að sýna þetta í hex. Ef að maður setur FFh (11111111 á binary eða 255 í venjulegum tölum) í AH og EEh í AL, þá inniheldur AX FFEEh. Og EAX inniheldur þá aftur á móti 0000FFEEh. Þannig að ef að við setjum 12345678h í eax, hvað innihalda þá ah og al ? nú það er ah = 56h og al = 78þ Þar af leiðandi er ax = 5678h. Það er mikilvægt að átta sig á því hvernig þetta virkar.
Alla þessa registera er hægt að nota í 32-bita umhverfi, en þó er best að notast við 32-bita registerana: eax, ebx, ecx osfrv.
Þótt að þeir heiti “general purpose” þá hafa sumir þeirra sérstök hlutverk, eax er látin geyma gildi sem föll skila, “return value”.
Dæmi: í c/c++ int myfunc(){ return 34; } í assembly myfunc: mov eax, 34 ret
Það eru nokkrir registerar í viðbót, svokallaðir “segment registers” en þá þarf ekki að nota vegna þess að Windows (og linux einnig) meðhöndlar þá fyrir okkur. Þeir eru ein af ástæðum þess að vont orð fer af Assembly, en það er algjör martröð að eiga við þá í DOS. Vegna þess að Windows sér um þá fyrir okkur þá látum við einsog þeir séu ekki til (en þeir eru þarna!). Ef okkur virkilega langar til, þá er hægt að eiga við “segment registerana” en þá er mjög líklegt að það næsta sem við sjáum sé “blue screen of death”. :D
Það er allt saman gott og blessað en þetta gerir ekkert gagn nema við lærum nokkar assembly skipanir.
Hérna eru nokkrar algengustu:
mov (dest), (value)
Move. Þessi skipun afritar (value) og setur í (dest). Hún leyfir að flytja frá registerum í minnið, úr minninu í registera en ekki úr minni í minni. Plássin þurfa einnig að vera jafn stór.
Dæmi: mov eax, 12h Þetta setur gildið 12h í eax mov ebx, eax Þetta afritar gildið í eax, og skrifar í ebx mov ecx, myint Afritar gildið í myint breytunni og setur í ecx mov myint, ecx Afritar gildið í ecx og setur í myint mov myint, myint2 Ólögleg skipun, minni í minni mov cl, edi Ólögleg skipun, 32-bit í 8-bit
add (dest), (value)
Add, samlagning. Þessi skipun afritar gildið í (value) leggur við (dest) og skilar útkomunni í (dest). Allar skipanir hafa sömu skilyrði og mov, nema að annað sé tekið fram
Dæmi: mov eax, 0 ; eax inniheldur núna 0 add eax, 3 ; núna inniheldur eax 3 mov ebx, 5 add eax, ebx ; núna inniheldur eax 8 og ebx innihedur 5
sub (dest), (value)
Subtract, frádráttur. Þessi skipun er allveg einsog add, nema dregur gildið í value frá gildinu í (dest).
Dæmi: mov eax, 10 sub eax, 8 ; núna inniheldur eax 2 sub eax, 2 ; núna inniheldur eax 0 sub eax, 1 ; núna inniheldur eax FFFFFFFFh, eða -1
jmp (location)
Jump. Þessi skipun lætur okkur hoppa á tiltekin stað í kóðanum. Þetta er það sama og goto í c/c++ og öðrum forritunarmálum.
Dæmi: mov eax, 2 mov ebx, 0 jmp mylabel ; hérna hoppum við áfram add eax, 5 ; þessu er sleppt mov ebx 3 ; líka þessu mylabel: ; og lendum hér add eax, 2 ; eax er núna 2+ 2 = 4 add ebx, 1 ; ebx = 0 + 1 = 1
call (function)
Call. Kallar á fall. Setur einnig núverandi staðsetningu efst á stackin, til þess að hægt sé að koma til baka.
Dæmi: myfunc proc mov eax, 0 ret ; ret er það sama og pop ebx ; jmp ebx myfunc endp main call myfunc ; hvað ætli þetta geri ?
Þetta er það sem gerist þegar við köllum á föll í c/c++. T.d. myfunc(); er í raun call myfunc.
push (value)
Push. Afritar gildið í (value) og setur það efst á stackin. Engar áhyggjur stackurinn verður útskýrður eftir smá.
Dæmi: push eax ; setur gildið í eax efst á stackin push 12 ; setur 12 efst. eax gildið fyrir neðan push myint ; setur gildi myint efst, 12 neðan, eax neðst
pop (dest)
Pop. Fjarlægir efsta gildi stacksins og setur í (dest). (dest) má vera register eða pláss í minni.
Dæmi: push eax ; ath. hefur engin áhrif á það sem er í eax. pop ebx ; ebx inniheldur núna sama gildi og er í eax push myint pop myint2 ; gerir það sama og myint2 = myint; í C.
Þá er komið að því að finna út hvað stackurinn er. Minnið er einsog pósthólf, hvert hólf geymir 1 byte og þau eru númeruð frá 0 til FFFFFFFFh, eða 4.294.967.295, allveg sama hve mikið minni raunverulega er í tölvunni. Það er sérstakur staður í minninu sem að kallast stack. Ólíkt venjulegu minni, þá vex stackinn niður á við í minninu. Esp registerinn bendir alltaf á “efsta gildið”, en það
er neðst í minninu. Stackin lýtur í raun svona út:
stackurinn ------------ | | <-- esp bendir hingað, og vex svo niður við hvert push |----------| | | <-- bendir hingað eftir eitt push |----------| | | <-- bendir hingað eftir annað push, og aftur hér beint |----------| fyrir ofan eftir eitt pop. | | ------------ Dæmi: mov esp, 40 ; lætur esp benda á byte númer 40 í minninu. mov eax, 24 push eax ; eax er 4 byte, esp minnkar um 4 push 12h ; pushast venjulega 32-bit, esp = 32 pop ebx ; ebx er núna = 12h , og esp hækkar um 4. pop edx ; edx = eax og esp = 40
Maður verður að fara varlega þegar maður er að vinna með stackin, ef að við hefðum breytt esp eitthvað á milli push og pop. Þá fáum við ekki það sem við viljum út og mjög góðar líkur er á að allt crashi. Einnig verður að passa að á móti hverju pushi kemur pop. Annars stækkar stackinn bara og endar með að skrifa yfir eitthvað mikilvægt í minninu.
Þá erum við tilbúin að gera okkar fyrsta assembly forrit, en fyrst skuluð þið fara á http://website.assemblercode.com/masm32/m32v10r.zip . Unzipið og installið á c: drivið, þetta er um 3,7 mb download en tekur um 40 mb eftir install. Þetta er masm32 pakkin sem ástrali að nafni Hutch viðheldur. Hann inniheldur Microsoft MASM , öll helstu libraryin og fullt af tækjum og hjálpargögnum sem hægt er að nota við að gera windows assembly forrit.
Pakkin er allveg ókeipis og einu skilyrðin eru að það er bannað að selja hann. Microsoft gefur MASM, en hann heitir núna ml.exe, fyrir svokallað “educational usage”, og má þá ekki búa til forrit sem ætlað er að selja, eða fyrir annað stýrikerfi en windows. S.s. bannað er að nota ml.exe til að gera linux forrit samkvæmt EULA-inu. Ég ætla samt að leyfa mér að efast um að Microsoft komi og sparki niður hurðinni hjá þér, ef að þú gerir “hello world!” forrit í ml.exe fyrir linux. En þið hafið verið aðvöruð.
Allt annað í þessum pakka nema ml.exe, þ.e. libraryin, .inc fælarnir osfrv. er gert af random fólki sem hefur gefið vinnuna sína, ekki undir gpl-leyfinu, heldur algerlega frítt, og má því gera hvað sem er við það.
Assembly forrit eru sett upp á eftir farandi hátt:
.386 .model flat, stdcall option casemap:none include \masm32\include\eitthvað-random-inc-file.inc include \masm32\lib\samsvarandi-lib-file.lib .data fullt af breytum sem eru gefin upphafsgildi .data? fullt af breytum sem fá ekki upphafsgildi, t.d. bufferar .const constantarnir okkar .code kóðinn okkra
.386
Þetta er skipun sem segir assemblernum hvernig örgjörva hann á að gera skipanir fyrir, einnig hægt að gera .486, .586 (pentium 1!) og uppí .686, en öruggast er að halda sig bara við .386 eða .486.
.model flat, stdcall
Þetta er önnur skipun fyrir assemblerinn sem segir okkur hvernig minnið er byggt upp. Á windows og linux er bara einn möguleiki en það er flat. En það merkir að við þurfum ekki að hugsa um segment registerana. Stdcall, segir MASM hvernig við gefum föllum breytur. Aðrir möguleikar eru C og pascal. C virkar þannig að breyturnar eru settar á stackin í röð frá hægri til vinstri. Kallandi sér einnig um að hreinsa til á stacknum.
foo(int firsti, int annar, int þriðji, int fjórði) myndi þá vera svona:
push fjórði ; fyrst seinasti push þriðji ; svo næsti push annar ; osfrv. push firsti call foo add esp, 16 ; kallandi lagar til á stacknum, 4 * int = 16 byte.
Pascal aðferðin setur breyturnar á stackin í öfugri röð miðað við C, þ.e frá vinstri til hægri og fallið sér sjálft um að laga stackin til eftir sig. Stdcall er hybrid á milli C og pascal og virkar þannig að breytunum er raðað frá hægri til vinstri og fallið sér um að laga stackin. Windows notast einungis við stdcall nema í einu falli, en það er wsprintf(), þar er notuð C aðferðin.
option casemap:none
Þetta segir MASM að gera greinarmun á há og lágstöfum. Windows krefst þess að þetta sé gert.
.data
.data?
.const
.code
Þetta eru skipanir sem skipt forritinu upp í hluta. Hægt er að gera hvern hluta mörgum sinnum í kóðanum, þar sem að linkerinn sér um að setja alla .data hlutana saman, alla .code hlutana saman osfrv.
Munurinn á .data og .data? er sá að .data? breytunum eru ekki gefin byrjunargildi, og þær taka ekki neitt pláss í .exe skjalinu. T.d. ef að við myndum gera 10.000 byte af breytum í .data þá stækkar .exe skjalið um 10.000 byte, en ekki ef við setjum þær í .data? hlutann.
.code er svo eini hlutinn þar sem assembly skipanirnar koma svo fyrir.
——————————————————————-
Allavegana, kveikið upp qeditor.exe, eða smellið á MASM32 editor shortcutið á skjáborðinu. Þar opnast upp kynning sem gæti verið þess virði að skoða. Við hunsum það og förum í file -> new. Stimplið svo inn:
.386 .model flat, stdcall option casemap:none .data mystring db "hallo heimur!",0 ;zero-terminated string .code start: mov ebx, 2 push ebx mov eax, 0 pop edx jmp mylabel mov eax, 2 ; ath. að það er hoppað yfir þetta mylabel: sub ebx, ecx ; ebx = 0 ret ;hér er eax = 0 end start
svo í project –> run program. Ef að allt er rétt gert, þá gerist ekki neitt og enginn villa.
Málið er að það er heilmikið mál að láta forrit skila einhverju út í windows. T.d. til að gera þetta að venjulegu “halló heimur” forriti þá þurfum við að bæta við heilum helling af libraryum.
; ************************************************************************** ; Einfallt hallo heimur forrit! ; Gerið forritið með að velja project - Console assemble & link ; ************************************************************************** .386 .model flat, stdcall option casemap:none include \masm32\include\windows.inc include \masm32\macros\macros.asm ; ----- prótótýpur fyrir föllin, þessi skjöl eru samskonar og .h skjöl í c/c++ include \masm32\include\masm32.inc include \masm32\include\gdi32.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc ; ------ libraryin sem geyma staðsetningu fallana. includelib \masm32\lib\masm32.lib includelib \masm32\lib\gdi32.lib includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib .data minnstrengur db "hallo heimur!",0 ; ,13,10 það sama og \n .code start: print chr$("Haegt er ad segja 'Hallo heimur!' svona ",13,10) call main mov ebx, input() ; svo forritið lokist ekki strax ret main proc print chr$("og lika svona: ") mov eax, offset minnstrengur ;eax = &minnstrengur print eax ret main endp end start
Print er macro sem að tekur inn pointer að zero-terminated streng og skrifar hann í console. Það sem print gerir er að kalla á StdOut(), sem að er skipun sem er geymd í kernel32.dll í c:\windows\system32\ möppunni. Printf() og cout fela þetta fyrir okkur og er hérna verið að líkja eftir þessari hegðun, en það er bara gert með macros.
Input er allveg eins, en það kallar á StdIn() og skilar út pointer á streng sem sleginn var inn.
chr$ er einkar þægilegt macro en það skilgreinir streng í .data hlutanum og skilar út addressuni, sem leyfir okkur þá að skilgreina strengi í .code hlutanum og innan í öðrum macroum.
Ólíkt c og c++ þá er macrovél MASM mjög öflug og hægt er að gera virkilega flókin og öflug macro til þess að aðstoða við forritun. Margir eru á móti macro notknuninni og segja að þetta sé ekki assembly, en macroin eru eina ástæða þess að fólk verði ekki gjörsamlega geðveikt við að höndla stór forrit sem skrifuð eru í assembly. Að nota og gera sín eigin macro er mjög stór hluti af því að læra assembly.
Að lokum er hér eitt forrit sem að breytir streng úr lágstöfum í hástafi, og breytir ekki öðrum stöfum.
; ************************************************************************** ; Breytir lágstafa streng í hástafi ; Gerið forritið með að velja project - Console assemble & link ; ************************************************************************** .386 .model flat, stdcall option casemap:none include \masm32\include\windows.inc include \masm32\macros\macros.asm ; ----- prótótýpur fyrir föllin, þessi skjöl eru samskonar og .h skjöl í c/c++ include \masm32\include\masm32.inc include \masm32\include\gdi32.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc ; ------ libraryin sem geyma staðsetningu fallana. includelib \masm32\lib\masm32.lib includelib \masm32\lib\gdi32.lib includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib .data minnstrengur db "touppercase # $ 12345 ",0 dummy db 0 ;dummy breyta .code start: print chr$("Nuna breytum vid streng ur lag i hastafi ",13,10) push offset minnstrengur push offset dummy - 1 call toUpper ; toUpper lagar stackin print offset minnstrengur mov ebx, input() ret toUpper proc mov edx, [esp + 4] ; edx = &dummy - 1 mov ebx, [esp + 8] ; ebx = &minnstrengur sub edx, ebx ; edx = lengd strengsins mov ecx, 0 toU: cmp ecx, edx ; ber saman ecx og edx, komin á endan ? je out1 ; je merkir jump if equal, hoppeð ef ecx=edx mov al, [ebx + ecx] ;flytja staf í al .if al < "a" ; ef að al < ascii a þá breytum við ekki mov [ebx + ecx], al ; skila óbreyttu add ecx, 1 ; næsta stak jmp toU .endif continue: ; við erum hér ef að al > ascii a sub eax, 20h ; A = a - 20h osrfv. mov [ebx + ecx], al ; skila nýja gildinu add ecx, 1 ; næsta stak jmp toU out1: ; vegna stdcall, þarf að laga stackin til pop ebp ; geyma efsta stakið pop ebx ; poppa &dummy-1 af pop ebx ; poppa &minnstrengur af push ebp ; skella ebp til baka á stackin ret toUpper endp end start
Forritið hér fyrir ofan sýnir nokkra nýja hluti, t.d. hvernig maður gerir loopur, notar .if og hvernig maður gefur falli breytur. Þessar þrjár línur :
push offset minnstrengur
push offset dummy - 1
call toUpper
Eru það sama og gera svona í c/c++
toUpper( &(dummy) - 1, &minnstrengur);
Inni í fallinu nálgast svo maður breyturnar af stackinum en það er gert hérna:
mov edx, [esp + 4] ; edx = &dummy - 1
mov ebx, [esp + 8] ; ebx = &minnstrengur
Ef að maður gerir mov edx, [esp] þá merkir það að setja breytuna sem esp bendir á í edx, í stað þess að setja töluna esp inniheldur. Ef að bætum við inn í hornklofan, [esp+4], þá förum við þangað sem esp bendir og 4 bætum ofar, og þar er talan sem or notuð. Þetta heitir offset. Skipunin er þá það sama og edx = *(esp + 4 ). Það er mikilvægt að átta sig á því hvernig þetta virkar. Lesið aftur til um stackin, aðeins ofar, til að fá betri mynd af því hvað er að gerast.
Síðan er ný skipun ‘je’,þetta er svo kallað “conditional jump”, sem merkir það að örgjörvinn skoðar sérstakan flag-register, og ef að hann uppfyllir ákveðin skilyrði, þá er hoppað. Maður notar oft ‘cmp’ , en sú skipun ber saman 2 gildi og setur flag registerinn í samræmi við það hvernig fyrsta gildið er miðað við það seinna. Þá er hægt að fylgja því eftir með einhverri af eftirfarandi, ath sumar gera það sama og eru þarna bara til hægðarauka.
je --- Jump if equal cmp a, b a == b jne --- Jump not equal a != b ja --- Jump above a > b jna --- Jump not above a <= b jae --- Jump above or equal a >= b jb --- Jump below a < b jnb --- Jump not below a >= b jbe --- Jump below or equal a <= b
Síðast en ekki síst þá notast forritið við .if, en það virkar einsog í c/c++, og hægt er að nota ==, <=, >= !=, &&, || og einhver fleirri samanburðar tákn. Svona lítur .if klausa út.
.if (skilyrði) blabla .elseif (annað skylrði) mmmhmhmhmh .else bla .endif
Loopuna sem er í forritinu, ecx er látin vera 0 til að byrja með, síðan er hann borinn saman við edx sem inniheldur lengd strengsins. Ef að ecx = edx, þá höfum við lokið við að breyta öllum strengnum. Ecx er svo hækkaður um 1 í hverjum hring sem er endurtekin þar til ecx = edx. Þetta má þá setja upp sem for lykkju, sem væri einhvernvegin svona:
for(ecx = 0 ; ecx < edx ; ecx++).
Jæja, þetta er nú orðið heldur langt, svo að ég ætla ekki að skrifa meira í bili. En ég vona að ég hafi náð að koma einhverju til skila og að einhverjir hafi gagn og gaman að. Ef að þessu er vel tekið get ég skellt því inn hvernig maður gerir svo í linux, og hvernig maður gerir gluggaforrit og jafnvel opengl. Ég gæti freistast til að setja inn kóðan af opengl demoinu mínu, en þar meðhöndla ég allar varpanir sjálfur, og væri því nýtt tutorial um hvernig tölvugrafík virkar.
Endilega kíkið síðan á http://www.masmforum.com og kynnið ykkur Assembly forritun. Það gerir mikið gagn fyrir mann að vita hvernig vélin virkar undir húddinu. Ef að maður hefur það í huga hvernig tölvan vinnur þegar maður skrifar kóða í high-level forritunarmáli, þá gerir maður betri kóða en ella.
Þið verðið að afsaka hversu illa myndirnar og kóðarnir komu út, en það er vegna þess að parser-inn á huga.is á eitthvað erfitt með að gera eðlileg bil og tab-s. Endilega spyrjið ef að það er eitthvað sem að vefst fyrir ykkur og ég skal svara einosg ég get.
Takk fyrir mig,
Budingu