This shows you the differences between two versions of the page.
iocla:laboratoare:laborator-06 [2019/11/02 14:38] laurentiu.stefan97 |
— (current) | ||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== Laborator 06: Lucrul cu stiva ====== | ||
- | |||
- | În acest laborator vom învăța cum este reprezentată stiva in limbajul de asamblare, care este utilitatea ei si cum se programeaza cu ajutorul acesteia. | ||
- | |||
- | ===== Terminologie ===== | ||
- | |||
- | In lumea algoritmicii, stiva este o structura de date abstracta prin intermediul careia se poate reprezenta informatie ce respecta regula "primul venit, ultimul servit". | ||
- | |||
- | In lumea programarii assembly, stiva este o zona rezervata de memorie folosita pentru a tine evidenta operatiilor interne ale unui program: functii, adrese de retur, parametrii pasati, etc. Astfel, stiva poate fi vazuta ca o implementare la nivel hardware a stivei abstracte. | ||
- | |||
- | ===== Utilitatea stivei ===== | ||
- | |||
- | * **Apeluri de functii**. Fiecare apel de functie necesita, printre altele, memorarea adresei de retur. Imaginati-va ca aveti un numar de functii N ce se apeleaza succesiv : prima functie o apeleaza pe a 2-a, a 2-a pe a 3-a si asa mai departe; cand functia N a fost apelata si si-a terminat executia, programul trebuie sa continue in contextul in care a avut loc apelul. Daca retinem adresele de retur intr-o stiva, ordinea apelurilor se pastreaza in mod natural. | ||
- | * **Variabile temporare**. In general, programele folosesc un numar mare de variabile si rezultate partiale; avand in vedere ca exista un numar limitat de registre, este posibil ca la un moment dat, sa nu avem niciun registru disponibil pentru a retine valoarea unei operatii. In cazul acesta, putem memora rezultatul la o adresa de memorie pe care o definim in zona de date. Aceasta metoda are dezavantajul ca vom "polua" tabela de offset-uri cu variabile pe care le folosim o singura data. In loc sa facem aceasta pentru fiecare rezultat partial, este mai usor sa punem pe stiva valoarea unui registru pentru a il putea folosi si a o recupera in momentul in care nu mai folosim variabila temporara. | ||
- | |||
- | ===== Operatii asupra stivei ===== | ||
- | |||
- | Stiva poate fi modificata in 2 moduri: | ||
- | |||
- | 1. Prin utilizarea instructiunilor special implementate pentru lucrul cu stiva, dintre care cele mai uzuale sunt ''push'' si ''pop'': | ||
- | |||
- | <code asm> | ||
- | |||
- | %include "io.inc" | ||
- | |||
- | section .text | ||
- | global CMAIN | ||
- | CMAIN: | ||
- | |||
- | mov eax, 7 | ||
- | mov ebx, 8 | ||
- | add eax, ebx | ||
- | push eax ; pune pe stiva continutul registrului eax | ||
- | mov eax, 10 ; acum putem folosi registrul, intrucat valoarea lui este salvata pe stiva | ||
- | PRINT_UDEC 4, eax ; 10 | ||
- | NEWLINE | ||
- | | ||
- | pop eax ; recupereaza valoarea registrului eax | ||
- | PRINT_UDEC 4, eax ; 15 | ||
- | |||
- | </code> | ||
- | |||
- | 2. Adresand memoria, cu ajutorului registrului in care este tinut pointerul catre capul stivei ("stack pointer") ''esp'': | ||
- | |||
- | <code asm> | ||
- | |||
- | %include "io.inc" | ||
- | |||
- | section .text | ||
- | global CMAIN | ||
- | CMAIN: | ||
- | mov eax, 7 | ||
- | mov ebx, 8 | ||
- | add eax, ebx | ||
- | sub esp, 4 ; rezerva 4 octeti pe stiva | ||
- | mov [esp], eax ; muta la noua adresa catre care pointeaza esp continutul registrului eax | ||
- | mov eax, 10 | ||
- | PRINT_UDEC 4, eax | ||
- | NEWLINE | ||
- | | ||
- | mov eax, [esp] ; recupereaza valoarea de pe stiva | ||
- | add esp, 4 ; restabileste valoarea registrului esp | ||
- | PRINT_UDEC 4, eax | ||
- | |||
- | </code> | ||
- | |||
- | <note important> Comentati instructiunile ''sub esp, 4'' si ''add esp, 4''. Ce se intampla? De ce?</note> | ||
- | <note tip> Stiva este folosita pentru a memora adresa de retur in momentul in care o functie este apelata</note> | ||
- | |||
- | **Remarcati faptul ca stiva creste de la adrese mari la adrese mici**. Acesta este motivul pentru care alocarea memoriei pe stiva se face folosind instructiunea ''sub'', iar eliberarea se face folosind instructiunea ''add''. | ||
- | |||
- | {{ :iocla:laboratoare:stack.png?300 |}} | ||
- | |||
- | Unele procesoare nu au suport pentru lucrul cu stiva: spre exemplu, procesoarele [[https://en.wikipedia.org/wiki/MIPS_architecture|MIPS]] nu au instructiuni ''push'' si ''pop'' si nici un registru special pentru stack pointer. Astfel, daca am dori sa implementam operatiile pe stiva in procesorul MIPS aceasta s-ar realiza exact ca in exemplul de mai sus, doar ca am putea sa ne alegem noi orice registru pentru a tine minte stack pointerul. | ||
- | |||
- | Asadar, instructiunea ''push eax'', pe un procesor x86, este echivalenta cu: | ||
- | <code asm> | ||
- | sub esp, 4 | ||
- | mov [esp], eax | ||
- | </code> | ||
- | |||
- | Iar instructiunea ''pop eax'': | ||
- | <code asm> | ||
- | mov eax, [esp] | ||
- | add esp, 4 | ||
- | </code> | ||
- | ===== Tricks and tips ===== | ||
- | |||
- | 1. Regula de aur a utilizarii stivei este : numarul de ''push''-uri == numarul de ''pop''-uri intr-o functie. Avand in vedere ca stiva este folosita pentru apelarea functiilor, este foarte important ca in momentul in care o functie isi termina executia, sa actualizeze ''esp''-ul astfel incat acesta sa indice catre aceeasi zona de memorie (a stivei) catre care indica in momentul intrarii in functie. | ||
- | |||
- | 2. Avand in vedere ca exista situatii in care facem un numar N de ''push''-uri si ajungem la finalul functiei fara sa fi facut ''pop'' pentru niciuna dintre valori, putem restabili capul stivei folosind instructiunea ''add''. | ||
- | |||
- | <code asm> | ||
- | |||
- | %include "io.inc" | ||
- | |||
- | section .text | ||
- | global CMAIN | ||
- | CMAIN: | ||
- | mov eax, 5 | ||
- | mov ebx, 6 | ||
- | mov ecx, 7 | ||
- | | ||
- | push eax | ||
- | push ebx | ||
- | push ecx | ||
- | | ||
- | add esp, 12 ; echivalent cu utilizarea a 3 pop-uri consecutive | ||
- | ret | ||
- | |||
- | </code> | ||
- | |||
- | 3. Metoda de mai sus are dezavantajul ca tot trebuie sa cautam prin program cate ''push''-uri am facut (ceea ce poate sa necesite destul de mult timp in programele din viata reala). Daca nu vrem sa nu ne batem deloc capul cu stack pointerul, | ||
- | putem sa folosim urmatoarea constructie: | ||
- | |||
- | <code asm> | ||
- | |||
- | %include "io.inc" | ||
- | |||
- | section .text | ||
- | global CMAIN | ||
- | CMAIN: | ||
- | | ||
- | mov ebp, esp ; salveaza stack pointerul curent | ||
- | | ||
- | mov eax, 5 | ||
- | mov ebx, 6 | ||
- | mov ecx, 7 | ||
- | |||
- | push eax | ||
- | push ebx | ||
- | push ecx | ||
- | | ||
- | mov esp, ebp ; restaureaza stack pointerul | ||
- | ret | ||
- | |||
- | </code> | ||
- | |||
- | <note important> Care este intrebuintarea principala a registrului ''ebp''? </note> | ||
- | |||
- | ===== Exerciții ===== | ||
- | |||
- | În cadrul exercițiilor vom folosi [[http://elf.cs.pub.ro/asm/res/laboratoare/lab-06-tasks.zip|arhiva de laborator]]. | ||
- | |||
- | Descărcați arhiva, decomprimați-o și accesați directorul aferent. | ||
- | |||
- | ==== [1p] 0. Recapitulare: Media aritmetică a elementelor dintr-un vector ==== | ||
- | |||
- | Pornind de la exercițiul ''0-recap-mean.asm'' din arhiva de laborator, implementați codul lipsă, marcat de comentarii de tip ''TODO'', pentru a realiza un program care calculează media aritmetică a elementelor dintr-un vector. Afișați doar partea întreagă a mediei (câtul împărțirii). | ||
- | |||
- | <note tip> | ||
- | Dacă ați făcut calculul corect, suma elementelor vectorului va fi ''3735'' iar media aritmetică a elementelor din vector va fi ''287''. | ||
- | </note> | ||
- | |||
- | ==== [1p] 1. Max ==== | ||
- | |||
- | Calculați maximul dintre numerele din 2 registre (''eax'' și ''ebx'') folosind o instrucțiune de comparație, o instrucțiune de salt și instrucțiuni ''push''/''pop''. | ||
- | |||
- | <note tip> | ||
- | Gandiți-vă cum puteți să interschimbați două registre folosind stiva. | ||
- | </note> | ||
- | ==== [1.5p] 2. Construirea array-ului inversat ==== | ||
- | |||
- | Pornind de la exercițiul ''reverse-array.asm'', implementați TODO-urile **fără a folosi instrucțiunea ''mov'' în lucrul cu array-urile** astfel încât în array-ul ''output'' la finalul programului să se afle array-ul ''input'' inversat. | ||
- | |||
- | <note> | ||
- | Dupa o rezolvare corecta programul ar trebui sa printeze: | ||
- | <code> | ||
- | Reversed array: | ||
- | 911 | ||
- | 845 | ||
- | 263 | ||
- | 242 | ||
- | 199 | ||
- | 184 | ||
- | 122 | ||
- | </code> | ||
- | </note> | ||
- | |||
- | ==== [5p] 3. Adresarea si printarea stivei ==== | ||
- | |||
- | Programul ''stack-addressing.asm'' din arhiva laboratorului alocă și inițializează două variabile locale pe stivă: | ||
- | * un array format din numerele naturale de la 1 la ''NUM'' | ||
- | * un string "Ana are mere". | ||
- | |||
- | - [**1p**] Înlocuiți fiecare instrucțiune ''push'' cu o secvență de instrucțiuni echivalentă. | ||
- | - [**2p**] Printați adresele și valorile de pe stivă din intervalul **[ESP, EBP]** (de la adrese mici la adrese mari) octet cu octet. | ||
- | - [**1p**] Printați string-ul alocat pe stivă octet cu octet și explicați cum arată acesta în memorie. Gândiți-vă de la ce adresă ar trebui să afișați și când ar trebui să vă opriți. | ||
- | - [**1p**] Printați vectorul alocat pe stivă element cu element. Gândiți-vă de la ce adresă ar trebui să începeți afișarea și ce dimensiune are un element. | ||
- | |||
- | <note tip>Citiți sectiunea [[https://ocw.cs.pub.ro/courses/iocla/laboratoare/laborator-07#operatii_asupra_stivei|Operatii asupra stivei]].</note> | ||
- | |||
- | <note> | ||
- | După o implementare cu succes, programul ar trebui să afișeze ceva de genul (nu fix același lucru, adresele de pe stivă putând să difere): | ||
- | <code> | ||
- | 0xffcf071b: 65 | ||
- | 0xffcf071c: 110 | ||
- | 0xffcf071d: 97 | ||
- | 0xffcf071e: 32 | ||
- | 0xffcf071f: 97 | ||
- | ... | ||
- | 0xffcf0734: 4 | ||
- | 0xffcf0735: 0 | ||
- | 0xffcf0736: 0 | ||
- | 0xffcf0737: 0 | ||
- | 0xffcf0738: 5 | ||
- | 0xffcf0739: 0 | ||
- | 0xffcf073a: 0 | ||
- | 0xffcf073b: 0 | ||
- | Ana are mere | ||
- | 1 2 3 4 5 | ||
- | </code> | ||
- | Explicați semnificația fiecărui octet. De ce sunt puși în ordinea respectivă? De ce unii octeti sunt 0? | ||
- | </note> | ||
- | |||
- | <note tip> | ||
- | Amintiți-vă ce valoare au caracterele în reprezentarea zecimală(codul ASCII).\\ | ||
- | Amintiți-vă în ce ordine sunt ținuți octeții unui număr: revedeți secțiunea ''Ordinea de reprezentare a numerelor mai mari de un octet'' din [[https://ocw.cs.pub.ro/courses/iocla/laboratoare/laborator-01|Laboratorul 01]]. | ||
- | </note> | ||
- | |||
- | ==== [1.5p] 4. Variabile locale ==== | ||
- | |||
- | Programul ''merge-arrays.asm'' din cadrul arhivei de laborator, îmbină două array-uri sortate crescător (''array_1'' și ''array_2'') punând array-ul rezultat în ''array_output'' definit în secțiunea ''.data''. | ||
- | |||
- | Modificați programul astfel încat ''array_output'' să fie alocat pe stivă. | ||
- | |||
- | /* | ||
- | |||
- | <note tip> | ||
- | - Alocati spatiu pe stiva pentru array-uri si initializati-le | ||
- | - Aflati offset-ul fiecarui array fata de EBP (EBP - x) | ||
- | - Inlocuiti simbolurile array_1, array_2 etc cu adresele aflate | ||
- | </note> | ||
- | |||
- | */ | ||
- | ==== [2p] 5. BONUS: GCD - Greatest Common Divisor ==== | ||
- | |||
- | Deschideți ''gcd.asm'' și rulați programul. Codul calculează cel mai mare divizor comun dintre două numere date ca și parametru prin registrele eax și edx, și pune valoarea aflată tot în registrul eax. | ||
- | - [**1p**] Faceți modificările necesare astfel încat mesajul de eroare - ''The program crashed!'' - să nu mai apară. | ||
- | - [**1p**] În cadrul label-ului ''print'' afișați rezultatul sub forma: | ||
- | |||
- | <code> | ||
- | gdc(49,28)=7 | ||
- | </code> | ||