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