Calculatoare
Elemente ale limbajului de asamblareELEMENTE ALE LIMBAJULUI DE ASAMBLARE1. INTRODUCERE IN LIMBAJUL DE ASAMBLARE INTELLimbajul de asamblare poseda instructiuni cod-masina, mnemonice (provin de la cuvintele in engleza ce specifica actiunea lor) precum ADD, MOV, SUB sau JMP. Programul scris folosind un limbaj de asamblare apare sub forma unei liste de de mnemonice ce pot fi convertite usor in limbajul de cod binar. Programele ce fac aceasta translatare se numesc asambloare. Exemple de limbaje de asamblare sunt: MASM (Microsoft Assembler) si TASM (Turbo Assembler – dezvoltat de firma Borland). 2. Formatul general al unei instructiuni in limbaj de asamblareO linie de cod scrisa in limbaj de asamblare are urmatorul format general: <nume> <instructiune/directiva> <operanzi> <;comentariu> unde: - <nume> - reprezinta un nume simbolic optional; - <instructiune/directiva> - reprezinta mnemonica (numele) unei instructiuni sau a unei directive; - <operanzi> - reprezinta o combinatie de unul, doi sau mai multi operanzi (sau chiar nici unul), care pot fi constante, referinte de memorie, referinte de registri, siruri de caractere, in functie de structura particulara a instructiunii; - <;comentariu> - reprezinta un comentariu optional ce poate fi plasat dupa caracterul „;” pana la sfarsitul liniei respective de cod. 3. Instructiuni generale (mnemonice)o MOV - mutare de informatie, o PUSH - punere de informatii in memoria organizata ca o stiva, o POP - aducerea informatiei din memoria stiva; instructiuni de intrare/iesire o IN - depunerea in registrul acumulator a informatiei stocate in registrul de intrare/iesire (portul de date), o OUT - scrierea in portul de date a informatiei aflate in registrul acumulator; instructiuni aritmetice o adunare (ADD, ADC, INC etc.), o scadere (SUB, DEC, NEG, CMP etc.), o inmultire (MUL, IMUL etc.), o impartire (DIV, IDIV etc.); instructiuni de manipulare a sirurilor de biti o operatii logice (NOT, AND, OR, XOR, TEST), o deplasare (SHL, SAL, SHR, SAR), o rotire (ROL, ROR, RCL, RCR); instructiuni de transfer o salt neconditionat (CALL, RET, JMP), o salt conditionat - prin testarea indicatorilor de conditii (JC, JNC, JE, JG, JL etc.), o cicluri (LOOP etc.), o intreruperi (INT, IRET etc.); instructiuni de sincronizare externa: HLT, WAIT, NOP etc. Registrii microprocesorului IntelRegistrii (sau registrele) microprocesorului reprezinta locatii de memorie speciale aflate direct pe cip; din aceasta cauza reprezinta cel mai rapid tip de memorie. Un lucru deosebit legat de registri este faptul ca fiecare dintre acestia au un scop bine precizat, oferind anumite functionalitati speciale, unice. Exista patru mari categorii de registri:
1. Registrii de uz generalRegistrul AX (EAX) Registrul AX (EAX) este denumit si registrul acumulator, fiind principalul registru de uz general utilizat pentru operatii aritmetice, logice si de deplasare de date. Totdeauna operatiile de inmultire si impartire presupun implicarea registrului AX. Unele dintre instructiuni sunt optimizate pentru a se executa mai rapid atunci cand este folosit AX. In plus, registrul AX este folosit si pentru toate transferurile de date de la/catre porturile de Intrare/Iesire. Poate fi accesat pe portiuni de 8, 16 sau 32 de biti, fiind referit drept AL (cei mai putin semnificativi 8 biti din AX), AH (cei mai semnificativi 8 biti din AX), AX (16 biti) sau EAX (32 de biti). Acest lucru este utilizat pentru a lucra cu date pe un octet, permitand ca registrul AX sa fie folosit pe postul a doi registri separati (AH si AL). Prezentam in continuare alte cateva exemple de instructiuni ce utilizeaza registrul AX. De remarcat este faptul ca transferurile de date se fac pentru instructiunile (denumite si mnemonice) Intel de la dreapta spre stanga, exact invers decat la Motorola, unde transferul se face de la stanga la dreapta. Exp: Urmatoarele trei instructiuni seteaza registrul AH cu valoarea 1, incrementeaza cu 1 aceasta valoare si apoi o copiaza in registrul AL: MOV AH, 1 INC AH MOV AL, AH Valoarea finala a registrului AX va fi 22 (AH = AL = 2). Registrul BX (EBX) Registrul BX (Base), sau registrul de baza poate stoca adrese pentru a face referire la diverse structuri de date, cum ar fi vectorii stocati in memorie. O valoare reprezentata pe 16 biti stocata in registrul BX poate fi utilizata ca fiind o portiune din adresa unei locatii de memorie ce va fi accesata. Spre exemplu, urmatoarele instructiuni incarca registrul AH cu valoarea din memorie de la adresa 21. MOV AX, 0 MOV DS, AX MOV BX, 21 MOV AH, [ BX ] Se observa ca am incarcat valoarea 0 in registrul DS inainte de a accesa locatia de memorie referita de registrul BX. Acest lucru este datorat segmentarii memoriei (segmentare discutata mai in detaliu in sectiunea consacrata registrilor de segment); implicit, atunci cand este folosit ca pointer de memorie, BX face referire relativa la registrul de segment DS adresa la care face referire este o adresa relativa la adresa de segment continuta in registrul DS). Registrul CX (ECX) Specializarea registrului CX (Counter) este numararea; de aceea, el se numeste si registrul contor. De asemenea, registrul CX joaca un rol special atunci cand se foloseste instructiunea LOOP. Rolul de contor al registrului CX se observa imediat din exemplul urmator: MOV CX, 5 start: <instructiuni ce se vor executa de 5 ori> SUB CX, 1JNZ start Deoarece valoarea initiala a lui CX este 5, instructiunile cuprinse intre eticheta start si instructiunea JNZ se vor executa de 5 ori (pana cand registrul CX devine 0). Instructiunea SUB CX, 1 decrementeaza registrul CX cu valoarea 1 iar instructiunea JNZ start determina saltul inapoi la eticheta start daca CX nu are valoarea 0. In limbajul microprocesorului exista si o instructiune speciala legata de ciclare. Aceasta este instructiunea LOOP, care este folosita in combinatie cu registrul CX. Liniile de cod urmatoare sunt echivalente cu cele anterioare, dar aici se utilizeaza instructiunea LOOP: MOV CX, 5 start: <instructiuni ce se vor executa de 5 ori> LOOP start Se observa ca instructiunea LOOP este folosita in locul celor doua instructiuni SUB si JNZ anterioare; LOOP decrementeaza automat registrul CX cu 1 si executa saltul la eticheta specificata (start) daca CX este diferit de zero, totul intr-o singura instructiune. Registrul DX (EDX) Registrul de uz general DX (Data register), denumit si registrul de date, poate fi folosit in cazul transferurilor de date Intrare/Iesire sau atunci cand are loc o operatie de inmultire sau de impartire. Instructiunea IN AL, DX copiaza o valoare de tip Byte dintr-un port de intrare, a carui adresa se afla in registrul DX. Urmatoarele instructiuni determina scrierea valorii 101 in portul I/O 1002: MOV AL, 101 MOV DX, 1002 OUT
Referitor la operatiile de inmultire si impartire, atunci cand impartim un numar pe 32 de biti la un numar pe 16 biti, cei mai semnificativi 16 biti ai deimpartitului trebuie sa fie in DX. Dupa impartire, restul impartirii se va afla in DX. Cei mai putin semnificativi 16 biti ai deimpartitului trebuie sa fie in AX iar catul impartirii va fi in AX. La inmultire, atunci cand se inmultesc doua numere pe 16 biti, cei mai semnificativi 16 biti ai produsului vor fi stocati in DX iar cei mai putin semnificativi 16 biti in registrul AX. Registrul SI Registrul SI (Source Index) poate fi folosit, ca si BX, pentru a referi adrese de memorie. De exemplu, secventa de instructiuni urmatoare: MOV AX, 0 MOV DS, AX MOV SI, 33 MOV AL, [ SI ] Incarca valoarea (pe 8 biti) din memorie de la adresa 33 in registrul AL. Registrul SI este, de asemenea, foarte folositor atunci cand este utilizat in legatura cu instructiunile dedicate tipului STRING (sir de caractere). Registrul DI Registrul DI (Destination Index) este utilizat in mod asemanator registrului SI. In secventa de instructiuni urmatoare: MOV AX, 0 MOV DS, AX MOV DI, 1000 ADD BL, [ DI ] se aduna la registrul BL valoarea pe 8 biti stocata la adresa 1000. Registrul DI este putin diferit fata de registrul SI in cazul instructiunilor de tip string; daca SI este intotdeauna pe post de pointer sursa de memorie, registrul DI serveste drept pointer destinatie de memorie. Mai mult, in cazul instructiunilor de tip string, registrul SI adreseaza memoria relativ la registrul de segment DS, in timp ce DI contine referiri la memorie relativ la registrul de segment ES. In cazul in care SI si DI sunt utilizati cu alte instructiuni, ei fac referire la registrul de segment DS. Registrul BP Rolul registrilor BP si SP este prezenta in contextul utilizarii stivei. Stiva reprezinta o portiune speciala de locatii adiacente din memorie. Aceasta este continuta in cadrul unui segment de memorie si identificata de un selector de segment memorat in registrul SS (cu exceptia cazului in care se foloseste modelul nesegmentat de memorie in care stiva poate fi localizata oriunde in spatiul de adrese liniare al programului). Stiva este o portiune a memoriei unde valorile pot fi stocate si accesate pe principul LIFO (Last In – First Out), drept urmare ultima valoare stocata in stiva este prima ce va fi citita din stiva. De regula, stiva este utilizata la apelul unei proceduri sau la intoarcerea dintr-un apel de procedura (principalele instructiuni folosite sunt CALL si RET). Registrul pointer de baza, BP (Base Pointer) poate fi utilizat ca pointer de memorie precum registrii BX, SI si DI. Diferenta este aceea ca, daca BX, SI si DI sunt utilizati in mod normal ca pointeri de memorie relativ la segmentul DS, registrul BP face referire relativ la segmentul de stiva SS. Deoarece BX, SI si DI se refera la segmentul de date, nu exista o modalitate eficienta de a folosi registrii BX, SI, DI pentru a face referire la parametrii salvati in stiva din cauza ca stiva este localizata intr-un alt segment de memorie. Registrul BP ofera rezolvarea acestei probleme asigurand adresarea in segmentul de stiva. Spre exemplu, instructiunile: PUSH BP MOV BP, SP MOV AX, [ BP+4 ] fac sa se acceseze segmentul de stiva pentru a incarca registrul AX cu primul parametru trimis de un apel C unei rutine scrise in limbaj de asamblare. In concluzie, registrul BP este conceput astfel incat sa ofere suport pentru accesul la parametri, variabile locale si alte necesitati legate de accesul la portiunea de stiva din memorie. Fig. Structura stivei Registrul SP Registrul SP (Stack Pointer), sau pointerul de stiva, retine de regula adresa de deplasament a urmatorului element disponibil in cadrul segmentului de stiva. Acest registru este, probabil, cel mai putin „general” dintre registrii de uz general, deoarece este dedicat mai tot timpul administrarii stivei. Registrul BP face in fiecare clipa referire la varful stivei – acest varf al stivei reprezinta adresa locatiei de memorie in care va fi introdus urmatorul element in stiva. Actiunea de a introduce un nou element in stiva se numeste „impingere” (in engleza push); de aceea, instructiunea respectiva poarta numele de PUSH. In mod asemanator, operatia de scoatere a unui element din varful stivei poarta, in engleza, numele de pop, iar instructiunea echivalenta operatiei se numeste POP. Este permisa stocarea valorilor in registrul SP precum si modificarea valorii sale prin adunare sau scadere la fel ca si in cazul celorlalti registri de uz general; totusi, acest lucru nu este recomandat daca nu suntem foarte siguri de ceea ce facem. Prin modificarea registrului SP, vom modifica adresa de memorie a varfului stivei, ceea ce poate avea efecte neprevazute, aceasta pentru ca instructiunile PUSH si POP nu reprezinta unicele modalitati de utilizare a stivei. Indiferent daca apelam o subrutina sau ne intoarcem dintr-un astfel de apel de subrutina, fie procedura sau functie, in acest caz este folosita stiva. Unele resurse de sistem, precum tastatura sau ceasul de sistem, pot folosi stiva in momentul trimiterii unei intreruperi la microprocesor. Acest lucru presupune ca stiva este folosita continuu, deci daca se modifica registrul SP (adica adresa stivei), datele din noile locatii de memorie nu vor mai fi cele corecte. In concluzie, registrul SP nu trebuie modificat in mod direct; el este modificat automat in urma instructiunilor POP, PUSH, CALL, RET. Oricare dintre ceilalti registri de uz general pot fi modificati in mod direct in orice moment. 2.2. Registrul pointer de instructiuni (IP)Registrul pointer de instructiuni este folosit, intotdeauna, pentru a stoca adresa urmatoarei instructiuni ce va fi executata de catre microprocesor. Pe masura ce o instructiune este executata, pointerul de instructiune este incrementat si se va referi la urmatoarea adresa de memorie (unde este stocata urmatoarea instructiune ce va fi executata). De regula, instructiunea ce urmeaza a fi executata se afla la adresa imediat urmatoare instructiunii ce a fost executata, dar exista si cazuri speciale (rezultate fie din apelul unei subrutine prin instructiunea CALL, fie prin intoarcerea dintr-o subrutina, prin instructiunea RET). Pointerul de instructiuni nu poate fi modificat sau citit in mod direct; doar instructiuni speciale pot incarca acest registru cu o noua valoare. Registrul pointer de instructiune nu specifica pe de-a intregul adresa din memorie a urmatoarei instructiuni ce va fi executata, din aceeasi cauza a segmentarii memoriei. Pentru a aduce o instructiune din memorie, registrul CS ofera o adresa de baza iar registrul pointer de instructiuneindica adresa de deplasament plecand de la aceasta adresa de baza.
Fig. Registrii de segment, pointerul de instructiuni si registrul indicatorilor de stare
2.3. Registrul indicatorilor de stare (FLAGS) pe 16 biti contine informatii legate de starea microprocesorului precum si de rezultatele ultimilor instructiuni executate. Un indicator de stare (flag) este in sine o locatie de memorie de 1 bit ce indica starea curenta a microprocesorului si modalitatea sa de operare. Un indicator se spune ca “este setat” daca are valoarea 1 si “nu este setat” in caz contrar. Indicatorii de stare se modifica dupa executia unor instructiuni aritmetice sau logice. Exemple de indicatori de stare: - C (Carry) indica aparitia unei cifre binare de transport in cazul unei adunari sau imprumut in cazul unei scaderi; - O (Overflow) apare in urma unei operatii aritmetice. Daca este setat, inseamna ca rezultatul nu incape in operandul destinatie; - Z (Zero) indica faptul ca rezultatul unei operatii aritmetice sau logice este zero; - S (Sign) indica semnul rezultatului unei operatii aritmetice; - D (Direction) – cand este zero, procesarea elementelor sirului se face de la adresa mai mica la cea mai mare, in caz contrar este invers; - I (Interrupt) controleaza posibilitatea microprocesorului de a raspunde la evenimente externe (apeluri de intrerupere); - T (Trap) este folosit de programele de depanare (de tip debugger), activand sau nu posibilitatea executiei programului pas cu pas. Daca este setat, UCP intrerupe fiecare instructiune, lasand programul depanator sa execute programul respectiv pas cu pas; - A (Auxiliary carry) suporta operatii in codul BCD. Majoritatea programelor nu ofera suport pentru reprezentarea numerelor in acest format, de aceea se utilizeaza foarte rar; - P (Parity) este setat in conformitate cu paritatea bitilor cei mai putin semnificativi ai unei operatii cu date. Astfel, daca rezultatul unei operatii contine un numar par de biti 1, acest indicator este setat. Daca numarul de biti 1 din rezultat este impar, atunci indicatorul PF este zero. Este folosit de regula de programe de comunicatii, dar Intel a introdus acest indicator nu pentru a indeplini o anumita functionalitate, ci pentru a asigura compatibilitatea cu vechile microprocesoare ale familiei x86. 3.4 Registrii de segmentProprietatile registrilor de segment sunt in stransa legatura cu notiunea de segmentare a memoriei. Premisa de la care se pleaca este urmatoarea: 8086 este capabil sa adreseze 1MB de memorie, astfel ca sunt necesare adrese pe 20 de biti pentru a cuprinde toate locatiile din spatiul de 1 MB de memorie. Totusi, registrele utilizate sunt registre pe 16 biti, deci a trebuit sa se gaseasca o solutie pentru aceasta problema. Solutia gasita se numeste segmentarea memoriei; in acest caz memoria de 1MB este impartita in 16 segmente de cate 64 KB (16*64 KB = 1024 KB = 1 MB). Notiunea de segmentare a memoriei presupune utilizarea unor adrese de memorie formate din doua parti. Prima parte reprezinta adresa segmentului iar cea de-a doua portiune reprezinta adresa de deplasament, sau offset-ul. Exemplu de program scris folosind instructiunile limbajuli de asamblare : Cerinta : Sa se scrie secventa de cod cu ajutorul careia se pot interschimba doua linii dintr-o matrice. Rezolvare: main() //matricea ocupa in memorie 12*4 octeti long col=3; long i=1; long j=4; long tempESP=0; //variabile utilizate pt. memorarea pointerului (cursorului) si bazei stivei long tempEBP=0; long vari=0; //variabila in care memoram adresa primului element din linia i long varj=0; //variabila in care memoram adresa primului element din linia j long sz=sizeof(long); //incarcam adresa efectiva a primului element al matricei _asm lea ebx,matr //calculam offset-ul primului octet al primului element din linia i, adica (i-1) x col _asm mov eax,i _asm sub eax _asm mul col _asm mul sz ;luam in calcul faptul ca elementele matricei sunt de tip long, adica sunt variabile pe 4 octeti //stim offset-ul (calculat in eax) si, ca atare, putem calcula adresa primului element al liniei i; memoram //in <vari> aceasta adresa _asm add ebx,eax _asm mov vari,ebx //REincarcam adresa efectiva a primului element al matricei _asm lea ebx,matr //calculam indicele primului element din linia j, adica (j-1) x col _asm mov eax,j _asm sub eax,1 _asm mul col _asm mul sz ;luam in calcul faptul ca elementele matricei sunt de tip long, adica sunt variabile pe 4 octeti //stim indicele (calculat in eax) si, ca atare, putem calcula adresa primului element al liniei j; memoram in <varj> aceasta adresa _asm add ebx,eax _asm mov varj,ebx _asm mov ecx,col _asm mov eax,vari _asm mov ebx,varj _asm mov tempESP,esp ;salvam indicatorul de stiva _asm mov tempEBP,ebp ;salvam baza stivei _asm bucla: _asm push [eax] _asm push [ebx] _asm mov edx,esp //salvam cursorul (pointerul) stivei _asm pop [eax] //scoatem ultima valoare dusa in stiva in locul valorii din EAX _asm mov esp,edx //restauram pointerul stivei de dinanintea ultimului pop _asm add esp,sz //incrementam pointerul stivei cu patru octeti (elementele matricei noastre sunt pe 4 octeti) _asm mov edx,esp //salvam cursorul (pointerul) stivei _asm pop [ebx] _asm mov esp,edx //restauram pointerul stivei de dinanintea ultimului pop _asm add esp,sz //incrementam pointerul stivei cu patru octeti (elementele matricei noastre sunt pe 4 octeti) _asm add eax,sz //ne deplasam la urmatorul element din linia i _asm add ebx,sz //ne deplasam la urmatorul element din linia j _asm loop bucla _asm mov esp,tempESP //restauram pointerul si baza stivei memorate anterior _asm mov ebp,tempEBP return 0;
|