Procedúry deklarujeme pomocou direktívy
PROC. Jej syntax v zjednodušenej podobe
môžeme popísať takto:
meno_procedúry PROC [jazyk]
[uses
registre] [parameter1[,parameter2]…]
Potom nasleduje telo
procedúry. Deklarácia procedúry končí direktívou ENDP:
meno_procedúry ENDP
Meno procedúry je symbolická adresa prvej
inštrukcie v procedúre.
O položkách [jazyk] a [uses] a o parametroch v direktíve
PROC budeme hovoriť neskôr.
call meno_procedúry
(call procedure)
Inštrukcia call
uloží do zásobníka návratovú adresu
a vykoná skok na prvú inštrukciu procedúry. Návratovou adresou je aktuálna hodnota čítača inštrukcií
EIP, t.j. offset inštrukcie, ktorá nasleduje za inštrukciou
call.
Volanie procedúry môže
byť priame a nepriame (podobne ako nepodmienený skok). Pri priamom volaní je
operandom inštrukcie call meno
procedúry, pri nepriamom volaní register alebo pamäťový operand. Inštrukcia call sa prekladá rovnako ako inštrukcia jmp: pri priamom
volaní je operandom displacement, ktorý sa pripočíta k aktuálnej hodnote
čítača inštrukcií.
Návrat z procedúry vykonáva inštrukcia
ret [číslo]
(return from procedure)
Inštrukcia ret
vyberie zo zásobníka
návratovú adresu a uloží ju do registra EIP.
Inštrukcia ret môže mať priamy operand, ktorý sa
po výbere návratovej adresy pripočíta k ukazovateľu zásobníka ESP. To
znamená, že po výbere návratovej adresy sa v zásobníku preskočí toľko
bajtov, koľko udáva operand inštrukcie. Inštrukcia ret
s operandom sa používa vtedy, keď sa do procedúry odovzdávajú
parametre cez zásobník. O parametroch procedúry bude reč onedlho.
Nasleduje krátky program, ktorý ilustruje
použitie inštrukcií call a ret.
Vedľa zdrojového textu je uvedený jeho preklad spolu s adresou inštrukcie Všimnite si, že
operandom inštrukcie call
v preloženom kóde je 0FFFFFFF9h = -7, čo je vzdialenosť medzi inštrukciou nasledujúcou za call a prvou inštrukciou procedúry.
Na obr. 15 zároveň môžete sledovať,
ako sa mení obsah registrov EIP a ESP a obsah zásobníka.
.code
Nic PROC
004033F0 90 nop
004033F1 C3 ret
Nic ENDP
main PROC
004033F2 E8 F9 FF FF FF call Nic
004033F7 33 C0 xor eax,eax
|
|
|
|
Zásobník |
Na začiatku hlavného programu: |
|
|
|
|
|
|
Po call Nic: |
|
|
|
|
|
|
Po nop: |
|
|
|
|
|
|
Po ret: |
|
|
|
|
|
Obr. 15. Registre EIP, ESP a zásobník pri volaní procedúry
- v registroch;
- v zásobníku.
Prvý spôsob sa používa pri čistých
assemblerovských programoch, druhý najmä vtedy, keď je assemblerovský program
volaný z programu vo vyššom jazyku. Samozrejme, nič vám nebráni, aby ste parametre
odovzdávali v zásobníku, aj keď je celý program v JSA.
K odovzdávaniu parametrov v registroch niet čo dodať, preto sa
v ďalšom výklade zameriame na parametre v zásobníku.
Parametre musíme do zásobníka uložiť predtým,
ako vyvoláme procedúru. V procedúre potom parametre sprístupníme pomocou
nepriameho adresovania s použitím bázového registra EBP. Sledujte
nasledujúci príklad:
Úloha
Napíšte procedúru, ktorá ku všetkým prvkom
poľa typu byte pripočíta zadanú hodnotu.
Parametrami procedúry budú:
- adresa poľa
- dĺžka poľa v bajtoch
- číslo, ktoré sa má pripočítať
Riešenie
Zostavíme nielen procedúru, ale aj hlavný
program, aby ste videli celý postup odovzdávania parametrov.
TITLE MASM Procedury
INCLUDE Irvine32.inc
.data
Pole DB 0,1,2,3,4
Dlzka DW $-Pole
Cislo EQU 1
.CODE
Pripocitaj PROC
mov ebp,esp
mov ebx,[ebp+12]; offset
mov ecx,[ebp+8]; Dlzka
mov al,[ebp+4]; Cislo
Cyklus: add [ebx],al
inc ebx
loop Cyklus
ret 12; zo zásobníka vyberie návratovú adresu a parametre (12 bajtov)
Pripocitaj ENDP
main PROC
push offset Pole
push Dlzka
push Cislo
call Pripocitaj
exit
main ENP
END Zac
Na obr. 16 vidíme stav zásobníka po
inštrukcii call Pripocitaj.
|
|
|
ESP |
: |
návratová adresa |
ESP |
+ 4: |
Cislo |
ESP |
+ 8: |
Dlzka |
ESP |
+ 12: |
offset Pole |
|
|
|
Obr. 16. Zásobník po inštrukcii call Pripocitaj
Pri deklarácii procedúry v direktíve PROC môžeme
parametrom procedúry priradiť symbolické mená. Potom sa na parametre môžeme
odvolávať pomocou týchto mien, a nie cez nepriamu adresu s registrom EBP.
Procedúra sa stáva čitateľnejšou a nemusíme si pamätať vzdialenosť parametrov od
vrcholu zásobníka. Tým, že parametrom priradíme symbolické mená, stávajú sa
z nich formálne parametre procedúry. Každému formálnemu
parametru môžeme predpísať typ. Ak neuvedieme typ
parametra, predpokladá sa dword.
Teraz sa dostávame
k špecifikácii jazyka v deklarácii procedúry. Táto informuje
prekladač o poradí ukladania parametrov do zásobníka pri volaní procedúry a o
tom, kto ich zo zásobníka odstráni.
Ak definujeme jazyk Pascal,
Basic alebo Fortran, budú sa
parametre ukladať zľava doprava a prekladač bude generovať inštrukciu
ret so správnym operandom. Okrem toho prekladač automaticky
v procedúre generuje inštrukcie pre nastavenie EBP a ESP. V jazyku C
a Prolog sa parametre ukladajú sprava
doľava a prekladač predpokladá, že parametre odstráni volajúci program. V
32-bitových aplikáciách určených pre operačný systém Windows treba zadať
jazyk stdcall. Táto konvencia je zmiešaninou
C a Pascalu. Parametre sa ukladajú do zásobníka sprava doľava a zo zásobníka
ich odstraňuje volaná procedúra.
Konvenciu jazyka môžeme určiť pri definovaní modelu pamäti na začiatku programu. Táto potom platí pre všetky procedúry a netreba ju definovať v každej procedúre zvlášť. V súbore Irvine32.inc je vložený súbor SmallWin.inc, kde je definovaný model pamäti spolu so špecifikáciou stdcall takto:
.MODEL flat,stdcall
Procedúra Pripocitaj s formálnymi parametrami bude
vyzerať takto:
Pripocitaj PROC paOffset, paDlzka, paCislo:byte
mov ebx,paOffset
mov ecx,paDlzka
mov al,paCislo
Cyklus: add [ebx],al
inc ebx
loop Cyklus
ret
Pripocitaj ENDP
Prekladač automaticky doplní na začiatku procedúry príkazy
push ebp
mov ebp,esp
ktoré slúžia na zachovanie obsahu registra EBP z volajúceho programu. Na konci procedúry automaticky vloží príkazy
leave
ret 0Ch
pre obnovenie regitra EBP a návrat z procedúry do volajúceho programu. Inštrukcia leave vykoná to isté ako
mov esp,ebp
pop ebp
V hlavnom programe sa formálne parametre nahradia skutočnými parametrami takto:
main PROC
INVOKE Pripocitaj, offset Pole, Dlzka, Cislo
exit
main ENDP
Direktíva INVOKE spôsobí, že prekladač vygeneruje príkazy
push 1; konštanta Cislo
push dword ptr ds:[406005h]; premenná Dlzka, 406005h je jej adresa
push 406000h; adresa premennej Pole
call Pripocitaj
Špecifikácia uses
v direktíve PROC definuje registre (maximálne 8
položiek oddelených medzerami), ktorých obsahy budú automaticky na začiatku
procedúry uložené do zásobníka a pred návratom z procedúry opäť
automaticky obnovené, aby sa zachovali ich hodnoty z volajúceho programu.
Ak nie je špecifikovaný jazyk, direktíva uses sa ignoruje.
Procedúra Pripocitaj modifikuje registre EAX, EBX a ECX, preto by jej deklarácia mala vyzerať takto:
Pripocitaj PROC USES eax ebx ecx paOffset, paDlzka, paCislo:byte
mov ebx,paOffset
mov ecx,paDlzka
mov al,paCislo
Cyklus: add [ebx],al
inc ebx
loop Cyklus
ret
Pripocitaj ENDP
Prekladač automaticky doplní na začiatku procedúry príkazy
push ebp
mov ebp,esp
push eax
push ebx
push ecx
a na konci procedúry:
pop ecx
pop ebx
pop eax
leave
ret 0Ch
Procedúry majú často lokálne premenné. Lokálne
premenné sú pamäťové miesta v zásobníku nad návratovou adresou a odloženým
registrom EBP. Miesto na lokálne premenné sa vyhradzuje v procedúre tak, že sa
od ukazovateľa zásobníka ESP odčíta počet bajtov, ktoré lokálne premenné
zaberajú. K lokálnym premenným pristupujeme v procedúre podobne ako
k parametrom (pomocou nepriameho adresovania s registrom EBP), ale
posunutie oproti báze je záporné. Lokálne premenné sa deklarujú pomocou
direktívy
LOCAL premenná1[,premenná2]…[=symbol]
Premennej môžeme
priradiť typ (implicitne dword) a počet
položiek, napr.
LOCAL Sucet:byte
LOCAL Retazec[8]:byte
Úloha
Upravte procedúru Pripocitaj tak, že
v nej spočítate všetky prvky poľa a súčet uložíte do lokálnej premennej Sucet.
Riešenie
Do procedúry doplníme lokálnu premennú
Sucet typu byte.
Pripocitaj PROC USES eax ebx ecx paOffset, paDlzka, paCislo:byte
LOCAL Sucet:byte
mov ebx,paOffset
mov ecx,paDlzka
mov al,paCislo
mov Sucet,0
Cyklus: add [ebx],al
mov ah,[ebx]
add Sucet,ah
inc ebx
loop Cyklus
ret
Pripocitaj ENDP
Prekladač automaticky doplní na začiatku procedúry príkazy
push ebp
mov ebp,esp
add esp,0FFFFFFCh; -4
push eax
push ebx
push ecx
a na konci procedúry:
pop ecx
pop ebx
pop eax
leave
ret 0Ch
Na obr. 17 vidíme stav zásobníka po
inštrukcii add esp,-4.
|
|
miesto, ktoré sa môže používať v procedúre |
ESP |
: |
Sucet |
EBP |
: |
EBP z hl. programu |
EBP |
+ 4: |
návratová adresa |
EBP |
+ 8: |
offset Pole |
EBP |
+ 12: |
Dlzka |
EBP |
+ 16: |
Cislo |
|
|
|
Obr. 17. Zásobník po inštrukcii add esp,-4
Na záver kapitoly o procedúrach upozorňujeme
na dobrý zvyk: komentovať zdrojový
text! V záhlaví každej procedúry by sa mala objaviť informácia o tom,
- čo procedúra robí,
- aké sú vstupné a výstupné parametre
procedúry, vrátane spôsobu ich odovzdávania (v registroch alebo
v zásobníku),
- ktoré registre procedúra mení.
Tieto komentáre vám v prvom
rade uľahčia orientáciu v programe. Oceníte ich však najmä vtedy, keď
budete chcieť neskôr procedúru modifikovať alebo využívať v inom programe.
Bez popisu procedúry by ste znovu prácne museli študovať jej zdrojový text.
Procedúry je nevyhnutné komentovať tiež v prípade, keď ich chcete
poskytnúť inému programátorovi.
Hore