Udostępnij za pośrednictwem


Prolog i epilog x64

Każda funkcja, która przydziela przestrzeń stosu, wywołuje inne funkcje, zapisuje rejestry niezawolone lub używa obsługi wyjątków, musi mieć prolog, którego limity adresów są opisane w danych odwijanych skojarzonych z odpowiednim wpisem tabeli funkcji. Aby uzyskać więcej informacji, zobacz obsługa wyjątków x64. Prolog zapisuje rejestry argumentów w swoich adresach domowych w razie potrzeby, wypycha niewolne rejestry na stosie, przydziela stałą część stosu dla ustawień lokalnych i tymczasowych, a opcjonalnie ustanawia wskaźnik ramki. Skojarzone dane odwijające muszą opisywać akcję prologu i muszą podać informacje niezbędne do cofnięcia efektu kodu prologu.

Jeśli stała alokacja w stosie jest większa niż jedna strona (czyli większa niż 4096 bajtów), możliwe jest, że alokacja stosu może obejmować więcej niż jedną stronę pamięci wirtualnej i dlatego należy sprawdzić alokację przed jej przydziałem. Specjalna rutyna, która jest wywoływana z prologu i która nie niszczy żadnego z rejestrów argumentów, jest zapewniana w tym celu.

Preferowaną metodą zapisywania rejestrów niewolnych jest przeniesienie ich na stos przed alokacją stałego stosu. Jeśli alokacja stałego stosu jest wykonywana przed zapisaniem rejestrów niezawolonych, najprawdopodobniej do zapisania zapisanego obszaru rejestru jest wymagane przemieszczanie 32-bitowe. (Podobno wypychania rejestrów są tak szybkie, jak ruchy i powinny pozostać tak w najbliższej przyszłości pomimo domniemanej zależności między wypchnięciami). Rejestry nonvolatile można zapisać w dowolnej kolejności. Jednak pierwszym zastosowaniem rejestru nonvolatile w prologu musi być zapisanie go.

Kod prologu

Kod typowego prologu może być:

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

Ten prolog przechowuje argument rejestru RCX w swojej lokalizacji głównej, zapisuje niewolne rejestry R13-R15, przydziela stałą część ramki stosu i ustanawia wskaźnik ramki, który wskazuje 128 bajtów na stały obszar alokacji. Użycie przesunięcia umożliwia rozwiązanie większej liczby stałych obszarów alokacji przy użyciu przesunięć jedno bajtowych.

Jeśli stały rozmiar alokacji jest większy lub równy jednej stronie pamięci, przed zmodyfikowaniem dostawcy zasobów należy wywołać funkcję pomocnika. Ten pomocnik, __chkstk, sonduje zakres stosu, który ma zostać przydzielony, aby upewnić się, że stos został prawidłowo rozszerzony. W takim przypadku poprzedni przykład prologu będzie:

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

Pomocnik __chkstk nie zmodyfikuje żadnych rejestrów innych niż R10, R11 i kody warunku. W szczególności zwróci ona raX bez zmian i pozostawi wszystkie rejestry niewolalne i przekazujące argumenty rejestry niezmodyfikowane.

Kod epilogu

Kod epilogu istnieje w każdym wyjściu z funkcji. Podczas gdy zwykle istnieje tylko jeden prolog, może istnieć wiele epilogów. Kod epilogu przycina stos do jego stałego rozmiaru alokacji (w razie potrzeby), cofa przydział stałej alokacji stosu, przywraca nieuwolne rejestry, popping ich zapisane wartości ze stosu i zwraca.

Kod epilogu musi przestrzegać ścisłego zestawu reguł dla kodu odwijanego, aby niezawodnie odwinąć się przez wyjątki i przerwania. Te reguły zmniejszają ilość wymaganych danych, ponieważ żadne dodatkowe dane nie są potrzebne do opisania każdego epilogu. Zamiast tego kod odwijania może określić, że epilog jest wykonywany przez skanowanie dalej przez strumień kodu w celu zidentyfikowania epilogu.

Jeśli w funkcji nie jest używany wskaźnik ramki, epilog musi najpierw cofnąć przydział stałej części stosu, rejestry niewolu są zwinięte, a kontrolka jest zwracana do funkcji wywołującej. Na przykład:

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

Jeśli wskaźnik ramki jest używany w funkcji, stos musi zostać przycięty do stałej alokacji przed wykonaniem epilogu. Ta akcja nie jest technicznie częścią epilogu. Na przykład do cofnięcia użytego wcześniej prologu można użyć następującego epilogu:

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

W praktyce, gdy jest używany wskaźnik ramki, nie ma powodu, aby dostosować RSP w dwóch krokach, więc zamiast tego będzie używany następujący epilog:

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

Formularze te są jedynymi prawami dla epilogu. Musi składać się z add RSP,constant typu lub lea RSP,constant[FPReg], po którym następuje seria zera lub więcej 8-bajtowych okienek rejestru i return lub jmp. (Tylko podzbiór instrukcji jmp jest dozwolony w epilogu. Podzestaw jest wyłącznie klasą instrukcji jmp z odwołaniami do pamięci ModRM, gdzie wartość pola modrm mod wynosi 00. Korzystanie z instrukcji jmp w epilogu z modrm mod wartości pola 01 lub 10 jest zabronione. Zobacz tabelę A-15 w podręczniku programisty architektury AMD x86-64: Ogólnego przeznaczenia i instrukcje systemowe, aby uzyskać więcej informacji na temat dozwolonych odwołań modRM. Nie można wyświetlić żadnego innego kodu. W szczególności nie można zaplanować nic w epilogu, w tym ładowanie wartości zwracanej.

Jeśli wskaźnik ramki nie jest używany, epilog musi użyć add RSP,constant do cofnięcia przydziału stałej części stosu. Zamiast tego może nie być używany lea RSP,constant[RSP] . To ograniczenie istnieje, więc kod unwind ma mniej wzorców do rozpoznania podczas wyszukiwania epilogów.

Zgodnie z tymi regułami można cofnąć kod, aby określić, że epilog jest obecnie wykonywany i symulować wykonywanie pozostałej części epilogu, aby umożliwić ponowne utworzenie kontekstu funkcji wywołującej.

Zobacz też

Konwencje kodowania x64