20
1 10. NAPREDNE PROCEDURE 10.1 Okviri steka Okvir steka (ili zapis aktivacije) je oblast na steku odvojena za prosleđene argumente, povratne adrese subrutina, lokalne promenljive i sačuvane registre. Okvir steka se kreira po sledećim sekvencijalnim koracima: Ako postoje prosleđeni argumenti, oni se smeštaju na stek. Poziva se subrutina, čime se njena povratna adresa smešta na stek. Čim subrutina počne sa izvršavanjem, EBP se smešta na stek. EBP se izjednačava sa ESP. Od ove tačke, EBP služi 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 sačuvati neke registre, oni se smeštaju na stek. Na strukturu okvira steka direktno utiče memorijski model programa i njegova konvencija za prosleđivanje argumenata. Skoro svi jezici visokog nivoa koriste prosleđivanje 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 prosleđivanje argumenata procedurama. Možemo reći da su procedure koristile registarske parametre. Registarski parametri su optimizovani za brzo izvršavanje programa i lako se koriste. Nažalost, registarski parametri često mogu da naprave zbrku u pozivu programa. Postojeći sadržaj registara često se mora sačuvati pre nego što se u njih mogu učitati vrednosti argumenata. To je slučaj kada se poziva DumpMem procedura iz Irvine32 biblioteke, na primer:

Vezba 10 - Napredne Procedure

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.