Sdílet prostřednictvím


Prolog a epilog

Každá funkce, která přiděluje místo v zásobníku, volá jiné funkce, ukládá stálé registry nebo používá zpracování výjimek, musí mít prolog, jehož adresní limity jsou popsány v unwind datech, která jsou přidružená k příslušném záznamu tabulky funkcí (viz Zpracovávání výjimek (x64)). V případě potřeby prolog uloží argument registrů v jejich domovských adresách, přesune stálé registry do zásobníku, přidělí pevnou část zásobníku pro místní a dočasné proměnné a volitelně vytvoří rámcový ukazatel. Přidružená unwind data musí popisovat akce prologu a musí poskytnout informace potřebné k vrácení účinku kódu prologu.

Pokud je pevné přidělení v zásobníku více než na jedné stránce (to znamená, větší než 4096 bajtů), potom je možné, že by se přidělení zásobníku mohlo rozložit na více než jednu stránku virtuální paměti a přidělení proto musí být kontrolována dříve, než je ve skutečnosti přiděleno. Pro tento účel je k dispozici zvláštní rutina, která lze volat z prologu a která nezničí žádný z argumentů registrů.

Upřednostňovanou metodou pro uložení stálých registrů je jejich přesunutí do zásobníku před pevným přidělením zásobníku. Pokud bylo pevné přidělení zásobníku provedeno před tím, než byly stálé registry uloženy, pak by mohl být nejspíš požadován 32bitový posun adresy uložené zaregistrované oblasti (údajně posuny registrů jsou stejně tak rychlé jako přesuny a tak by mělo zůstat v dohledné budoucnosti navzdory předpokládané závislosti mezi posuny). Stálé registry lze uložit v libovolném pořadí. První použití stálého registru v prologu však musí být jeho uložení.

Kód pro typický prolog může být:

mov       [RSP + 8], RCX
push   R15
push   R14
push   R13
sub      RSP, fixed-allocation-size
lea      R13, 128[RSP]
...

Tento prolog ukládá argument registru RCX v jeho domovském umístění, uloží stálé registry R13-R15, přidělí pevnou část bloku zásobníku a stanovuje rámcový ukazatel, který odkazuje 128 bajtů do pevně přidělené oblasti. Použití posunutí umožňuje adresovat většinu pevně přidělených oblastí jedno bajtovými posuny.

Pokud je pevně přidělená velikost větší nebo rovna jedné stránce paměti, pak musí být volána pomocná funkce před změnou RSP. Tato pomocná funkce __chkstk je zodpovědná za zjišťování, zda je přidělený rozsah zásobníku správně rozšířen. V tom případě by namísto předchozího příkladu prologu bylo:

mov       [RSP + 8], RCX
push   R15
push   R14
push   R13
mov      RAX,  fixed-allocation-size
call   __chkstk
sub      RSP, RAX
lea      R13, 128[RSP]
...

Pomocná funkce __chkstk nezmění žádné jiné registry než R10, R11 a kódy stavu. Hlavně vrátí RAX beze změny a ponechá všechny stálé registry a registry pro předávání argumentů nezměněny.

Kód epilogu existuje na konci každé funkce. Zatímco je zde obvykle pouze jeden prolog, může existovat mnoho epilogů. Kód epilogu zkrátí zásobník na jeho pevně přidělenou velikost (pokud je to nutné), dealokuje pevné přidělení zásobníku, obnoví stálé registry tím, že odebere jejich uložené hodnoty ze zásobníku a vrátí je.

Kód epilogu se musí řídit podle přísné sady pravidel pro unwind kód k zajištění spolehlivého unwind prostřednictvím výjimek a přerušení. To snižuje množství potřebných unwind dat, protože žádná další data nejsou potřeba k popisu každého epilogu. Unwind kód místo toho může určit, jestli je epilog prováděný skenováním přesměrovaným skrze kód datového proudu k identifikaci epilogu.

Pokud není ve funkci použit rámcový ukazatel, potom musí epilog nejprve dealokovat pevně určenou část zásobníku, stálé registry jsou odebrány a řízení je vráceno funkci, která jej volala. Příklad:

add      RSP, fixed-allocation-size
pop      R13
pop      R14
pop      R15
ret

Jestliže je ve funkci použit rámcový ukazatel, pak musí být zásobník oříznut na jeho pevně určenou velikost dříve, než dojde k provedení epilogu. Toto není technicky vzato součást epilogu. Například následující epilog by mohl být použít k vrácení prologu, který jsme již dříve použili:

lea      RSP, -128[R13]
; epilogue proper starts here
add      RSP, fixed-allocation-size
pop      R13
pop      R14
pop      R15
ret

V praxi když je použit rámcový ukazatel, pak zde není žádný důvod upravit RSP ve dvou krocích, místo toho by byl použit následující epilog:

lea      RSP, fixed-allocation-size – 128[R13]
pop      R13
pop      R14
pop      R15
ret

Toto jsou jediné platné tvary pro epilog. Musí obsahovat buď add RSP,constant nebo lea RSP,constant[FPReg], následované řadou nul nebo více 8-byte registrů a návrat nebo skok. (Pouze podmnožina skokových příkazů je přípustná v epilogu. Jedná se výhradně o třídu jmps s ModRM pamětí odkazující na umístění, kde ModRM mod hodnota pole 00. Použití jmps v epilogu s ModRM mod hodnota pole 01 nebo 10 je zakázáno. Podívejte se do tabulky A-15 v AMD x86-64 Architecture Programmer’s Manual Volume 3: General Purpose and System Instructions. Další informace získáte o povolených ModRM odkazech.). Žádný další kód se nemůže objevit. Především nic nemůže být naplánováno v rámci epilogu, včetně načítání návratové hodnoty.

Všimněte si, že v případě, kdy není použit rámcový ukazatel, musí epilog použít add RSP,constant chcete-li dealokovat pevně určenou část zásobníku. Nemůže namísto toho použít lea RSP,constant[RSP]. Toto omezení existuje takže unwind kód má méně vzorků k rozpoznání při hledání epilogů.

Dodržování těchto pravidel umožňuje unwind kódu určit, zda je epilog aktuálně prováděný a simulovat provádění zbytku epilogu proto, aby se povolilo obnovení kontextu volající funkce.

Viz také

Odkaz

Úmluvy softwaru x64