Documente Academic
Documente Profesional
Documente Cultură
Limbajul de asamblare este o notaie inteligibil din punctul de vedere al oamenilor (humanreadable) pentru limbajul main specific unei anumite arhitecturi. Limbajul main const n iruri de bii, interpretai ca instruciuni, adrese, valori, etc. Acest limbaj devine inteligibil prin nlocuirea valorilor binare prin simboluri numite mnemonice. Transformarea limbajului de asamblare n limbaj main se face de ctre un asamblor, iar invers de ctre un dezasamblor. Spre deosebire de limbajele de nivel mediu sau nalt (C, C++...), n general exist o coresponden 1 la 1 ntre instruciunile n limbaj de asamblare i instruciunile n limbaj main. Anumite asambloare pot pune la dispoziie pseudoinstruciuni, fieare dintre acestea corespunznd unui anumit set de instruciuni n limbaj main. Fiecare arhitectur de procesor are propriul limbaj main, deci i limbajul de asamblare difer. Exist diferene din punctul de vedere al tipului i numrului de operaii suportate, al numrului i dimensiunilor regitrilor, precum i al reprezentrilor. Regitrii procesoarelor Intel Un registru este o bucic de memorie (n acest context 16 sau 32 bii) foarte rapid, care ine de procesor, n care sunt ncrcate din memoria principal datele unui program pentru procesare. Majoritatea procesoarelor (dar nu toate) funcioneaz pe principiul mutrii datelor din memorie n regitri, procesarea lor i mutarea rezultatelor napoi n memorie. # Regitri generali
Regitrii generali pot fi folosii att pentru a reine date (toi), ct i pentru formarea adreselor de date (numai cei pe 32 bii). Excepie fac IP/EIP, care nu apar n mod explicit n nici o instruciune; rolul lor este exclusiv n formarea adresei urmtoarei instruciuni de executat. # Registrul de indicatori
Civa indicatori:
Carry (bitul 0): este 1 dac avem "transport"; de exemplu: o la adunare, rezultatul iese din registru (al 17, 33-lea bit) o la scdere, este nevoie de mprumutarea unui bit Zero (bitul 6): 1 dac rezultatul ultimei operaii este 0, 0 altfel Sign (bitul 7): setat conform semnului rezultatului ultimei operaii (+ ~ 0, - ~ 1) Overflow (bitul 11): 1 dac n urma ultimei operaii s-a schimbat bitul cel mai semnificativ (bitul de semn: 1 - negativ, 0 - pozitiv), 0 altfel
# Regitrii segment
Constant numeric. Exemplu: [100] Valoarea unui registru general pe 32 bii. Exemplu: [EAX] (se poate scrie i cu litere mici) Suma dintre valoarea unui registru general pe 32 bii i o constant. Exemplu: [EBX+5] Suma a doi regitri generali pe 32 bii. Exemplu: [ECX+ESI] Combinaia celor 2 variante anterioare: suma a 2 regitri i a unei constante. Exemplu: [EDX+EBP+14]
2
Suma a 2 regitri, dintre care unul nmulit cu 2, 4, sau 8, la care se poate aduna o constant. Exemple: [EAX+EDI*2], [ECX+EDX*4+5]
Adresa urmtoarei instruciuni de executat este reinut de regitrii CS (partea de segment) i EIP (partea de offset). Actualizarea valorii acestor regitri este realizat automat de procesor, la trecerea de la o instruciune la alta; programatorul nu-i poate modifica n mod direct, neexistnd instruciuni n acest sens (indirect se poate prin salt). Pentru adresele de date se poate folosi oricare registru segment, dar cel mai adesea DS. n practic, att n Windows, ct i n Linux, toi regitrii de segment pot fi folosii pentru a accesa ntreaga memorie aflat la dispoziia programului (asta nu nseamn toat memoria sistemului!), fr nici o deosebire ntre ei. n plus, partea de segment poate efectiv s nu fie precizat n instruciune, fiind considerat n mod implicit unul dintre regitri, nu conteaz care. Toi regitrii generali pe 32 bii pot fi folosii la adresare, fr restricii i fr diferene ntre ei, n combinaiile precizate mai sus.
registru, registru. Exemple: mov eax, ebx;, mov al, bh; registru, adresa de memorie. Exemplu: mov bl, [eax]; adresa de memorie, registru. Exemplu: mov [esi], esx; registru, constant numeric. Exemplu: mov ah, 0; memorie, constant numeric. Exemplu: mov [eax], 3;
Cauza: tipul int descrie numere ntregi pe 32 bii; registrul ax are 16 bii; i nu ncape n ax. Operanzii trebuie s aib aceeai dimensiune! Soluie:
#include <stdio.h> void main(){ int i = 0; _asm{ mov ax, word ptr i; } }
registru, registru. Exemple: add eax, ebx;, add al, bh; registru, adresa de memorie. Exemplu: add si, [...]; adresa de memorie, registru. Exemplu: add [...], ebp; registru, constant numeric. Exemplu: add dl, 3; memorie, constant numeric. Exemplu: add [eax], 3; add byte ptr [eax], 3;
Ca i la atribuire, nu este permis ca ambii operanzi s fie adrese de memorie, iar operanzii trebuie s aib aceeai dimensiune! Un prim exemplu:
#include <stdio.h> void main(){ int a=10; _asm { add a,5 } printf("%d\n",a); }
Add modific indicatorii! n cazul n care adunarea are ca efect modificarea bitului celui mai semnificativ (bitul de semn) din destinaie, se seteaz automat flag-ul Overflow. Dac programatorul lucreaz pe ntregi fr semn, acest fapt nu prezint interes. Dac programatorul lucreaz pe ntregi cu semn, este responsabilitatea lui s verifice dac s-a produs depire. De asemenea, dac rezultatul nu ncape n destinatie, se seteaz flag-ul Carry.
#include <stdio.h> void main(){ _asm { mov eax,0xFFFFFFFF; add eax,2; // rezultatul este 0x100000001; necesita 33 biti. // setare carry mov eax,0; mov ax, 0xFFFF; add ax, 2; // doar ax se modifica! // desi rezultatul este 0x10001, // al 17-lea bit din eax nu se modifica. // se seteaza carry } printf("%d\n",c); }
Adunarea cu transport: adc (add with carry) Sintaxa: adc op1, op2 Efect: op1 = op1 + op2 + carry Utilitate: Efectuarea de adunri pe numere ntregi a cror reprezentare necesit mai mult de 32 bii. Exemplu:
#include <stdio.h> void main() { /* Reprezentam un nr pe 64 biti folosind un vector de 2 intregi*/ unsigned a[2]={0x80000001, 1},b[2]={0x80000001, 1},c[2]={0,0}; /* Nr reprezentat de a este a[1] * 2^32 + a[0] (adica vom concatena reprezentarile): 0x180000001 */ _asm { // vom aduna "pe bucati" incepand cu partea cea mai nesemnificativa // (asa cum in baza 10, adunarea nr de mai multe cifre incepe cu unitatile) mov eax,a add eax,b // punem in c[0] rezultatul partial mov c, eax; // e posibil sa avem transport! // vom folosi deci adc pentru partea cea mai semnificativa, pentru a lua in // considerare si transportul mov eax, a[4] adc eax, b[4] mov c[4], eax // a[4], b[4], c[4] in loc de a[1], ... pentru ca trebuie sa sarim peste // 4 octeti pentru a accesa urmatorul numar intreg } printf("%x%08x\n",c[1],c[0]); }
# Scderea Instruciunea sub Sintaxa: sub op1, op2 Efect: op1 = op1 - op2 Restriciile asupra operanzilor sunt aceleai ca la adunare.
#include <stdio.h> void main() { int a=10,b=14; _asm { mov eax,b sub a,eax } printf("%d\n",a); }
Scderea unui numr mai mare din unul mai mic va avea ca efect setarea flag-ului Carry, cu semnificaia, n acest context, de mprumut. Scderea cu mprumut: sbb (subtract, borrow) Sintaxa: sbb op1, op2 Efect: op1 = op1 - op2 - carry Utilitate: Efectuarea de scderi pe numere ntregi a cror reprezentare necesit mai mult de 32 bii. A se adapta exemplul de mai sus pentru scdere pe numere de 64 bii.
10110111 * 11010010 ---------------0 10110111 10110111 10110111 10110111 ---------------1001011000011110 Deci dimensiunea destinaiei implicite trebuie s fie dublul dimensiunii operanzilor. Tabelul de mai jos prezint operanzii implicii i destinaiile implicite pentru diversele dimensiuni ale operandului implicit:
7
Dimensiune operand explicit Operand implicit Destinaie implicit Al ax 1 octet Ax (dx, ax) 2 octei Eax (edx, eax) 4 octei Operandul implicit nu poate fi constant numeric:
mul 10; mul byte ptr 2; //EROARE //EROARE
El trebuie s fie ori un registru de date (al, ebx, ...), ori o adres de memorie. Dac adresa de memorie nu este dat prin numele simbolic (de exemplu, numele unei variabile declarate n programul C ce ncapsuleaz codul asm), ci prin modurile de adresare discutate anterior, trebuie precizat dimensiunea n octei a zonei de memorie ce conine respectivul operand explicit, pentru a se putea stabili operandul implicit i destinaia implicit. De exemplu:
mul byte ptr [ebp - 4]: operandul explicit se afl n memorie la adresa [ebp - 4] i are dimensiunea de 1 octet (valoarea efectiv este cea din octetul de la aceasta adresa) mul word ptr [ebp - 4]: operandul explicit se afl la adresa [ebp - 4] i are dimensiunea de 2 octei (valoarea efectiv este cea compus din primii 2 octei de la aceasta adresa) mul dword ptr [ebp - 4]: operandul explicit se afl la adresa [ebp - 4] i are dimensiunea de 4 octei (valoarea efectiv este cea compus din primii 4 octei de la aceasta adresa)
Cteva exemple:
linia de cod mul bl va avea urmatorul efect: o se calculeaz rezultatul nmulirii dintre al i bl (bl are dimensiunea de 1 octet, deci operandul implicit la nmulire este al) o acest rezultat se pune n ax(care este destinaia implicit pentru nmuliri cu operandul explicit op de 1 octet Mai concret, mul bl <=> ax = al * bl
linia de cod mul bx va avea urmatorul efect: o se calculeaz rezultatul nmulirii dintre ax i bx (bx are dimensiunea de 2 octei, deci operandul implicit la nmulire este ax) o acest rezultat se pune n (dx,ax) astfel: primii 2 octei (cei mai semnificativi) din rezultat vor fi plasai n dx, iar ultimii 2 octei (cei mai puin semnificativi) n ax 16 o rezultatul nmulirii este, de fapt, dx*2 + ax Desigur, acest rezultat pe 4 octei ar fi ncput n eax. Se folosesc ns regitrii (dx,ax) pentru compatibilitatea cu mainile mai vechi, pe 16 bii.
#include <stdio.h> void main(){ _asm { mov ax, 60000; //in baza 16: EA60 mov bx, 60000; //in baza 16: EA60 mul bx; //rezultatul inmultirii este 3600000000; //in baza 16: D693A400, plasat astfel: //in registrul dx - partea cea mai semnificativa: D693 //in registrul ax - partea cea mai putin semnificativa: A400 }}
linia de cod mul ebx va avea urmatorul efect: o se calculeaz rezultatul nmulirii dintre eax i ebx (ebx are dimensiunea de 4 octei, deci operandul implicit la nmulire este eax) o acest rezultat se pune n (edx,eax) astfel: primii 4 octei (cei mai semnificativi) din rezultat vor fi plasai n edx, iar ultimii 4 octei (cei mai puin semnificativi) n eax 32 o rezultatul nmulirii este, de fapt, edx*2 + eax Exemplu de cod:
#include <stdio.h> void main(){ _asm { mov eax, 60000; //in baza 16: 0000EA60 mov ebx, 60000; //in baza 16: 0000EA60 mul ebx; //rezultatul inmultirii este 3600000000; //in baza 16: D693A400, plasat astfel: //in edx - partea cea mai semnificativa: 00000000 //in eax - partea cea mai putin semnificativa: D693A400 } }
Instruciunea imul nmulire de numere cu semn (Integer MULtiply) Sintaxa: imul op Dup cum am precizat mai sus, la instruciunea mul operanzii sunt considerai numere fr semn. Aceasta nseamn c se lucreaz cu numere pozitive, iar bitul cel mai semnificativ din reprezentare este prima cifr a reprezentrii binare, nu bitul de semn. Pentru operaii de nmulire care implic numere negative exist instruciunea imul (este nevoie de dou instruciuni distincte deoarece, spre deosebire de adunare sau scdere, agoritmul de nmulire este diferit la numerele cu semn). Ceea s-a prezentat la mul este valabil i pentru imul. Diferena este aceea c numerele care au bitul cel mai semnificativ 1 sunt considerate numere negative, reprezentate n complement fa de 2. Exemplu:
void main(){ _asm { mov ax, 0xFFFF; mov bx, 0xFFFE; mul bx; //rezultatul inmultirii numerelor FARA SEMN: //65535 * 65534 = 4294770690; //in baza 16: FFFD0002, plasat astfel: //in dx - partea cea mai semnificativa: FFFD //in ax - partea cea mai putin semnificativa: 0002 mov ax, 0xFFFF; mov bx, 0xFFFE; imul bx; //rezultatul inmultirii numerelor CU SEMN: //-1 * -2 = 2; //in baza 16: 00000002, plasat astfel: //in dx - partea cea mai semnificativa: 0000 //in ax - partea cea mai putin semnificativa: 0002 } }
# mprirea Instruciunea div mprire de numere fr semn Sintaxa: div op Efect: cat_implicit, rest_implicit = deimpartit_implicit : op Instruciunea div corespunde operaiei de mprire cu rest. Ca i la nmulire, operandul implicit (dempritul) i destinaia implicit (ctul i restul) depind de dimensiunea operandului explicit op (mpritorul): Dimensiune operand explicit Demprit Ct Rest (mpritor) ax al ah 1 octet (dx, ax) ax dx 2 octei (edx, eax) eax edx 4 octei (A se observa similaritatea cu instruciunea de nmulire.) n toate cazurile, ctul este depus n jumtatea cea mai puin semnificativ a dempritului, iar restul n cea mai semnificativ. Acest mod de plasare a rezultatelor permite reluarea operaiei de nmprire nn bucl, dac este cazul, fr a mai fi nevoie de operaii de transfer suplimentare. Analog cu nmulirea, operandul explicit (mpritorul) poate fi un registru sau o locaie de memorie, dar nu o constant:
div div div div div div div ebx cx dh byte ptr [...] word ptr [...] dword ptr [...] byte ptr 10 // eroare
Programul va semnala o eroare la execuie (integer divide by zero) i va fi terminat forat. Efectund urmtoarea modificare:
#include <stdio.h> void main(){ _asm { mov eax, 1 mov edx, 1
10
//1 in loc de 0
se obine o alt eroare la execuie: integer overflow. Motivul este acela c se ncearc mprirea numrului 0x100000001 la 1, ctul fiind 0x100000001. Acest ct trebuie depus n registrul eax, ns valoarea lui depete valoarea maxim ce poate fi pus n acest registru, adic 0xFFFFFFFF. Mai concret, n cazul n care ctul nu ncape n registrul corespunztor, se obine eroare: (edx*232 + eax) / ebx 232 <=> edx*232 + eax ebx*232 <=> eax (ebx - edx) * 232 <=> ebx edx Cu alte cuvinte, vom obine cu siguran eroare dac mpritorul este mai mic sau egal cu partea cea mai semnificativ a dempritului. Pentru a evita terminarea forat a programului, trebuie verificat aceast situaie nainte de efectuarea mpririi. Instruciunea idiv mprire de numere cu semn Sintaxa: idiv op idiv funcioneaz ca i div, cu diferena c numerele care au bitul cel mai semnificativ 1 sunt considerate numere negative, reprezentate n complement fa de 2. Exemple de cod:
#include <stdio.h> void main(){ _asm { mov ax, 35; mov dx, 0; //nu trebuie uitata initializarea lui (e)dx! //(in general, initializarea partii celei mai // semnificative a deimpartitului) mov bx, 7; div bx; //rezultat: ax devine 5, adica 0x0005 (catul) // dx devine 0 (restul) mov ax, 35; mov dx, 0; mov bx,7 idiv bx // acelasi efect, deoarece numerele sunt pozitive mov mov mov div ax, -35; //in hexa (complement fata de 2): FFDD dx, 0; bx,7 bx //deimpartitul este (dx, ax), adica 0000FFDD //in baza 10: 65501 //rezultat: ax devine 0x332C, adica 13100 (catul) // dx devine 0x0001 (restul)
mov ax, -35; //in hexa (complement fata de 2): FFDD mov dx, 0; mov bx,7
11
idiv bx
//deimpartitul este (dx, ax), adica 0000FFDD //este un mumar pozitiv, adica, in baza 10, 65501 //rezultat: ax devine 0x332C, adica 13100 (catul) // dx devine 0x0001 (restul) //(efectul este acelasi ca la secventa de mai sus)
mov ax, -35; //in hexa (complement fata de 2): FFDD mov dx, -1; //in hexa (complement fata de 2): FFFF mov bx,7 idiv bx //deimpartitul este (dx, ax), adica FFFFFFDD // - numar negativ, reprezentat in complement fata de 2 //in baza 10: -35 //rezultat: ax devine 0xFFF9, adica -5 (catul) // dx devine 0 (restul) mov mov mov div ax, -35; //in hexa (complement fata de 2): FFDD dx, -1; //in hexa (complement fata de 2): FFFF bx,7 bx //deimpartitul este (dx, ax), adica FFFFFFDD // - numar pozitiv (deoarece folosim div) // in baza 10: 4294967261 //rezultat: EROARE, deoarece FFFF > 0007, // catul (613566751, adica 2492491F) nu incape in ax
} }
Instruciuni pe bii
# Instruciuni booleene Sunt implementate operaiile booleene binare AND, OR, XOR, i cea unar NOT, denumirile instruciunilor corespunztoare fiind aceleai cu ale operaiilor. Sintaxa:
and destinatie, sursa or destinatie, sursa xor destinatie, sursa not destinatie
Efect:
and, or, xor: se efectueaz operaia corespunzztoare ntre biii de pe aceeai poziie din cei doi operanzi, pentru toate pozitiile; rezultatul este pus n primul operand (destinatie). not: se neag fiecare bit din operand.
operaii binare (and, or, xor): o operanzii pot fi: registru, registru registru, memorie memorie, registru memorie, constant numeric registru, constant numeric (la fel ca la mov, add, adc, sub, sbb)
12
o operanzii trebuie s aib aceeai dimensiune. operaii unare (not): o operandul poate fi: registru memorie
(la fel ca la mul, imul, div, idiv) Instruciunile and, or, xor modific indicatorul ZERO: dac rezultatul operaiei booleene corespunztoare este 0, atunci indicatorul ZERO devine 1; dac rezultatul este diferit de 0, atunci indicatorul ZERO devine 0. Utilitatea principal a acestor instruciuni este n lucrul cu mti. De exemplu, dac ne intereseaz valoarea bitului al 5-lea din registrul ax, este suficient s se execute and ntre ax i valoarea (scrisa binar) 0000000000010000 (aceasta se numete masc). Rezultatul operaiei va fi 0 (iar indicatorul ZERO va deveni 1) dac bitul al 5-lea din ax are valoarea 0, respectiv va fi diferit de 0 (iar indicatorul ZERO va deveni 0) dac bitul al 5-lea din ax are valoarea 1. Dezavantajul abordrii de mai sus este acela c instruciunea and modific valoarea primului operand, plasnd acolo rezultatul operaiei. Instruciunea test are acelai efect ca i and (execut AND ntre biii celor doi operanzi, modific la fel indicatorul ZERO), dar nu altereaz valoarea primului operand. De exemplu:
test ax, 0x0010 // binar: 0000000000010000
fr a altera valoarea din ax. Nu exist instruciuni similare pentru celelalte funcii logice. # Instruciuni de deplasare Sunt instruciuni care permit deplasarea biilor n cadrul operanzilor cu un numr precizat de poziii. Deplasrile pot fi aritmetice sau logice. Deplasrile aritmetice pot fi utilizate pentru a nmuli sau mpri numere prin puteri ale lui 2. Deplasrile logice pot fi utilizate pentru a izola bii n octei sau cuvinte. Dintre modificrile pe care deplasrile le fac asupra indicatorilor:
Carry Flag (CF) = ultimul bit deplasat n afara operandului destinaie; Sign Flag (SF) = bitul cel mai semnificativ din operandul destinaie; Zero Flag (ZF) = 1 dac operandul destinaie devine 0, 0 altfel.
shr dest, count shl dest, count sar dest, count sal dest, count
unde:
dest semnific destinaia a crei valoare va fi modificat; poate fi registru sau locaie de memorie: o shl eax, 1 o shl dx, 3 o shl byte ptr [...], 2 count precizeaz cu cte poziii se face deplasarea; poate fi constant numeric sau registrul cl: o shl ebx, cl
Instruciunea shr (SHift Right) Sintaxa: shr dest, count Efect: deplasarea la dreapta a biilor din dest cu numrul de poziii precizat de count; completarea la stnga cu 0; plasarea n Carry a ultimului bit ieit. Exemplu:
mov bl, 33; //binar: 00100001 shr bl, 3; //bl devine 00000100 //Carry devine 0 shr bl, 3 //bl devine 00000000 //Carry devine 1
Instruciunea shl (SHift Left) Sintaxa: shl dest, count Efect: deplasarea la stnga a biilor din dest cu numrul de poziii precizat de count; completarea la dreapta cu 0; plasarea n Carry a ultimului bit ieit. Exemplu:
mov bl, 33; //binar: 00100001 shl bl, 3; //bl devine 00001000 //Carry devine 1 shl bl, 1 //bl devine 00010000 //Carry devine 0
Instruciunea sar (Shift Arithmetic Right) Sintaxa: sar dest, count Efect: deplasarea la dreapta a biilor din dest cu numrul de poziii precizat de count; bitul cel mai semnificativ i pstreaz vechea valoare, dar este i deplasat spre dreapta (extensie de semn); plasarea n Carry a ultimului bit ieit. Exemplu:
mov bl, -36; //binar: 11011100 shr bl, 2; //bl devine 11110111 //Carry devine 0
14
Trebuie menionat c sar nu furnizeaz aceeai valoare ca i idiv pentru operanzi echivaleni, deoarece idiv trunchiaz toate cturile ctre 0, n timp ce sar trunchiaz cturile pozitive ctre 0 iar pe cele negative ctre infinit negativ. Exemplu
mov ah, -7; //binar: 11111001 sar ah, 1; //teoretic, echivalent cu impartirea la 2 //rezultat: 11111100, adica -4 //idiv obtine catul -3
Instruciunea sal (Shift Arithmetic Left) Sintaxa: sal dest, count Efect: identic cu shl.
# Instruciuni de salt Instruciunile de salt modific valoarea registrului contor program (EIP), astfel nct urmtoarea instruciune care se execut s nu fie neaprat cea care urmeaz n memorie. Sunt utile pentru implementarea, la nivel de limbaj de asamblare, a structurilor de control (testri sau bucle). Salturile pot fi:
Sintaxa: instructiune_salt adresa Vom considera n continuare doar cazul n care adresa este o constant referit de o etichet. Exemplul de mai jos ilustraz modul de definire i utilizare a etichetelor:
#include <stdio.h> void main(){ int i; _asm{ mov i, 11; jmp eticheta; sub i, 3; // aceasta instructiune nu se executa eticheta: add i, 4; } printf ("%d\n", i); }
Saltul necondiionat (instruciunea jmp) Nu introduce o ramificaie n program, neexistnd variante de execuie. Este util, folosit mpreun cu salturi condiionate, pentru reluarea unei secvene de de cod ntr-o bucl, aa cum se va vedea ntr-un exemplu ulterior.
15
condiia de salt este adevrat se face saltul la adresa indicat condiia de salt este fals se continu cu instruciunea urmtoare din memorie ca i cum nu ar fi existat instruciune de salt.
# Instruciuni care testeaz indicatorii individuali Cele mai utile la acest nivel sunt cele care testeaz indicatorii: Carry, Overflow, Sign, Zero. Pentru fiecare indicator exist dou instruciuni de salt condiionat: una care face saltul cnd indicatorul testat are valoarea 1 i una care face saltul cnd are valoarea 0. indicator testat salt pe valoarea 1 salt pe valoarea 0 Carry Overflow Zero Sign Exemplu:
#include <stdio.h> void main(){ int a, b, s=0; printf("a="); scanf("%d", &a); printf("b="); scanf("%d", &b); _asm{ mov eax, a; add eax, b; jo semnaleaza_depasire; //in Visual C++ 6.0, // putem sari la o eticheta din codul C mov s, eax; jmp afiseaza_suma; //sau din alt bloc asm } semnaleaza_depasire: printf ("S-a produs depasire!\n"); return; _asm{ afiseaza_suma: } printf ("%d + %d = %d\n", a, b, s); }
jc jo jz js
# Instruciuni corespunztoare operatorilor relaionali n practic, utilizm mai des ramificri dictate de operatori relaionali: <, <=, !=, etc. n acest sens este util instruciunea de comparare cmp:
16
cmp funcioneaz ca i sub (aceleai restricii pentru operanzi, aceiai indicatori modificai), ns nu modific primul operand (destinaia). Prin verificarea indicatorilor se poate stabili n urma aceste operaii relaia dintre operanzi. Instruciunile care fac aceste verificri sunt: relaie instruciune Comentariu "Jump if Below" "Jump if Below or Equal" "Jump if Above" "Jump if Above or Equal"
op1 < op2 jb op1 <= op2 jbe op1 > op2 ja op1 >= op2 jae
pentru numere fr semn, respectiv relaie instruciune Comentariu "Jump if Less than" "Jump if Less than or Equal" "Jump if Greater than" "Jump if Greater than or Equal"
op1 < op2 jl op1 <= op2 jle op1 > op2 jg op1 >= op2 jge pentru numere cu semn.
Sunt necesare instruciuni diferite pentru numere fr semn, respectiv cu semn, deoarece indicatorii ce trebuie verificai difer. De exemplu, comparnd 00100110 i 11001101, ar trebui s obinem relaia 00100110 < 11001101 dac sunt numere fr semn, i 00100110 > 11001101 dac sunt numere cu semn. Independent de statutul bitului celui mai semnificativ (semn sau cifr) funcioneaz instruciunile: relaie instruciune Comentariu "Jump if Equal" (identic cu jz) "Jump if Not Equal" (identic cu jnz)
regitri SS i ESP; deoarece regitrii segment au, pe mainile pe 32 de bii, doar scop de "validare" a adresei, vom lucra n continuare numai cu ESP. Instruciunile care permit lucrul cu stiva sunt push i pop.
# Instruciunea push Realizeaz introducerea unei valori n stiv. Sintaxa: push operand; Operandul poate fi registru, locaie de memorie sau constant numeric. Stiva lucreaz doar cu valori de 2 sau 4 octei, pentru uniformitate preferndu-se numai operanzi de 4 octei (varianta cu 2 se pstraz pentru compatibilitate cu procesoarele mai vechi). Exemple:
push push push push push push eax dx dword ptr [...] word ptr [...] dword ptr 5 word ptr 14
Introducerea valorii n stiv se face astfel: procesorul scrie valoarea operandului la adresa indicat de registrul ESP (vrful stivei), dup care scade din ESP dimendiunea, n octei, a valorii depuse n stiv, adic 2 sau 4 (se observ c se avanseaz "n jos", de la adresele mai mari la adresele mai mici); n acest mod, vrful stivei este pregtit pentru urmtoarea operaie de scriere. De exemplu, instruciunea:
push eax; ar fi echivalent cu mov [esp], eax; sub esp, 4;
Prin folosirea lui push n locul secvenei echivalente se reduce, ns, riscul erorilor.
# Instruciunea pop Extrage vrful stivei ntr-un operand destinaie. Sintaxa: pop operand; Operandul poate fi registru sau locaie de memorie, de 2 sau 4 octei. Exemple:
pop pop pop pop eax cx dword ptr [...] word ptr [...]
18
Extragerea valorii din stiv se face prin depunerea n destinaie a valorii aflate n vrful stivei (la adresa [ESP]) i adunarea, la ESP, a numrului de octei ai operandului (acesta indic, practic, numrul de octei scoi din stiv).
# Rolul stivei Rolul stivei procesorului este acela de a stoca informaii cu caracter temporar. De exemplu, dac avem nevoie s folosim un registru pentru nite operaii, dar nu avem la dispoziie nici un registru a crui valoare curent s ne permitem s o pierdem, putem proceda ca mai jos:
push eax; //se salveaza temporar valoarea lui eax pe stiva ... // utilizare eax pop eax //restaurare
Variabilele locale (cu excepia celor statice) sunt plasate de asemenea n stiv (deoarece au caracter temporar: sunt create la intrarea n funcie i distruse la ieire). n lucrul cu stiva, instruciunile de introducere n stiv trebuie riguros compensate de cele de scoatere, din punctul de vedere al numrului de instruciuni i al dimensiunii operanzilor. Orice eroare poate afecta mai multe date, din cauza decalajelor.
push edx; push eax; ... //utilizare registri pop ax //se recupereaza doar 2 octeti din valoarea anterioara a lui eax pop edx //nu se recupereaza edx, ci 2 octeti din eax, 2 din edx //decalajul se poate propaga astfel pana la capatul stivei
O alt eroare poate aprea atunci cnd registrul ESP este manipulat direct. De exemplu, pentru a aloca spaiu unei variabile locale (neiniializat), e suficient a scdea din ESP dimensiunea variabilei respective. Similar, la distrugerea variabilei, valoarea ESP este crescut. Aici nu se folosesc n general instruciuni push, repectiv pop, deoarece nu intereseaz valorile implicate, ci doar ocuparea i eliberarea de spaiu. Se prefer adunarea i scderea direct cu registrul ESP; evident c o eroare n aceste operaii are consecine de aceeai natur ca i cele de mai sus.
# Apelul funciilor Un apel de funcie arat la prima vedere ca o instruciune de salt, n sensul c se ntrerupe execuia liniar a programului i se sare la o alt adres. Diferena fundamental const n faptul c la terminarea funciei se revine la adresa de unde s-a fcut apelul i se continu cu instruciunea urmtoare. Din moment ce ntrun program se poate apela o funcie de mai multe ori, din mai multe locuri, i ntotdeauna se revine unde trebuie, este clar c adresa la care trebuie revenit este memorat i folosit atunci cnd este cazul. Cum adresa de revenire este n mod evident o informaie temporar, locul su este tot pe stiv.
# Instruciunea call Apelul unei funcii se realizeaz prin instruciunea call. Sintaxa: call adresa
19
n Visual C++ vom folosi nume simbolice pentru a preciza adresa, cu meniunea c de data asta nu este vorba de etichete, ca la salturi, ci chiar de numele funciilor apelate. Efectul instruciunii call: se introduce n stiv adresa instruciunii urmtoare (adresa de revenire) i se face salt la adresa indicat. Aceste aciuni puteau fi realizate i cu instruciuni push i jmp, dar din nou se prefer call pentru evitarea erorilor.
# Instruciunea ret Revenirea dintr-o funcie se face prin instruciunea ret, care poate fi folosit fr operand. n acest caz, se preia adresa de revenire din vrful stivei (similar unei instruciuni pop) i se face saltul la adresa respectiv. Din motive de conlucrare cu Visual Studio, nu vom folosi aceast instruciune.
# Transmiterea parametrilor Parametrii sunt tot nite variabile locale, deci se gsesc pe stiv. Cel care face apelul are responsabilitatea de a-i pune pe stiv la apel i de a-i scoate de pe stiv le revenirea din funcia apelat. Avem la dispoziie instruciunea push pentru plasarea n stiv. Evident, aceast operaie trebuie realizat imediat nainte de apelul propriu-zis. n plus, n limbajul C/C++ (nu n toate), parametrii trebuie pui n stiv n ordine invers celei n care se gsesc n lista de parametri. La revenire, parametrii trebuie scoi din stiv, nemaifiind necesari. Cum nu ne intereseaz preluarea valorilor lor, nu se folosete instruciunea pop, care ar putea altera inutil un registru, de exemplu, ci se adun la ESP numrul total de octei ocupat de parametri (atenie, pe stiv se lucreaz n general cu 4 octei, chiar dac operanzii au dimensiuni mai mici). S lum ca exemplu funcia urmtoare:
void show_dif(int a,int b){ int c; c=a-b; printf("%d\n",c); }
Apelul dif(5,9) se traduce prin secvena care se poate vedea mai jos:
void main(){ _asm{ push dword ptr 9 push dword ptr 5 call show_dif add esp,8 } }
# Returnarea unei valori Convenia n Visual C++ (i la majoritatea compilatoarelor) este c rezultatul se depune ntr-un anumit registru, n funcie de dimensiunea sa:
pentru tipurile de date de dimensiune 1 octet - n registrul AL pentru tipurile de date de dimensiune 2 octei - n registrul AX
20
pentru tipurile de date de dimensiune 4 octei - n registrul EAX pentru tipurile de date de dimensiune 8 octei - n regitii EDX i EAX
Evident, la revenirea din funcie, cel care a fcut apelul trebuie s preia rezultatul din registrul corespunztor. Vom modifica exemplul de mai sus astfel nct funcia s returneze diferena pe care o calculeaz ntr-o secven de instruciuni n limbaj de asamblare:
#include <stdio.h> int compute_dif(int a,int b){ _asm{ mov eax, a; sub eax, b; //in eax ramane rezultatul, care // va fi preluat la termiarea functiei }; } void main(){ int c; _asm{ push dword ptr 9 push dword ptr 5 call compute_dif //se salveaza adresa de revenire pe stiva mov c, eax; add esp,8 //"stergerea" parametrilor din stiva } printf("Diferenta este %d.\n", c); }
Sursa documentatie: Prof. Marta Grdea : http://profs.info.uaic.ro/~marta/ Pagina neoficiala de Facebook a FII: http://facebook.com/fii.uaic Fisa disciplinei Arhitectura calculatoarelor i sisteme de operare: http://www.infoiasi.ro/bin/Programs/CS1102_10 Fisa disiplinei Practic hardware: http://www.infoiasi.ro/bin/Programs/CS1211_10
21