Upload
acikamvulovic
View
236
Download
2
Embed Size (px)
DESCRIPTION
Vezba 10 - Napredne Procedure
Citation preview
1
10.
NAPREDNE PROCEDURE
10.1 Okviri steka
Okvir steka (ili zapis aktivacije) je oblast na steku odvojena za prosleene argumente,
povratne adrese subrutina, lokalne promenljive i sauvane registre. Okvir steka se kreira po
sledeim sekvencijalnim koracima:
Ako postoje prosleeni argumenti, oni se smetaju na stek.
Poziva se subrutina, ime se njena povratna adresa smeta na stek.
im subrutina pone sa izvravanjem, EBP se smeta na stek.
EBP se izjednaava sa ESP. Od ove take, EBP slui kao osnovna referenca za sve
parametre subrutine.
Ako postoje lokalne promenljive, ESP se dekrementira kako bi rezervisao prostor
za promenljive na steku.
Ako je potrebno sauvati neke registre, oni se smetaju na stek.
Na strukturu okvira steka direktno utie memorijski model programa i njegova
konvencija za prosleivanje argumenata.
Skoro svi jezici visokog nivoa koriste prosleivanje argumenata na stek. Ako elite da
zovete funkcije u MS-Windows API-ju, na primer, morate proslediti argumente na stek.
10.2 Parametri steka
Sve do sada smo koristili samo registre za prosleivanje argumenata procedurama.
Moemo rei da su procedure koristile registarske parametre. Registarski parametri su
optimizovani za brzo izvravanje programa i lako se koriste. Naalost, registarski parametri
esto mogu da naprave zbrku u pozivu programa. Postojei sadraj registara esto se mora
sauvati pre nego to se u njih mogu uitati vrednosti argumenata. To je sluaj kada se poziva
DumpMem procedura iz Irvine32 biblioteke, na primer:
2
pushad
mov esi,OFFSET array ; poetni OFFSET
mov ecx,LENGTHOF array ; veliina, u jedinicama
mov ebx,TYPE array ; format dvostruke rei
call DumpMem ; prikaz memorije
popad
Parametri steka pruaju fleksibilniji pristup. Neposredno pre poziva subrutine, argumenti
se smetaju na stek. Na primer, ako DumpMem koriste parametre steka, pozvali bismo je
koristei sledei kod:
push TYPE array
push LENGTHOF array
push OFFSET array
call DumpMem
Dve opte vrste argumenata se smetaju na stek tokom poziva subrutine:
Argumenti vrednosti (vrednosti promenljivih i konstanti),
Argumenti referenci (adrese promenljivih).
10.2.1 Prosleivanje preko vrednosti
Kada se argument prosleuje preko vrednosti, kopija vrednosti se smeta na stek.
Pretpostavimo da pozivamo subrutinu AddTwo, prosleujui joj dva 32-bitna cela broja:
.data
val1 DWORD 5
val2 DWORD 6
.code
push val2
push val1
call AddTwo
Sledi prikaz steka neposredno pre CALL instrukcije:
Ekvivalentan poziv funkcije u C++-u bi bio:
int sum = AddTwo( val1, val2 );
Primetite da se argumenti smetaju na stek u obrnutom redosledu, to je karakteristino
za C i C++.
10.2.2 Prosleivanje preko reference
Argument prosleen preko reference se sastoji od adrese (ofseta) objekta. Sledeim
iskazima se poziva Swap i prosleuju se dva argumenta preko referenci:
3
push OFFSET val2
push OFFSET val1
call Swap
Sledi prikaz steka neposredno pre poziva Swap:
Ekvivalentan poziv funkcije u C/C++-u bi prosledio adrese argumenata val1 i val2:
Swap( &val1, &val2 );
10.2.3 Prosleivanje nizova
Jezici visokog nivoa uvek prosleuju nizove subrutinama preko reference. Odnosno, oni
smetaju adresu niza na stek. Subrutina potom uzima adresu sa steka i koristi je da bi pristupila
nizu. Lako je videti zato neko ne bi eleo da prosledi niz preko vrednosti, zato to bi to
zahtevalo da se svaki element niza posebno smesti na stek. Takva operacija bi bila veoma spora
i iskoristila bi eljeni prostor na steku. Sledeim iskazima se prosleivanje niza radi na pravi
nain, prosleivanjem ofseta niza subrutini ArrayFill:
.data
array DWORD 50 DUP(?)
.code
push OFFSET array
call ArrayFill
10.3 Pristupanje parametrima steka
Jezici visokog nivoa imaju razne naine inicijalizacije i pristupanja parametrima tokom
poziva funkcija. Koristiemo C i C++ kao primer. Oni poinju sa prologom, koji se sastoji od
iskaza koji uvaju EBP registar i vre da EBP ukazuje na vrh steka. Opciono, mogu da smeste
odreene registre na stek ije vrednosti e se restaurirati pri povratku iz funkcije. Kraj funkcije
se sastoji od epiloga u kojem se EBP restaurira, a RET instrukcija vraa kontrolu pozivajuoj
funkciji.
AddTwo primer
Sledea AddTwo funkcija, napisana u C-u, prima dva cela broja prosleena preko
vrednosti i vraa njihovu sumu:
int AddTwo( int x, int y )
{
return x + y;
}
4
Kreirajmo ekvivalentnu implementaciju u asembleru. U prologu, AddTwo smeta EBP na
stek kako bi sauvala njegovu trenutnu vrednost:
AddTwo PROC
push ebp
Potom, EBP treba da ukazuje na istu vrednost kao i ESP, tako da EBP moe da bude bazni
pokaziva za okvir steka AddTwo procedure:
AddTwo PROC
push ebp
mov ebp,esp
Nakon izvrenja ove dve instrukcije, sledea slika prikazuje sadraj okvira steka. Poziv
funkcije kao to je AddTwo(5,6) bi smestio najpre drugi parametar na stek, a potom prvi:
Procedura bi mogla da smesti dodatne registre na stek bez menjanja ofseta parametara
steka od EBP. ESP bi promenio vrednost, ali EBP ne bi.
10.3.1 Bazno adresiranje sa ofsetom
Koristiemo bazno adresiranje sa ofsetom da bismo pristupili parametrima steka. EBP je
bazni registar, a ofset je konstanta. EAX obino slui za pamenje 32-bitnih povratnih
vrednosti. Sledea implementacija AddTwo procedure sabira parametre i vraa njihovu sumu u
EAX:
AddTwo PROC
push ebp
mov ebp,esp ; baza okvira steka
mov eax,[ebp + 12] ; drugi parametar
add eax,[ebp + 8] ; prvi parametar
pop ebp
ret
AddTwo ENDP
10.3.2 Eksplicitni parametri steka
Kada se parametri steka referenciraju sa izrazima kao to su [ebp + 8], nazivamo ih
eksplicitnim parametrima steka. Razlog za ovaj naziv je to da asembler eksplicitno uzima ofset
parametra kao konstantnu vrednost. Neki programeri definiu simbolike konstante kako bi
predstavili eksplicitne parametre steka, kako bi njihov kod bio laki za itanje:
5
y_param EQU [ebp + 12]
x_param EQU [ebp + 8]
AddTwo PROC
push ebp
mov ebp,esp
mov eax,y_param
add eax,x_param
pop ebp
ret
AddTwo ENDP
10.3.3 ienje steka
Mora postojati nain da se parametri uklone sa steka kada se vri povratak iz subrutine.
U suprotnom, doi e do curenja memorije, a stek e biti nekorektan. Na primer, pretpostavimo
da se sledeim iskazima u main-u poziva AddTwo:
push 6
push 5
call AddTwo
Pod pretpostavkom da AddTwo ostavlja dva parametra na steku, sledea slika prikazuje
stek nakon povratka iz procedure:
Unutar main-a, moemo pokuati da ignoriemo problem i da se nadamo da e se program
normalno zavriti do kraja. Ali ako bismo pozivali AddTwo iz petlje, moglo bi doi do
prekoraenja steka (stack overflow). Svaki poziv koristi 12 bajtova steka 4 bajta za svaki
parametar plus 4 bajta za povratnu adresu CALL instrukcije. Ozbiljniji problem bi bio ako
bismo pozivali Example1 iz main-a, koji posle poziva AddTwo:
main PROC
call Example1
exit
main ENDP
Example1 PROC
push 6
push 5
call AddTwo
ret ; stek je korumpiran!
Example1 ENDP
Kada treba da se izvri RET instrukcija iz Example1, ESP ukazuje na broj 5 umesto na
povratnu adresu kojom e se vratiti u main:
6
Instrukcija RET uitava vrednost 5 u instrukcijski pokaziva i pokuava da vrati kontrolu
memorijskoj adresi 5. Pod pretpostavkom da je ovo adresa van granica programskog koda,
procesor javlja runtime exception, koji govori operativnom sistemu da prekine rad programa.
10.3.4 C konvencija poziva
Jednostavan nain za uklanjanje parametara sa steka je da se sa ESP sabere vrednost koja
jednaka ukupnoj veliini parametara. Potom, ESP e ukazivati na lokaciju na steku koja sadri
povratnu adresu subrutine. Koristei trenutni primer, nakon CALL emo ubaciti ADD:
Example1 PROC
push 6
push 5
call AddTwo
add esp,8 ; ukloni argumente sa steka
ret
Example1 ENDP
Programi pisani u C/C++-u uvek uklanjaju argumente sa steka na ovaj nain.
10.3.5 STDCALL konvencija poziva
Drugi uobiajen nain za uklanjanje parametara sa steka je korienje STDCALL
konvencije. U sledeoj AddTwo proceduri, RET instrukciji prosleujemo celobrojni parametar,
koji u stvari sabira 8 sa EBP nakon povratka u pozivajuu proceduru. Broj mora biti jednak
broju bajtova na steku koji zauzimaju parametri subrutine:
AddTwo PROC
push ebp
mov ebp,esp ; baza okvira steka
mov eax,[ebp + 12] ; drugi parametar
add eax,[ebp + 8] ; prvi parametar
pop ebp
ret 8 ; obrii stek
AddTwo ENDP
Treba istai da i STDCALL smeta argumente na stek u obrnutom redosledu. Poto RET
ima parametar, STDCALL smanjuje koliinu koda koji se generie za pozive subrutina (za
jednu instrukciju) i osigurava da pozivajui programi nee nikad zaboraviti da obriu stek. Sa
druge strane, C konvencija dozvoljava subrutinama da deklariu promenljivi broj parametara.
Pozivalac moe da odlui koliko argumenata e proslediti. Primer je printf funkcija u C-u, iji
broj argumenata zavisi od broja specifikatora formata u poetnom string argumentu:
7
int x = 5;
float y = 3.2;
char z = 'Z';
printf("Printing values: %d, %f, %c", x, y, z);
C kompajler smeta argumente na stek u obrnutom redosledu, nakon kojih ide argument
koji ukazuje na broj stvarnih argumenata. Funkcija uzima broj argumenata i pristupa im jedan
po jedan. Ne postoji odgovarajui nain da se implementacijom kodira konstanta u RET
instrukciji za ienje steka, tako da je ta odgovornost ostavljena pozivaocu.
Irvine32 biblioteka koristi STDCALL konvenciju kako bi bila kompatibilna sa MS-
Windows API bibliotekom. Od ovog trenutka, podrazumevaemo da se koristi STDCALL
konvencija, osim ako eksplicitno nije drugaije navedeno.
10.3.6 Prosleivanje 8-bitnih i 16-bitnih argumenata na stek
Kada se prosleuju argumenti steka procedurama u zatienom reimu, najbolje je
smetati 32-bitne operande. Iako moete smestiti 16-bitne operande na stek, ipak se time
spreava da ESP bude poravnat na granici dvostruke rei. Moe doi do greke stranienja
(page fault) i performanse se mogu degradirati. Ove parametre treba proiriti na 32 bita pre
smetanja na stek.
Sledea Uppercase procedura prima karakter i vraa njegov ekvivalent u obliku velikog
slova u AL:
Uppercase PROC
push ebp
mov ebp,esp
mov al,[esp+8] ; AL = karakter
cmp al,'a' ; manji od 'a'?
jb L1 ; da: ne radi nita
cmp al,'z' ; vei od 'z'?
ja L1 ; da: ne radi nita
sub al,32 ; ne: konvertuj ga
L1: pop ebp
ret 4 ; obrii stek
Uppercase ENDP
Ako prosledimo karakter Uppercase proceduri, PUSH instrukcija ga automatski proiruje
na 32 bita:
push 'x'
call Uppercase
Meutim, prosleivanje promenljive tipa karaktera zahteva vie panje zato to PUSH ne
dozvoljava 8-bitne operande:
.data
charVal BYTE 'x'
8
.code
push charVal ; sintaksna greka!
call Uppercase
Umesto toga, koristimo MOVZX da bismo proirili karakter u EAX:
movzx eax,charVal ; premetanje sa proirenjem
push eax
call Uppercase
Primer sa 16-bitnim argumentima
Pretpostavimo da elimo da prosledimo dva 16-bitna cela broja AddTwo proceduri.
Procedura oekuje 32-bitne vrednosti, tako da e sledei poziv dovesti do greke:
.data
word1 WORD 1234h
word2 WORD 4111h
.code
push word1
push word2
call AddTwo ; greka!
Umesto toga, moemo proiriti sa nulom svaki argument pre smetanja na stek. Sledei
kod ispravno poziva AddTwo:
movzx eax,word1
push eax
movzx eax,word2
push eax
call AddTwo ; suma je u EAX
Pozivalac procedure mora da osigura da su argumenti koje prosleuje konzistentni sa
parametrima koje procedura oekuje. U sluaju parametara steka, redosled i veliina parametara
su vani!
10.3.7 Prosleivanje argumenata od vie rei
Kada se prosleuju celi brojevi od vie rei proceduri pomou steka, moe se prvo
smestiti vii deo, pa potom se ii do nieg dela. Time se broj smeta na stek u little endian
redosledu. Sledea WriteHex64 procedura prima 64-bitni ceo broj sa steka i prikazuje ga u
heksadecimalnom obliku:
WriteHex64 PROC
push ebp
mov ebp,esp
mov eax,[ebp+12] ; via dvostruka re
call WriteHex
mov eax,[ebp+8] ; nia dvostruka re
call WriteHex
pop ebp
ret 8
WriteHex64 ENDP
9
Pozivom WriteHex64, na stek se smeta gornja polovina promenljive longVal, a potom i
donja polovina:
.data
longVal DQ 1234567800ABCDEFh
.code
push DWORD PTR longVal + 4 ; via dvostruka re
push DWORD PTR longVal ; nia dvostruka re
call WriteHex64
Na slici 1 je prikazan okvir steka unutar WriteHex64 odmah nakon smetanja EBP na stek
i kopiranja ESP u EBP.
Slika 1. Okvir steka nakon smetanja EBP
10.3.8 uvanje i restauracija registara
Subrutine esto uvaju trenutni sadraj registara na steku pre njihove modifikacije tako
da se originalne vrednosti mogu vratiti pre povratka iz subrutine. U idealnom sluaju, ove
registre treba smestiti na stek neposredno nakon instrukcije kojom se ESP smeta u EBP, a
neposredno pre rezervacije prostora za lokalne promenljive. Time se mogu izbei promene
ofseta postojeih parametara steka. Na primer, pretpostavimo da sledea MySub procedura ima
jedan parametar steka. Ona smeta ECX i EDX nakon to se EBP postavlja za bazu steka i
uitavanja parametara steka u EAX:
MySub PROC
push ebp ; sauvaj bazni pokaziva
mov ebp,esp ; baza okvira steka
push ecx
push edx ; sauvaj EDX
mov eax,[ebp+8] ; uzmi parametar steka
.
.
pop edx ; vrati sauvane registre
pop ecx
pop ebp ; vrati bazni pokaziva
ret ; obrii stek
MySub ENDP
Nakon incijalizacije, sadraj EBP registra ostaje nepromenjen tokom subrutine.
Smetanjem ECX i EDX se ne utie na pomeraj parametara koji su ve na steku od EBP, zato
to stek raste ispod EBP (slika 2).
10
Slika 2. Okvir steka MySub procedure
10.3.9 Uticaj USES operatora na stek
Operator USES izlistava imena registara koji se uvaju na poetku procedure, a
restauriraju na kraju. MASM automatski generie PUSH i POP instrukcije za svaki registar.
Meutim, procedure koje referiu parametre koristei konstantne ofsete, kao to je [ebp + 8], bi
trebalo da izbegavaju upotrebu USES operatora. Pogledajmo primer koji pokazuje zato ne
treba. Sledea MySub1 procedura ima USES operator kojim se uvaju ECX i EDX:
MySub1 PROC USES ecx edx
ret
MySub1 ENDP
MASM generie sledei kod:
push ecx
push edx
pop edx
pop ecx
ret
Pretpostavimo da kombinujemo USES sa parametrom steka, kao u sledeoj MySub2
proceduri. Njeni parametri treba da se nalaze na steku na lokaciji EBP + 8:
MySub2 PROC USES ecx edx
push ebp ; sauvaj bazni pokaziva
mov ebp,esp ; baza okvira steka
mov eax,[ebp+8] ; uzmi parametar steka
pop ebp ; vrati bazni pokaziva
ret 4 ; obrii stek
MySub2 ENDP
Sledi kod koji generie MASM:
push ecx
push edx
push ebp
mov ebp,esp
mov eax,dword ptr [ebp+8] ; pogrena lokacija!
pop ebp
pop edx
pop ecx
ret 4
11
Do greke dolazi zato to MASM ubacuje PUSH instrukcije za ECX i EDX na poetku
procedure, ime menja ofset parametra steka. Na slici 3 je prikazano kako se sada mora
referencirati parametar steka kao [EBP + 16]. USES modifikuje stek pre uvanja EBP, ime se
ide suprotno od standardnog koda za prolog procedure.
Slika 3. Okvir steka MySub2 procedure
10.4 Lokalne promenljive
U programima jezika visokog nivoa, promenljive koje se kreiraju, koriste i unitavaju u
jednoj subrutini se nazivaju lokalne promenljive. Lokalna promenljiva ima odreene prednosti
u odnosu na promenljive deklarisane van subrutine:
Samo iskazi unutar subrutine u kojoj je lokalna promenljiva deklarisana mogu da
pristupe ili modifikuju promenljivu. Ova osobina spreava programske greke usled
modifikovanja promenljive sa vie lokacija u kodu.
Memorija potrebna za smetaj promenljive se oslobaa na kraju subrutine.
Lokalna promenljiva moe da ima isto ime kao i lokalna promenljiva u drugoj
subrutini. Ova osobina je korisna u velikim programima kada postoji velika ansa
da dve promenljive imaju isto ime.
Lokalne promenljive su od velike vanosti kada se piu rekurzivne subrutine, kao i
subrutine koje izvrava vie niti.
Lokalne promenljive se kreiraju na steku, obino ispod EBP. Iako im se ne moe dodeliti
podrazumevana vrednost u trenutku asembliranja, mogu se inicijalizovati u trenutku
izvravanja. Moemo kreirati lokalne promenljive u asembleru pomou istih tehnika kao u C i
C++.
Primer
Sledea C++ funkcija deklarie lokalne promenljive X i Y:
void MySub()
{
int X = 10;
int Y = 20;
}
12
Moemo koristiti kompajlirani C++ program kao vodi, pokazujui kako C++ kompajler
alocira lokalne promenljive. Svaki red u steku je podrazumevano 32 bita dugaak, tako da je
memorijski prostor za svaku promenljivu umnoak broja 4. Ukupno 8 bajtova je rezervisano za
dve lokalne promenljive:
Promenljiva Bajtovi Ofset u steku
X 4 EBP 4
Y 4 EBP 8
Sledei asemblerski kod MySub funkcije, kojeg prikazuje debager, pokazuje kako C++
program kreira lokalne promenljive, dodeljuje vrednosti i uklanja promenljive sa steka. Koristi
se C konvencija poziva:
MySub PROC
push ebp
mov ebp,esp
sub esp,8 ; kreiranje lokalnih promenljivih
mov DWORD PTR [ebp-4],10 ; X
mov DWORD PTR [ebp-8],20 ; Y
mov esp,ebp ; uklanjanje lokalnih promenljivih sa steka
pop ebp
ret
MySub ENDP
Na slici 4 je prikazan okvir steka funkcije nakon inicijalizacije lokalnih promenljivih.
Slika 4. Okvir steka nakon kreiranja lokalnih promenljivih
Pre kraja, funkcija resetuje pokaziva steka tako to mu dodeljuje vrednost EBP-a. Time
se lokalne promenljive oslobaaju sa steka:
mov esp,ebp ; uklanjanje lokalnih promenljivih sa steka
Ako se ovaj korak izostavi, POP EBP instrukcija e setovati EBP na 20, a RET instrukcija
e skoiti na memorijsku lokaciju 10, ime e se program zavriti sa grekom. To je sluaj u
sledeoj verziji MySub:
MySub PROC
push ebp
mov ebp,esp
sub esp,8 ; kreiranje lokalnih promenljivih
mov DWORD PTR [ebp-4],10 ; X
mov DWORD PTR [ebp-8],20 ; Y
pop ebp
ret ; povratak na nevaeu adresu!
MySub ENDP
13
10.4.1 Simboli lokalnih promenljivih
Kako bi program bio lak za itanje, moete definisati simbol za svaki ofset lokalne
promenljive i koristiti taj simbol u kodu:
X_local EQU DWORD PTR [ebp-4]
Y_local EQU DWORD PTR [ebp-8]
MySub PROC
push ebp
mov ebp,esp
sub esp,8 ; alociraj prostor za lokalne promenljive
mov X_local,10 ; X
mov Y_local,20 ; Y
mov esp,ebp ; ukloni lokalne promenljive sa steka
pop ebp
ret
MySub ENDP
10.4.2 Pristup parametrima preko referenci
Parametrima koji su reference se obino pristupa koristei bazno adresiranje sa ofsetom
(od EBP). Usled toga to je svaka referenca pokaziva, obino se uitava u registar kako bi se
koristila kao indirektni operand. Pretpostavimo, na primer, da se pokaziva na niz nalazi na
adresi u steku [ebp+12]. Sledeim iskazima se kopira pokaziva u ESI:
mov esi,[ebp+12] ; ukazuje na niz
Primer
Procedura ArrayFill popunjava niz sa pseudosluajnom sekvencom 16-bitnih celih
brojeva. Prima dva argumenta: pokaziva na niz i duinu niza. Prvi se prosleuje preko
reference, a drugi preko vrednosti. Sledi primer poziva:
.data
count = 100
array WORD count DUP(?)
.code
push OFFSET array
push count
call ArrayFill
Unutar ArrayFill procedure, sledei kod prologa inicijalizuje pokaziva okvira steka
(EBP):
ArrayFill PROC
push ebp
mov ebp,esp
Sada, okvir steka sadri ofset niza, broj elemenata, povratnu adresu i sauvani EBP:
14
Procedura uva registre opte namene, uzima parametre i popunjava niz:
ArrayFill PROC
push ebp
mov ebp,esp
pushad ; sauvaj registre
mov ecx,[ebp+8] ; duina niza
cmp ecx,0 ; ECX == 0?
je L2 ; da: iskoi iz petlje
L1:
mov eax,10000h ; uzmi sluajan broj od 0 do FFFFh
call RandomRange ; iz linkerske biblioteke
mov [esi],ax ; ubaci vrednost u niz
add esi,TYPE WORD ; prei na sledei element
loop L1
L2: popad ; vrati registre
pop ebp
ret 8 ; obrii stek
ArrayFill ENDP
10.4.3 Instrukcija LEA
Instrukcija LEA vraa efektivnu adresu indirektnog operanda. Poto indirektni operand
sadri jedan ili vie registara, njihovi ofseti se raunaju u vreme izvravanja. Da bismo pokazali
kako se LEA moe koristiti, pogledajmo sledei C++ program, koji deklarie lokalni niz
karaktera i referencira myString kada pristupa vrednostima niza:
void makeArray( )
{
char myString[30];
for( int i = 0; i < 30; i++ )
myString[i] = '*';
}
Ekvivalentan kod u asembleru alocira prostor za myString na steku i dodeljuje adresu
ESI-ju, indirektnom operandu. Iako je niz samo 30 bajtova duine, ESP se dekrementira sa 32
kako bi se ouvalo poravnanje na granici dvostruke rei. Primetite kako se LEA koristi za
dodelu adrese niza ESI-ju:
makeArray PROC
push ebp
mov ebp,esp
sub esp,32 ; myString is je na EBP-30
lea esi,[ebp-30] ; uitaj adresu od myString
mov ecx,30 ; broja petlje
L1: mov BYTE PTR [esi],'*' ; popuni jednu poziciju
15
inc esi ; prei na sledeu
loop L1 ; nastavi dok ne bude ECX = 0
add esp,32 ; ukloni niz (vrati ESP)
pop ebp
ret
makeArray ENDP
Nije mogue koristiti OFFSET kako bi se dobila adresa parametra steka zato to OFFSET
radi samo sa adresama koje su poznate u vreme kompajliranja. Sledei iskaz se ne bi
asemblirao:
mov esi,OFFSET [ebp-30] ; greka
10.5 Instrukcije ENTER i LEAVE
Instrukcija ENTER automatski kreira okvir steka za pozivanu proceduru. Rezervie
prostor na steku za lokalne promenljive i uva EBP na steku. Obavlja sledee tri radnje:
Smeta EBP na stek (push ebp),
Podeava EBP kao bazu okvira steka (mov ebp, esp),
Rezervie prostor za lokalne promenljive (sub esp, numbytes).
ENTER ima dva operanda:
Prvi je konstanta koja specificira broj bajtova na steku koji treba rezervisati za
lokalne promenljive,
Drugi specificira nivo leksikog ugnjedavanja procedure.
ENTER numbytes, nestinglevel
Oba operanda su neposredne vrednosti. Numbytes se uvek zaokruuje na umnoak broja
4 kako bi ESP bio poravnat na granici dvostruke rei. Nestinglevel odreuje broj pokazivaa na
okvir steka koji se kopiraju u trenutni okvir steka iz okvira steka pozivajue procedure. U naim
programima, nestinglevel je uvek nula.
Primer 1
Sledeim primerom se deklarie procedura koja nema lokalnih promenljivih:
MySub PROC
enter 0,0
Ekvivalentno je sa sledeim instrukcijama:
MySub PROC
push ebp
mov ebp,esp
Primer 2
ENTER rezervie 8 bajtova steka za lokalne promenljive:
MySub PROC
16
enter 8,0
Ekvivalentno je sa sledeim instrukcijama:
MySub PROC
push ebp
mov ebp,esp
sub esp,8
Na slici 5 je prikazan izgled steka pre i nakon izvrenja ENTER instrukcije.
Slika 5. Okvir steka pre i posle izvrenja ENTER instrukcije
Ako se koristi ENTER instrukcija, preporuuje se da se koristi i LEAVE instrukcija na
kraju iste procedure. U suprotnom, prostor na steku koji je kreiran za lokalne promenljive
moda nee biti osloboen.
10.5.1 Instrukcija LEAVE
Instrukcija LEAVE zavrava okvir steka procedure. Radi suprotnom od ENTER
instrukcije tako to vraa EBP i ESP na vrednosti koje su imali kada je procedura bila pozvana.
Koristei MySub, moemo napisati sledee:
MySub PROC
enter 8,0
.
.
leave
ret
MySub ENDP
Sledei ekvivalentan skup instrukcija rezervie i odbacuje 8 bajtova prostora za lokalne
promenljive:
MySub PROC
push ebp
mov ebp,esp
sub esp,8
.
.
mov esp,ebp
pop ebp
ret
MySub ENDP
17
10.5.2 Direktiva LOCAL
Microsoft je kreirao LOCAL direktivu kao zamenu visokog nivoa za ENTER instrukciju.
LOCAL deklarie jednu ili vie lokalnih promenljivih preko imena, dodeljujui im atribute
veliine. Sa druge strane, ENTER samo rezervie jedan neimenovani blok prostora na steku za
lokalne promenljive. Ako se koristi, LOCAL se mora napisati na liniji neposredno posle PROC
direktive. Sintaksa je:
LOCAL varlist
Operand varlist je lista definicija promenljivih, odvojenih sa zarezima, a opciono moe
se prostirati i na vie linija. Svaka definicija promenljive ima sledei oblik:
labela:tip
Labela moe biti bilo koji validan identifikator, a tip moe biti ili standardan (WORD,
DWORD, itd.) ili korisniki definisan.
Primeri
Procedura MySub sadri lokalnu promenljivu pod imenom var1 tipa BYTE:
MySub PROC
LOCAL var1:BYTE
Procedura BubbleSort sadri lokalnu promenljivu pod imenom temp tipa DWORD i
SwapFlag tipa BYTE:
BubbleSort PROC
LOCAL temp:DWORD, SwapFlag:BYTE
Procedura Merge sadri PTR WORD lokalnu promenljivu pod imenom pArray, koja je
pokaziva na 16-bitni ceo broj:
Merge PROC
LOCAL pArray:PTR WORD
Lokalna promenljiva TempArray je niz od 10 dvostrukih rei. Primetite da se koriste
zagrade kako bi se ukazalo na veliinu niza:
LOCAL TempArray[10]:DWORD
10.5.3 MASM generisanje koda
Dobra je ideja pogledati kod koji generie MASM kada se koristi LOCAL direktiva.
Sledea procedura Example1 ima jednu lokalnu DWORD promenljivu:
Example1 PROC
LOCAL temp:DWORD
mov eax,temp
ret
Example1 ENDP
18
MASM generie sledei kod za Example1, pokazujui kako se ESP dekrementira za 4
kako bi se ostavilo prostora za DWORD promenljivu:
push ebp
mov ebp,esp
add esp,0FFFFFFFCh ; saberi -4 sa ESP
mov eax,[ebp-4]
leave
ret
Sledi i slika okvira steka ove procedure:
10.5.4 Lokalne promenljive razliitih veliina od dvostruke rei
LOCAL direktiva ima zanimljivo ponaanje kada se deklariu lokalne promenljive
razliitih veliina. Za svaku se alocira prostor na osnovu njene veliine: 8-bitna promenljiva se
dodeljuje sledeem dostupnom bajtu, 16-bitna promenljiva sledeoj parnoj adresi (poravnatoj
na nivou rei), a 32-bitna sledeoj granici dvostruke rei.
Pogledajmo nekoliko primera. Najpre, procedura Example1 sadri lokalnu promenljivu
var1 tipa BYTE:
Example1 PROC
LOCAL var1:BYTE
mov al,var1 ; [EBP - 1]
ret
Example1 ENDP
Poto su ofseti steka podrazumevano na granicama od 32 bita, moglo bi se oekivati da
e var1 biti locirana na EBP 4. Umesto toga, kao to je prikazano na slici 6, MASM
dekrementira ESP za 4 i smeta var1 na EBP 1, ostavljajui tri bajta ispod neiskoriena
(oznaeno slovima nu, not used). Na slici, svaki blok predstavlja jedan bajt.
Slika 6. Kreiranje prostora za lokalne promenljive u Example1 proceduri
19
Procedura Example2 ima dvostruku re i bajt za lokalne promenljive:
Example2 PROC
LOCAL temp:DWORD, SwapFlag:BYTE
.
.
ret
Example2 ENDP
MASM generie sledei kod. Instrukcija ADD sabira -8 sa ESP, kreirajui prostor u steku
izmeu ESP i EBP za dve lokalne promenljive:
push ebp
mov ebp,esp
add esp,0FFFFFFF8h ; saberi -8 sa ESP
mov eax,[ebp-4] ; temp
mov bl,[ebp-5] ; SwapFlag
leave
ret
SwapFlag je bajt, ESP se zaokruuje na dole na sledeu lokaciju na steku na granici
dvostruke rei. Detaljan prikaz steka na slici 7 pokazuje tanu lokaciju SwapFlag promenljive
i neiskorienog prostora ispod nje (oznaeno sa nu). Na slici, svaki blok je jednak jednom
bajtu.
Slika 7. Kreiranje prostora u Example2 za lokalne promenljive
10.5.5 Rezervisanje dodatnog prostora na steku
Ako planirate da kreirate niz koji je vei od nekoliko stotina bajtova kao lokalnu
promenljivu, morate rezervisati adekvatan prostor na steku, koristei STACK direktivu. U
Irvine32.inc biblioteci, rezervie se 4096 bajtova prostora na steku:
.STACK 4096
Ako su pozivi procedura ugnjedeni, stek mora biti dovoljno veliki kako bi mogao da
smesti sve lokalne promenljive koje su aktivne u nekom trenutku izvrenja programa. Na
20
primer, pretpostavimo da Sub1 poziva Sub2, a Sub2 poziva Sub3. Svaka od njih moe imati niz
kao lokalnu promenljivu:
Sub1 PROC
LOCAL array1[50]:DWORD ; 200 bajtova
.
.
Sub2 PROC
LOCAL array2[80]:WORD ; 160 bajtova
.
.
Sub3 PROC
LOCAL array3[300]:BYTE ; 300 bajtova
Kada program ue u Sub3, stek uva lokalne promenljive od Sub1, Sub2 i Sub3. Stek e
zahtevati 660 bajtova za lokalne promenljive, plus dve povratne adrese procedura (8 bajtova),
plus veliina registara koji su moda smeteni na stek unutar procedura. Ako se procedura
poziva rekurzivno, veliina steka koji koristi e biti priblino jednaka proizvodu veliine njenih
lokalnih promenljivih i parametara sa procenjenom dubinom rekurzije.