Výuka assembleru
6. Zásobník
Každý program při svém spuštění dostane k dispozici speciální paměťovou oblast, tzv. zásobník (angl. stack). Adresa zásobníku je uložena v registru ESP, jehož jméno v češtině znamená "ukazatel na zásobník" (Stack Pointer na 16bitových procesorech, Extended Stack Pointer) na 32bitových. Zásobník se skládá z 32bitových čísel a slouží k:
Velikost zásobníku je nastavena při sestavení programu. Pro programy vytvořené ve Visual C++ je to implicitně 1 MB, což většinou dostačuje. Schéma zásobníku ukazuje obrázek vpravo.
Z obrázku je patrné, že zásobník je jakási "studna", do níž "házíme" 32bitové hodnoty. V registru ESP je uložena adresa na hodnotu, která byla naposled vložena do zásobníku. Část zásobníku "nad" touto adresou (tj. paměť s nižšími adresami) je prázdná; je připravena na vložení dalších hodnot. Část zásobníku "pod" ukazatelem ESP (tedy na vyšších adresách) obsahuje hodnoty uložené dříve. Čím "hlouběji" daná hodnota leží, tím dříve tam byla vložena.
Ukládání do zásobníku probíhá způsobem "ulož hodnotu" (push value); vybírání hodnot ze zásobníku se provádí způsobem "vytáhni hodnotu" (pop value). Pokud uložíme hodnotu do zásobníku, bude tato hodnota uložena vždy na vrchol zásobníku a při vybírání bude také pouze v vrcholu zásobníku vybrána.
Pro ukládání do zásobníku a vybírání ze zásobníku jsou v assembleru dvě instrukce: push pro vložení, pop pro vybrání. Obě instrukce mají jediný operand, který musí být 32bitový - registr nebo adresa v paměti. Instrukce push může jako operand použít i konstantu (u instrukce pop by to samozřejmě nemělo smysl). Příklady intstrukcí ukazuje následující kód:
push 1 pop eax ; EAX bude obsahovat hodnotu 1 push VelikostSouboru pop ebx ; EBX bude obsahovat velikost souboru push edx pop ecx ; ECX bude obsahovat hodnoru registru EDX push eax push ebx pop ecx ; Do ECX bude uložena hodnota registru EBX pop edx ; Do ECX bude uložena hodnota registru EAX pushfd ; Uložíme registr EFLAGS popfd ; Obnovíme registr EFLAGS (jediný způsob, jak jej přímo modifikovat)
Instrukci push je možné napsat i jinak, a to pomocí instrukcí pro práci s pamětí. Jestliže např. instrukce
push eax
sníží ukazatel zásobníku (registr ESP) o 4 byty a uloží na vrchol zásobníku obsah registru EAX, dá se tedy přepsat takto:
sub esp, 4 mov [esp], eax
Podobně můžeme přepsat i instrukci pop eax:
mov eax, [esp] add esp, 4
Rozdíl mezi oběma zápisy je pouze ve velikosti kódu a v délce provádění, instrukce push a pop jsou rychlejší a kratší.
Pro uložení všech osmi registrů (EAX, EBX, ECX, EDX, ESI, EDI, ESP, EBP) můžeme buďto použít posloupnost instrukcí push pro každý registr, můžeme ale použít jednu instrukci, která uloží všechny registry najednou:
pushad ; Uložíme registry v pořadí EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI) popad ; Obnoví obsahy všech registrů
Při prácí se zásobníkem je nutné pamatovat na důležité pravidlo, že pokud do zásobníku uložíme jednu nebo více hodnot, musíme tyto hodnoty buďto zase vybrat, nebo přičíst příslušný počet bytů k registru ESP (každá vložená hodnota znamená 4 byty). Toto obnovení původního obsahu zásobníku musíme provést nejpozději před návratem z funkce (instrukce ret).
push esi ; Uložíme obsahy registrů do zásobníku push edi ; Něco provádíme pop edi ; Obnovíme obsahy registrů pop esi ; (a zároveň i stav zásobníku) retnebo takhle:
push esi ; Uložíme obsahy registrů do zásobníku push edi ; Něco provádíme add esp, 8 ; Obnovíme stav zásobníku, ale ne obsahy registrů ret
Důvodem je instrukce ret, která provádí návrat z funkce na adresu, odkud byla funkce volána. Funkcí se shoduje s pomyslnou instrukcí pop eip (která neexistuje). Při provedení jednak změní hodnotu instrukčního pointeru EIP (která bude vytažena ze zásobníku), a jednak zvýší hodnotu registru ESP o 4. Podívejme se, co provede tento kód:
push eax ret
Do zásobníku vložíme hodnotu registru EAX a provedeme instrukci ret. Uvedený kód je vlastně skok na adresu, která je uložena v registru EAX. Pokud tento skok skutečně chceme provést, je to v pořádku; pokud je to nedopatření, povede k pádu programu (pokud ovšem v registru EAX nebyla čistě náhodou uložena hodnota návratové adresy).
Copyright Ladislav Zezula 2003