Uvod: Intel-ova 32-bitna arhitektura (uobicajena oznaka je IA-32) je najcesce koriscena arhitektura procesora na danasnjim PC racunarima. Ona obuhvata sve Intel-ove procesore pocev od procesora 386, zatim 486, Pentium I, Pentium Pro, Pentium MMX Pentium II, Pentuim III, Pentium IV, kao i novije modele sa vise jezgara (Pentium Dual Core, Intel Core 2 Duo, itd.). Takodje, AMD ekvivalentni procesori (AMD k5, AMD k6, AMD k6-2, AMD k7, AMD Athlon, AMD Sempron, AMD Duron, itd.) takodje implementiraju IA-32 arhitekturu. Registri: IA-32 arhitektura ima 8 32-bitnih registara: EAX, EBX, ECX, EDX, ESI, EDI, ESP, EBP. Ovi registri su generalno opste namene, mada u praksi neki imaju specijalne uloge. Tako na primer, ESP se uvek koristi kao pokazivac vrha sistemskog steka, EBP je pokazivac tekuceg okvira steka. Ostali registri u nekim situacijama mogu imati specijalne uloge (npr. pri mnozenju i deljenju se uvek implicitno koriste registri EAX i EDX, dok se pri radu sa stringovima za pokazivace uvek koriste registri ESI i EDI). Vrste operanada i adresiranje: -- Registarski operandi -- koristi se vrednost iz registra koji je naveden kao operand. -- Neposredni operandi -- koristi se vrednost konstante koja se navodi kao operand. -- Memorijski operandi -- koristi se vrednost iz memorije na adresi koja se zadaje kao operand. Instrukcija moze imati najvise jedan memorijski operand. Prvi operand instrukcije je uvek odredisni operand, i ne moze biti konstanta. Zadavanje memorijskog operanda: Najjednostavniji nacin zadavanja memorijskog operanda je navodjenjem odgovarajuce labele. U opstem slucaju, adresa se moze zadati na slozeniji nacin, a opsti oblik je dat formulom: bazna_adresa + faktor * indeks + pomeraj gde je bazna adresa uvek sadzana u nekom registru, faktor je konstanta 1, 2, 4, ili 8 (ako se ne navede podrazumeva se 1), indeks je takodje uvek u nekom registru, a pomeraj je konstanta. Ovaj izraz se uvek navodi u zagradama [] prilikom navodjenja u asembleru. Svaka od komponenti se moze izostaviti. Tako, ako se navede samo bazna adresa, to se zove "bazno adresiranje" (u registru imamo pokazivac koji "dereferenciramo" i dobijamo podatak sa te adrese). Ako se navede bazna adresa i indeks (pomnozen nekim faktorom) to se zove "indeksno adresiranje". Ono je zgodno prilikom rada sa nizovima: u bazni registar se smesti adresa pocetka niza, a u indeksni registar se smesti tekuci indeks (koji se azurira u svakoj iteraciji). Za faktor se uzima velicina elementa niza. Primeri: mov eax, [esi] ## Premesta u eax vrednost sa adrese na koju pokazuje ## esi (bazno adresiranje) add ecx, [ebx + 4] ## Dodaje se na ecx vrednost sa adrese ebx + 4 (bazna ## adresa + pomeraj) mov eax, [ebx + 4 * ecx] ## U eax se kopira vrednost sa adrese ebx + 4 * ecx ## (indeksno adresiranje, ## pri cemu je bazna adresa (adresa pocetka niza) ## u registru ebx, a indeks se nalazi u registru ## ecx. Koristi se faktor 4, tj. elementi niza su ## velicine 4 bajta, npr. niz int-ova). sub [edi + 4 * ecx + 4], eax ## Najslozeniji oblik, sadrzi sve komponente. ## Pomeraj je 4 bajta, bazna adresa se nalazi ## u edi, indeks u ecx, a faktor skaliranja je 4. ## Vrednost na ovoj lokaciji se umanjuje za ## vrednost registra eax. cmp edi, [esi + ecx] ## Indeksno adresiranje -- bazni registar je u ## registru esi, indeks u ecx, faktor je podrazumevano ## 1, nema pomeraja. Adresa se prosto izracunava kao ## zbir bazne adrese i indeksa. Uporedjuje se registar ## edi sa podatkom na adresi esi + ecx. U slucaju da instrukcija koja sadrzi memorijski operand nema ni jedan registarski operand, tada se mora eksplicitno spcificirati sirina memorijskog operanda. Ovo se radi navodjenjem jednog od prefiksa: -- byte ptr -- za jednobajtni podatak -- word ptr -- za dvobajtni podatak -- dword ptr -- za cetvorobajtni podatak (mi cemo pretezno koristiti ovaj prefiks) Na primer: inc dword ptr [esi] ## uvecava cetvorobajtnu vrednost na adresi na koju ## pokazuje esi push dword ptr L ## potiskuje na stek cetvorobajtni podatak sa adrese ## koja je pridruzena labeli L. Ovo nije neophodno ako registarski operand postoji u instrukciji, zato sto onda njegova sirina implicitno odredjuje sirinu operanada koji se koriste. Instrukcija transfera: -- MOV op1, op2 -- premestanje (op1 = op2) -- LEA op1, op2 -- ucitavanje adrese, pri cemu je drugi operand uvek memorijski operand. Aritmeticko logicke instrukcije: -- ADD op1, op2 -- sabiranje (op1 = op1 + op2) -- SUB op1, op2 -- oduzimanje (op1 = op1 - op2) -- AND op1, op2 -- bitovska logicka konjukcija (op1 = op1 & op2) -- OR op1, op2 -- bitovska logicka disjunkcija (op1 = op1 | op2) -- XOR op1, op2 -- bitovska logicka ekskluzivna disjunkcija (op1 = op1 ^ op2) -- XCHG op1, op2 -- razmenjuje vrednosti operanada (swap(op1, op2)) -- NOT op -- bitovska negacija (op = ~op) -- MUL op -- mnozenje neoznacenih celih brojeva (edx:eax = eax * op) -- IMUL op -- mnozenje oznacenih celih brojeva (edx:eax = eax * op) -- DIV op -- deljenje neoznacenih celih brojeva (eax = edx:eax / op, edx = edx:eax % op) -- IDIV op -- deljenje oznacenih celih brojeva (eax = edx:eax / op, edx = edx:eax % op) -- NEG op -- promena znaka (op = -op) -- INC op -- uvecanje (op = op + 1) -- DEC op -- umanjenje (op = op - 1) -- SHL op1, op2 -- shift-ovanje ulevo (op1 = op1 << op2). op2 je konstanta. -- SHR op1, op2 -- shift-ovanje udesno (logicko) (op1 = op1 >> op2). op2 je konstanta. -- SAR op1, op2 -- shift-ovanje udesno (aritmeticko) (op1 = op1 >> op2). op2 je konstanta. Oznaka edx:eax znaci 64-bitni ceo broj ciji su visi bitovi u edx a nizi u eax. Instrukcije poredjenja: -- CMP -- uporedjivanje (oduzimanje bez upisivanja rezultata) -- TEST -- testiranje bitova (bitovska konjukcija bez upisivanja rezultata) Instrukcije za rad sa stekom: -- PUSH op -- postavljanje na stek (sub esp, 4 ; mov [esp], op) -- POP op -- skidanje sa steka (mov op, [esp] ; add esp, 4) -- PUSHA -- postavlja sve registre opste namene na stek -- POPA -- skida sa steka 8 vrednosti i smesta ih u registre opste namene) Zbog efikasnosti, na stek uvek treba postavljati 32-bitne vrednosti. Instrukcije kontrole toka: -- JMP op -- bezuslovni skok na adresu op (memorijski operand) -- CALL op -- bezuslovni skok uz pamcenje povratne adrese na steku. -- RET -- skida sa steka adresu i skace na tu adresu. -- JZ op -- skace ako je rezultat prethodne instrukcije nula. -- JE op -- skace ako je rezultat prethodnog poredjenja jednakost (ekvivalentno sa JZ) -- JNZ op -- skace ako je rezultat prethodne operacije razlicit od nule -- JNE op -- skace ako je rezultat prethodnog poredjenja razlicitost (ekvivalentno sa JNZ) -- JA op -- skace ako je rezultat prethodnog poredjenja vece (neoznaceni brojevi) -- JB op -- skace ako je rezultat prethodnog poredjenja manje (neoznaceni brojevi) -- JAE op -- skace ako je rezultat prethodnog poredjenja vece ili jednako (neoznaceni brojevi) -- JBE op -- skace ako je rezultat prethodnog poredjenja manje ili jednako (neoznaceni brojevi) -- JG op -- skace ako je rezultat prethodnog poredjenja vece (oznaceni brojevi) -- JL op -- skace ako je rezultat prethodnog poredjenja manje (oznaceni brojevi) -- JGE op -- skace ako je rezultat prethodnog poredjenja vece ili jednako (oznaceni brojevi) -- JLE op -- skace ako je rezultat prethodnog poredjenja manje ili jednako (oznaceni brojevi) Slicno, postoje i negacije gornjih instrukcija uslovnog skoka: JNA, JNB, JNAE, JNBE, JNG, JNL, JNGE, JNLE. Instrukcije koje se koriste u prologu i epilogu funkcije: -- ENTER N, 0 je ekvivalentno sa: push ebp mov ebp, esp sub esp, N -- LEAVE je ekvivalentno sa: mov esp, ebp pop ebp Matematicki koprocesor: ----------------------- Postoji 8 registara od po 80 bitova koji formiraju stek na koji se mogu postavljati i sa koga se mogu skidati podaci. U svakom trenutku, podatak na vrhu steka se moze referisati sa st(0) (ili st), a ostali podaci se mogu dalje referisati sa st(1), st(2), ... U slucaju punog steka, poslednji podatak (najdublje na steku) se referise sa st(7). Medjutim, programer je duzan da vodi racuna koliko podataka ima na steku u kom trenutku i gde se koji nalazi. Svi podaci na FPU steku su realni brojevi u dvostrukom prosirenom formatu (long double). Operandi FPU instrukcija: ------------------------- -- registarski -- samo vrednosti sa FPU steka se mogu navoditi. Registarski operandi mogu biti eksplicitni ili implicitni (neke instrukcije podrazumevaju da im je st(0) operand, ili st(0) i st(1), u slucaju dva operanda). -- memorijski -- mora se navesti sirina podatka u memoriji. Prefix dword ptr znaci float (ili int), prefix qword ptr znaci double, a prefix tbyte ptr znaci long double. Instrukcije FPU koprocesora: ---------------------------- FLD mem_op -- ucitava realan broj iz memorije (uz odgovarajucu konverziju u long double format). FLD ST(i) -- ucitava na stek vrednost koja se vec nalazi na steku u datom registru. FILD mem_op -- ucitava na stek celobrojnu vrednost iz memorije (uz odgovarajucu konverziju u long double). FLDZ -- ucitava 0 na stek. FLD1 -- ucitava 1 na stek. FLDL2T -- ucitava log_2(10) FLDL2E -- ucitava log_2(e) FLDLG2 -- ucitava log_10(2) FLDLN2 -- ucitava log_e(2) FLDPI -- ucitava Pi FST mem_op -- smesta vrednost sa vrha steka u memoriju (uz odgovarajucu konverziju iz long double u zeljeni format realnog broja). FST ST(i) -- smesta vrednost sa vrha steka u dati registar na steku. FIST mem_op -- smesta vrednost sa vrha steka u memoriju, konvertujuci ga u 32-bitni ceo broj. FSTP mem_op -- isto kao FST, samo nekon toga skida vrednost sa vrha steka. FSTP ST(i) -- isto kao FST, samo nekon toga skida vrednost sa vrha steka. FISTP mem_op -- isto kao FIST, samo skida vrednost sa steka nakon toga. FADD mem_op -- dodaje na vrednost na vrhu steka realnu vrednost iz memorije. FIADD mem_op -- dodaje na vrednost na vrhu steka celobrojnu vrednost iz memorije. FADD st(0), st(i) -- dodaje na st(0) vrednost st(i) FADD st(i), st(0) -- dodaje na st(i) vrednost st(0) FADDP st(i), st(0) -- dodaje na st(i) vrednost st(0) a zatim skida st(0) sa steka. FADDP -- ekvivalentno sa FADDP st(1), st(0) Analogno FADD familiji instrukcija postoji FMUL familija instrukcija. FSUB mem_op -- oduzima od vrednosti na vrhu steka realnu vrednost iz memorije. FISUB mem_op -- oduzima od vrednosti na vrhu steka celobrojnu vrednost iz memorije. FSUB st(0), st(i) -- oduzima st(1) od st(0) i razliku smesta u st(0) FSUB st(i), st(0) -- oduzima st(0) od st(i) i razliku smesta u st(i) FSUBP st(i), st(0) -- isto kao prethodno, samo sto skida st(0) sa steka nakon oduzimanja. FSUBP -- ekvivalentno sa FSUBP st(1), st(0) Za svaku od gornjih instrukcija postoji i varijanta sa R sufiksom (FSUBR, FISUBR, FSUBRP), koje rade isto, samo su umanjenik i umanjilac zamenili mesta. Npr FSUBR st(i), st(0) oduzima st(i) od st(0) i razliku smesta u st(i) (dakle, na mesto umanjioca). Analogno FSUB familiji, postoji FDIV familija instrukcija. FABS -- zamenjuje ST(0) njegovom apsolutno vrednoscu FCHS -- zamenjuje ST(0) sa -ST(0) FXCH st(i) -- zamenjuje ST(0) i ST(i) FXCH -- zamenjuje ST(0) i ST(1) FSIN -- zamenjuje ST(0) sa sin(ST(0)) FCOS -- zamenjuje ST(0) sa cos(ST(0)) FSINCOS -- zamenjuje ST(0) sa sin(ST(0)) a zatim dodaje cos(ST(0)) na stek. FPTAN -- zamenjuje ST(0) sa tan(ST(0)), a zatim dodaje vrednost 1.0 na stek. Vrednosti za sve trigonometrijske instrukcije su u radijanima. FPATAN -- zamenjuje ST(1) sa atan(ST(1)/ST(0)) a zatim skida ST(0) sa steka. Vrednost koja se vraca je iz intervala [-Pi, Pi], u zavisnosti od kvadranta kome pripada tacka (ST(0), ST(1)). F2XM1 -- zamenjuje vrednost ST(0) sa 2^(ST(0)) - 1, gde ST(0) mora biti u intervalu [-1, 1]. FRNDINT -- zamenjuje ST(0) sa celim delom vrednosti ST(0) FSCALE -- zamenjuje ST(0) sa ST(0)*2^(rnd(ST(1))), gde je rnd(ST(1)) ceo deo vrednosti ST(1). FSQRT -- zamenjuje ST(0) sa kvadratnim korenom od ST(0) FYL2X -- zamenjuje ST(1) sa ST(1)*log_2(ST(0)), a zatim skida ST(0) sa steka. Instrukcije za poredjenje realnih brojeva: FCOMI ST(0), ST(i) -- poredi ST(0) sa ST(i) i postavlja flagove u EFLAGS registru. FCOMIP ST(0), ST(i) -- isto kao prethodna, samo skida ST(0) sa steka nakon poredjenja. Konceptualno, poredjenje se vrsi tako sto se izracuna razlika ST(0) - ST(i), a zatim se flagovi CF i ZF postave isto kao da su oduzimana dva neoznacena cela broja. Ovo znaci da nakon instrukcije poredjenja treba da usledi neka od instrukcija JE, JA, JB, JAE, JBE, ili negacije tih instrukcija. Odbacivanje vrednosti sa steka se obavlja parom instrukcija: FFREE ST(0) FINCSTP Konvencije o pozivanju C-funkcija: -- registri ESI, EDI i EBX pripadaju pozivajucoj funkciji. Pozvana funkcija mora sacuvati na steku njihove vrednosti, ako ih koristi. Pozivajuca funkcija moze racunati na vrednosti ovih registara, nezavisno od poziva drugih funkcija. -- registri EAX, ECX i EDX pripadaju pozvanoj funkciji. Pozvana funkcija ne mora cuvati njihove vrednosti. Pozivajuca funkcija ne sme racunati da se njihove vrednosti nece promeniti prilikom poziva drugih funkcija. -- argumenti se prenose preko steka, tako sto pozivajuca funkcija na stek postavlja argumente u obrnutom poretku (sa desna na levo). Pozivajuca funkcija je takodje odgovorna za njihovo uklanjanje sa steka nakon povratka iz pozvane funkcije. Argumenti strukturnih tipova se prenose preko steka kao i svi drugi, pri cemu treba voditi racuna o velicini objekata strukturnih tipova. -- FPU stek na pocetku svake funkcije mora biti prazan (to mora obezbediti pozivajuca funkcija). Nakon zavrsetka, svaka funkcija mora takodje ostaviti prazan FPU stek (osim ako vraca realnu vrednost, videti dole). -- Povratna vrednost se ostavlja u registru EAX ukoliko je celobrojnog ili pokazivackog tipa. U slucaju da funkcija vraca realan broj, tada se ova vrednost ostavlja u ST(0) (kao jedina vrednost na FPU steku). Povratna vrednost strukturnog tipa se vraca preko adrese koju pozivajuca funkcija prenosi kao prvi "skriveni" argument. Pozvana funkcija je duzna da tu vrednost ukloni sa steka i da je ostavi u EAX registru. -- Podaci tipa long double su velicine 12 bajtova, iako se samo nizih 10 (80 bitova) efektivno koriste u vrednosti. Dodatna dva bajta su dodata zbog poravananja (da bi velicina podataka bila deljiva sa 4). O ovome treba voditi racuna kada se long double podaci postavljaju na stek, ili kada se radi sa nizom long double-ova. O GNU asembleru (as program): -- Komentari pocinju znakom #. Sve do kraja linije se ignorise. -- Direktive asemblera pocinju tackom. Neke cesto koriscene direktive: .intel_syntax noprefix -- oznacava da se koristi Intel-ova sintaksa. .text -- oznacava pocetak zone sa tekstom. .data -- oznacava pocetak zone sa statickim podacima. .int n -- emituje u objektni fajl ceo broj n .asciz str -- emituje u objektni fajl bajtove karaktera iz kojih se sastoji string str, kao i terminirajucu nulu. .global label -- oznacava labelu kao globalnu, cime se omogucava linker-u da poveze -- Ostale linije koje nisu direktive se smatraju instrukcijama. Instrukcije se sastoje iz mnemonika i zarezima razdvojenih operanada. -- Svaka linija moze pocinjati labelom -- identifikatorom za kojim sledi dvotacka. Svakoj labeli se u fazi prevodjenja dodeljuje adresa instrukcije ili podatka koji sledi neposredno nakon labele u kodu. Labele se kasnije mogu koristiti kao memorijski operandi (npr. add ecx, L, ili jmp L, gde je L neka labela). -- Prazne linije se ignorisu. Prevodjenje: Izvorni kod sa asemblerskim funkcijama se prevodi na sledeci nacin: as -o asm.o asm.s Gde se 'asm' treba zameniti nazivom konkretnog fajla. Uobicajeno je da se fajlovima sa asemblerskim kodom daje ekstenzija .s Izvorni kod sa C funkcijama se prevodi na sledeci nacin: gcc -c -o c_func.o c_func.c gde 'c_func' treba zameniti odgovarajucim imenom fajla. Opcija -c govori prevodiocu da ne pokusava link-ovanje, vec da se zaustavi na kreiranju odgovarajuceg objektnog fajla. Povezivanje (link-ovanje) se vrsi na sledeci nacin: gcc -o test c_func.o asm.o gde opet treba uzeti odgovarajuca imena fajlova. Izvrsni fajl ce se zvati 'test' (i ovo se moze birati po zelji). Za sve nejasnoce, obratiti se na mail: milan [at] matf.bg.ac.rs