De ce este necesar
Pentru a înlocui un teanc are sens atunci când:
- Apelați o funcție care necesită o stivă mare și nu doriți să utilizați funcția curentă.
- Apelați o funcție care necesită o stivă mare atunci când stack-ul dvs. este aproape plin (de exemplu, în același manipulator de preaplin).
Pe procesoare compatibile Intel pe 32 de biți pentru aplicații în modul de utilizare, dimensiunea paginii este de 4096 octeți.
După o scurtă analiză, am aflat că metoda (abordarea) aleasă este corectă, dar implementarea a fost eronată. Am petrecut câteva zile în spatele instrumentului de depanare și am aflat ceva despre cum sistemul organizează stivă. Rezultatele sunt prezentate în atenția dumneavoastră.
Un pic de teorie
Comanda PUSH
Echipa POP
Comanda RETN
De asemenea, acționează ca și pop, dar operandul implicit este registrul de instrucțiuni elip-extended. Cu această comandă puteți modifica conținutul pointerului de comandă, deși nu există instrucțiuni explicite pentru schimbarea acestuia.
Dimensiunea stivei
Ca toate lucrurile bune din această lume, stiva are o limită sau o mărime. Asta înseamnă că nu puteți pune fără sfârșit date în el. Odată ce atinge limita, sistemul va arunca o excepție EXCEPTION_STACK_OVERFLOW. Puțin mai târziu vom lua în considerare toate detaliile acestei operațiuni. În mod implicit, mărimea stivei este de un megabyte. Această valoare poate fi modificată cu opțiunea de legătură "/ STACK". Pentru fiecare fir, sistemul își organizează stiva.
organizarea stivă
În WindowsXP, utilizând steagul STACK_SIZE_PARAM_IS_A_RESERVATION, puteți specifica dimensiunea rezervelor rezervate.
Pentru ultima dintre paginile transferate inițial, este setat flagul PAGE_GUARD. Pe masura ce arborele de apeluri creste, sistemul transfera din ce in ce mai multe pagini din stiva de memorie fizica. Ultima pagină a stiva "normală" nu este transmisă și rămâne întotdeauna rezervată.
Lucrul cu stiva
Ultima pagină transferată a stivei are întotdeauna setul de pavilion PAGE_GUARD.
Pentru o stivă complet umplută, nu este cazul. Ultima pagină transmisă are același atribut ca toate celelalte pagini ale stiva care au fost transferate, și anume PAGE_READWRITE.
Atunci când accesează această pagină, sistemul aruncă o excepție EXCEPTION_GUARD_PAGE.
Dacă apare la accesarea paginilor de stivă, sistemul o procesează singură. În toate celelalte cazuri, excepția este transmisă aplicației. Nu știu la ce nivel sistemul determină că aceasta este o excepție de la stack, dar știu din ce date o fac. Despre asta mai târziu.
Executarea excepției EXCEPTION_GUARD_PAGE elimină atributul PAGE_GUARD de la pagina în care a apărut eroarea și încearcă să trimită pagina următoare. Dacă următoarea pagină este ultima, aceasta nu este trecută și handlerul generează EXCEPTION_STACK_OVERFLOW. Dacă nu este ultima, pagina este transmisă cu PAGE_GUARD și PAGE_READWRITE și devine următoarea pagină de vizionare a stivei de flux.
Astfel, sistemul ar trebui să știe cel puțin trei lucruri despre stack ca structură:
De fapt, sistemul folosește următoarea structură pentru a gestiona stiva:
Nu știu sigur, dar pot să presupun că primele două câmpuri sunt depășite și ignorate. Cel puțin, în [2] și în kernel32.dll la crearea unui flux, acestea sunt resetate.
Bloc de informații despre flux
TIB este o structură care este la începutul unei alte structuri - TEB. TEB - bloc de mediu pentru filet. Descrierea completă a TEB, nu vom lua în considerare, dar structura TIB poate fi dată. Este documentat în NTDDK. Acesta poate fi găsit și în fișierul antet winnt.h.
O funcție foarte simplă și importantă care returnează o referință TIB pentru firul curent.
Acum, înarmat cu teorie, încercați să implementați stivă sau, mai degrabă, încercați să înlocuiți stivele de fire actuale cu propria dvs.
Înlocuirea stivei
Și astfel, stiva este o structură destul de complexă și nu ne putem limita la o actualizare a registrului esp. De ce trebuie să înlocuiți stiva?
Pentru a restabili stiva, trebuie:
- Restaurați valorile vechi ale câmpurilor StackBase și StackLimit ale structurii NT_TIB.
- Recuperați registrul esp.
- Eliberați memoria sub teanc.
Toate lucrările privind înlocuirea și recuperarea stivei au două funcții:
Dimensiunea inițială a stivei este setată la șapte pagini, dintre care ultimul este un watchdog.
Limita stivei este setată la pagina care precede paza.
Salvarea stării anterioare și schimbarea câmpurilor TIB
Iată un extras din funcția SetNewStack. Totul va fi luat în considerare în continuare.
Primele două rânduri stochează valorile anterioare ale câmpurilor StackLimit și StackBase. Apoi, acestea sunt stabilite de noi valori. Acum, de fapt, structura TIB se află într-o stare inconsistentă, deoarece registrul esp nu a fost încă actualizat. Dacă apare orice situație, în cazul în care sistemul are nevoie de informații despre stivă, orice se poate întâmpla. Rețineți că schimbarea contextului nu este o astfel de operație.
Deoarece funcția SetNewStack are doi parametri, stiva arată astfel:
Schimbarea registrului esp și ieșirea din funcția SetNewStack
Dacă ați uitat deja, la compensarea de 0x4 în TIB este baza stack-ului (deja nou). Am pus această valoare în registrele ebp și esp. Următoarele două comenzi sunt destinate să revină din procedură.
Textul complet al funcției SetNewStack
Deoarece accesul la parametrii de intrare este prin intermediul registrului ebp, trebuie să îl ajustăm corespunzător. Prima comandă din registrul ebx este primul parametru. Apoi stocăm valoarea registrului ebp și apoi îl setăm la o valoare din registrul esp, minus patru. De ce? Faptul este că compilatorul generează un prolog standard și un epilog pentru o funcție care arată astfel:
Stivă după prolog arată astfel:
Pentru a avea acces la primul parametru, trebuie să utilizați registrul EBP a crescut cu 8. Dar noi nu suntem în funcția EBP plasat pe stivă, astfel încât [EBP + 8] se va întoarce la al doilea parametru. Pentru a remedia această situație, vom ajusta EBP.