Î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.
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.
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
:
%include "io.asm" 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 PRINTF32 `%d \n\x0`, eax ; 10 pop eax ; recupereaza valoarea registrului eax PRINTF32 `%d \n\x0`, eax ; 15
2. Adresand memoria, cu ajutorului registrului in care este tinut pointerul catre capul stivei (“stack pointer”) esp
:
%include "io.asm" 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 PRINTF32 `%d \n\x0`, eax mov eax, [esp] ; recupereaza valoarea de pe stiva add esp, 4 ; restabileste valoarea registrului esp PRINTF32 `%d \n\x0`, eax
sub esp, 4
si add esp, 4
. Ce se intampla? De ce?
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
.
Unele procesoare nu au suport pentru lucrul cu stiva: spre exemplu, procesoarele 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:
sub esp, 4 mov [esp], eax
Iar instructiunea pop eax
:
mov eax, [esp] add esp, 4
Spatiul de adresa al unui proces, sau, mai bine spus, spatiul virtual de adresa al unui proces reprezinta zona de memorie virtuala utilizabila de un proces. Fiecare proces are un spatiu de adresa propriu. Chiar in situatiile in care doua procese partajează o zona de memorie, spatiul virtual este distinct, dar se mapeaza peste aceeasi zona de memorie fizica.
In figura alaturata este prezentat un spatiu de adresa tipic pentru un proces.
Cele 4 zone importante din spatiul de adresa al unui proces sunt zona de date, zona de cod, stiva si heap-ul. După cum se observa si din figura, stiva si heap-ul sunt zonele care pot creste. De fapt, aceste doua zone sunt dinamice si au sens doar in contextul unui proces. De partea cealalta, informatiile din zona de date si din zona de cod sunt descrise in executabil.
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
.
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
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:
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
ebp
?
git pull origin master
din interiorul directorului în care se află repository-ul (~/Desktop/iocla
). Recomandarea este să îl actualizați cât mai frecvent, înainte să începeți lucrul, pentru a vă asigura că aveți versiunea cea mai recentă.Dacă doriți să descărcați repository-ul în altă locație, folosiți comanda git clone https://github.com/systems-cs-pub-ro/iocla ${target}
.Pentru mai multe informații despre folosirea utilitarului git
, urmați ghidul de la Git Immersion.
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).
3735
iar media aritmetică a elementelor din vector va fi 287
.
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
.
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.
Reversed array: 911 845 263 242 199 184 122
Programul stack-addressing.asm
din arhiva laboratorului alocă și inițializează două variabile locale pe stivă:
NUM
push
cu o secvență de instrucțiuni echivalentă.
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
Explicați semnificația fiecărui octet. De ce sunt puși în ordinea respectivă? De ce unii octeți sunt 0?
Ordinea de reprezentare a numerelor mai mari de un octet
din Laboratorul 01.
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ă. Alocarea array-ului se face cu instructiunea sub.
Deschideți gcd.asm
și rulați programul. Codul calculează cel mai mare divizor comun dintre două numere date ca parametru prin registrele eax și edx, și pune valoarea calculată tot în registrul eax.
Segmentation fault (core dumped)
- să nu mai apară.print
afișați rezultatul sub forma:gcd(49,28)=7
Soluțiile pentru exerciții sunt disponibile aici.