Share via


x64 – prolog a epilog

Každá funkce, která přiděluje prostor zásobníku, volá jiné funkce, ukládá nevolatilní registry nebo používá zpracování výjimek, musí mít prolog, jehož limity adres jsou popsány v datech odvíjení přidružených k příslušné položce tabulky funkcí. Další informace najdete v tématu zpracování výjimek x64. Prolog uloží argumenty v případě potřeby do svých domovských adres, vloží do zásobníku nevolatilní registry, přidělí pevnou část zásobníku pro místní a dočasné a volitelně vytvoří ukazatel rámce. Přidružená data odvíjení musí popisovat akci 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ž jedna stránka (tj. větší než 4096 bajtů), je možné, že přidělení zásobníku může zahrnovat více než jednu stránku virtuální paměti, a proto je nutné přidělení zkontrolovat před přidělením. Pro tento účel je k dispozici speciální rutina, která se dá volat z prologu a která nezničí žádný z registrů argumentů.

Upřednostňovanou metodou ukládání nevolatilních registrů je přesunout je do zásobníku před pevným přidělením zásobníku. Pokud se pevné přidělení zásobníku provádí před uložením nevolatilních registrů, je pravděpodobně nutné vyřešit uloženou oblast registru o 32bitovém posunu. (Nahlásilo se, že zápisy registrů jsou stejně rychlé jako pohyby a měly by zůstat v dohledné budoucnosti i přes předpokládané závislosti mezi nabízenými oznámeními.) Nevolatilní registry je možné uložit v libovolném pořadí. Prvním použitím nevolatilního registru v prologu však musí být uložení.

Kód prologu

Kód typického prologu může být následující:

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

Tento prolog uloží RCX registru argumentů do svého domovského umístění, uloží nevolatilní registry R13-R15, přidělí pevnou část rámce zásobníku a vytvoří ukazatel rámce, který odkazuje na 128 bajtů do pevné alokační oblasti. Použití posunu umožňuje adresovat větší část pevné oblasti přidělení pomocí posunů 1 bajtů.

Pokud je pevná velikost přidělení větší nebo rovna jedné stránce paměti, musí být před úpravou SOUBORU RSP volána pomocná funkce. Tato pomocná __chkstkrutina testuje rozsah zásobníku, který se má přidělit, aby se zajistilo správné rozšíření zásobníku. V takovém případě by předchozí příklad prologu byl:

    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ík __chkstk neupraví žádné registry jiné než R10, R11 a kódy podmínek. Konkrétně vrátí RAX beze změny a ponechá všechny nevolatilní registry a registry s předáváním argumentů nezměněné.

Kód epilogu

Kód epilogu existuje na každém výstupu funkce. Zatímco obvykle existuje jen jeden prolog, může existovat mnoho epilogů. Kód epilogu oříznou zásobník na pevnou velikost přidělení (v případě potřeby), uvolní pevné přidělení zásobníku, obnoví nevolatilní registry tak, že v zásobníku přepíná uložené hodnoty a vrátí se.

Kód epilogu musí dodržovat striktní sadu pravidel pro odvíjení kódu, aby spolehlivě odvíjely výjimky a přerušily. Tato pravidla snižují množství požadovaných dat odvíjení, protože k popisu každého epilogu není potřeba žádná další data. Místo toho může unwind kód určit, že se epilog spouští, a to tak, že prohledá přes stream kódu a identifikuje epilog.

Pokud ve funkci není použit žádný ukazatel rámce, musí epilog nejprve uvolnit pevnou část zásobníku, zobrazí se nevolatilní registry a ovládací prvek se vrátí do volající funkce. Příklad:

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

Pokud je ve funkci použit ukazatel rámce, musí být zásobník před spuštěním epilogu oříznut na pevné přidělení. Tato akce není technicky součástí epilogu. Například následující epilog lze použít k vrácení dříve použitého prologu zpět:

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

V praxi platí, že pokud se použije ukazatel na rámec, neexistuje žádný dobrý důvod k úpravě RSP ve dvou krocích, takže by se místo toho použil následující epilog:

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

Tyto formy jsou jediné právní formy pro epilog. Musí se skládat z jedné add RSP,constant nebo lea RSP,constant[FPReg], následované řadou 8 bajtů 8 bajtů a return nebo jmp. (V epilogu lze povolit pouze podmnožinu jmp příkazů. Podmnožina je výhradně třída jmp příkazů s odkazy na paměť ModRM, kde hodnota pole ModRM je 00. Použití jmp příkazů v epilogu s hodnotou pole ModRM mod 01 nebo 10 je zakázáno. Další informace o povolených odkazech ModRM najdete v tabulce A-15 v příručce AMD x86-64 Architecture Programmer's Manual Volume 3: General Purpose and System Instructions.) Nemůže se zobrazit žádný jiný kód. Konkrétně nelze v epilogu naplánovat nic, včetně načtení návratové hodnoty.

Pokud není použit ukazatel rámce, musí epilog použít add RSP,constant k uvolnění pevné části zásobníku. Místo toho se nemusí používat lea RSP,constant[RSP] . Toto omezení existuje, takže při hledání epilogů má méně vzorů, které by bylo potřeba rozpoznat.

Následující pravidla umožňují odvinutí kódu určit, že se aktuálně spouští epilog, a simulovat spuštění zbytku epilogu, aby bylo možné znovu vytvořit kontext volající funkce.

Viz také

x64 – softwarové konvence