Výuka assembleru
10. Zásobníkový rámec a lokální proměnné
Ve vyšších programovacích jazycích je běžné použití proměnných, jejichž platnost je omezena pouze na funkci, ve které jsou deklarovány. Podobné proměnné je možné použít i v assembleru, v této lekci si ukážeme, na jakém principu lokální proměnné fungují.
Z jedné z předchozích lekcí víme, jak funguje zásobník. Pokud program právě běží uvnitř nějaké
funkce, můžeme stav zásobníku znázornit obrázkem vpravo:
Pokud po vstupu do funkce snížíme hodnotu v registru ESP o určitý počet dvojslov, získáme volný prostor o této velikosti. Ten slouží pro ukládání lokálních proměnných. Protože před návratem z funkce musíme ukazatel zásobníku obnovit do původního stavu, lokální proměnné zaniknou.
MyFunction PROC Param1:DWORD,
Param2:PTR
sub esp, 12 ; Snížíme ukazatel zásobníku o 12 bytů (3x4 byty)
; První proměnná bude na adrese [esp+00h], druhá
; na adrese [esp+04h], třetí na [esp+08h]
...
add esp, 12 ; Vrátíme ukazatel zpět na původní pozici.
; (Lokální proměnné tím zaniknou)
ret ; Návrat z funkce
MyFunction ENDP
Obrázek vpravo zobrazuje uspořádání zásobníku během funkce MyFunction. Registr ESP ukazuje na lokální proměnnou uloženou v zásobníku nejvýše.
Menším problémem při používání lokálních proměnných tímto způsobem je jejich adresování. Hodnota v registru ESP se často mění (hlavně kvůli ukládání registrů nebo vkládání parametrů volaných funkcí), a na tyto změny je třeba brát zřetel, což může být např. u větších funkcí obtížné. Pokud v takovémto kódu uděláme chybu, velmi těžko ji pak budeme hledat. Přesto se toto adresování někdy používá, většinou v kódu generovaném automaticky překladačem vyššího programovacího jazyka při zapnutí vhodné volby (Ve Visual C++ je to volba v nastavení "Project\Settings", karta "C/C++", kategorie "Optimizations", volba "Frame-Pointer Ommission").
Potíže s adresováním pomocí registru ESP je možné odstranit postupem nazvaným zásobníkový rámec (angl. stack frame). Na začátku funkce zkopírujeme hodnotu registru ESP do registru EBP, který pak používáme k adresování. Postup ukazuje následující funkce:
MyFunction PROC Param1:DWORD,
Param2:PTR
push ebp ; Uložíme hodnotu EBP do zásobníku
mov ebp, esp ; Zkopírujeme hodnotu registru ESP to EBP
sub esp, 12 ; Snížíme ukazatel zásobníku o 12 bytů (3x4 byty)
; První proměnná bude na adrese [ebp-04h], druhá
; na adrese [ebp-08h], třetí na [ebp-0Ch]
...
mov esp, ebp ; Vrátíme ukazatel zpět na původní pozici.
; (Lokální proměnné tím zaniknou)
pop ebp ; Obnovíme původní hodnotu registru EBP
ret ; Návrat z funkce
MyFunction ENDP
Zásobníkový rámec je vytvářen překladači vyšších programovacích jazyků i překladačem assembleru. Jestliže nadeklarujeme jednu nebo více lokálních proměnných pomocí direktivy LOCAL, zásobníkový rámec je pak vytvořen automaticky:
MyFunction PROC Param1:DWORD,
Param2:PTR
LOCAL dwCount : DWORD ; Lokální proměnná "dwCount" typu DWORD
LOCAL hWin : HWND ; Lokální proměnná "hWin" typu HWND
LOCAL ps : PAINTSTRUCT ; Lokální proměnná "ps" typu PAINSTRUCT
...
ret ; Návrat z funkce
MyFunction ENDP
Překladač automaticky spočítá velikost nadeklarovaných lokálních proměnných a vyhradí pro ně místo v zásobníku. Lokální proměnné můžeme používat stejně jako proměnné v sekci .data. Jedinou výjimkou je získání adresy proměnné, pro kterou musíme použít direktivu ADDR místo direktivy OFFSET. Při použití lokálních proměnných je nutné zvyknout si tato omezení:
Copyright Ladislav Zezula 2004