Udostępnij za pośrednictwem


Obsługa wyjątku x64

Omówienie obsługi wyjątków strukturalnych i konwencji kodowania obsługi wyjątków w języku C++ oraz zachowania w architekturze x64. Aby uzyskać ogólne informacje na temat obsługi wyjątków, zobacz Obsługa wyjątków w programie Visual C++.

Odwij dane do obsługi wyjątków, obsługa debugera

Obsługa wyjątków i debugowanie wymaga kilku struktur danych.

struktura RUNTIME_FUNCTION

Obsługa wyjątków opartych na tabelach wymaga wpisu tabeli dla wszystkich funkcji, które przydzielą przestrzeń stosu lub wywołają inną funkcję (na przykład funkcje inne niżleaf). Wpisy tabeli funkcji mają format:

Rozmiar Wartość
ULONG Adres początkowy funkcji
ULONG Adres końcowy funkcji
ULONG Odwij adres informacyjny

Struktura RUNTIME_FUNCTION musi być wyrównana do pamięci DWORD. Wszystkie adresy są względne, czyli są to 32-bitowe przesunięcia od adresu początkowego obrazu zawierającego wpis tabeli funkcji. Te wpisy są sortowane i umieszczane w sekcji pdata obrazu PE32+. W przypadku funkcji generowanych dynamicznie [kompilatory JIT] środowisko uruchomieniowe do obsługi tych funkcji musi używać polecenia RtlInstallFunctionTableCallback lub RtlAddFunctionTable, aby udostępnić te informacje systemowi operacyjnemu. Niepowodzenie w tym celu spowoduje zawodną obsługę wyjątków i debugowanie procesów.

struktura UNWIND_INFO

Struktura informacji o danych unwind służy do rejestrowania efektów, jakie funkcja ma na wskaźniku stosu i gdzie rejestry niewolalne są zapisywane na stosie:

Rozmiar Wartość
UBYTE: 3 Wersja
UBYTE: 5 Flagi
UBYTE Rozmiar prologu
UBYTE Liczba kodów odwijanych
UBYTE: 4 Rejestr ramek
UBYTE: 4 Przesunięcie rejestru ramek (skalowane)
USHORT * n Odwij tablicę kodów
zmienna Może mieć formę (1) lub (2) poniżej

(1) Program obsługi wyjątków

Rozmiar Wartość
ULONG Adres programu obsługi wyjątków
zmienna Dane programu obsługi specyficzne dla języka (opcjonalnie)

(2) Informacje o łańcuchu unwind

Rozmiar Wartość
ULONG Adres początkowy funkcji
ULONG Adres końcowy funkcji
ULONG Odwij adres informacyjny

Struktura UNWIND_INFO musi być wyrównana do pamięci DWORD. Oto, co oznacza każde pole:

  • Wersja

    Numer wersji danych odwijanych, obecnie 1.

  • Flagi

    Obecnie zdefiniowano trzy flagi:

    Flaga opis
    UNW_FLAG_EHANDLER Funkcja ma procedurę obsługi wyjątków, która powinna być wywoływana podczas wyszukiwania funkcji, które muszą badać wyjątki.
    UNW_FLAG_UHANDLER Funkcja ma procedurę obsługi zakończenia, która powinna być wywoływana podczas odwijania wyjątku.
    UNW_FLAG_CHAININFO Ta struktura informacji nie jest podstawową strukturą procedury. Zamiast tego wpis informacji o łańcuchu jest zawartością poprzedniego wpisu RUNTIME_FUNCTION. Aby uzyskać informacje, zobacz Łańcuchowe struktury informacji. Jeśli ta flaga jest ustawiona, należy wyczyścić flagi UNW_FLAG_EHANDLER i UNW_FLAG_UHANDLER. Ponadto pola rejestrowania ramek i alokacji stosu stałego muszą mieć te same wartości co w podstawowych informacjach.
  • Rozmiar prologu

    Długość prologu funkcji w bajtach.

  • Liczba kodów odwijanych

    Liczba miejsc w tablicy kodów odwijanych. Niektóre kody przewijania, na przykład UWOP_SAVE_NONVOL, wymagają więcej niż jednego miejsca w tablicy.

  • Rejestr ramek

    Jeśli wartość niezerowa, funkcja używa wskaźnika ramki (FP), a to pole jest liczbą rejestrów bezwolnych używanych jako wskaźnik ramki, używając tego samego kodowania dla pola informacji o operacji węzłów UNWIND_CODE.

  • Przesunięcie rejestru ramowego (skalowane)

    Jeśli pole rejestru ramek jest niezerowe, to pole jest skalowane przesunięcie z RSP, które jest stosowane do rejestru FP po jego ustanowieniu. Rzeczywisty rejestr FP jest ustawiony na RSP + 16 * tej liczby, co umożliwia przesunięcie z zakresu od 0 do 240. To przesunięcie pozwala wskazać rejestr FP w środku alokacji stosu lokalnego dla dynamicznych ramek stosu, co pozwala na lepszą gęstość kodu dzięki krótszym instrukcjom. (Oznacza to, że więcej instrukcji może używać 8-bitowego formularza przesunięcia z podpisem).

  • Odwij tablicę kodów

    Tablica elementów wyjaśniających wpływ prologu na rejestry niewolacyjne i RSP. Zobacz sekcję dotyczącą UNWIND_CODE znaczenia poszczególnych elementów. W celach wyrównania ta tablica zawsze zawiera parzystą liczbę wpisów, a ostatni wpis jest potencjalnie nieużywany. W takim przypadku tablica jest jedną dłuższą niż wskazywana przez liczbę pól kodów odwijanych.

  • Adres programu obsługi wyjątków

    Wskaźnik względny obrazu do wyjątku specyficznego dla języka funkcji lub procedury obsługi zakończenia, jeśli flaga UNW_FLAG_CHAININFO jest jasna, a jedna z flag UNW_FLAG_EHANDLER lub UNW_FLAG_UHANDLER jest ustawiona.

  • Dane programu obsługi specyficzne dla języka

    Dane obsługi wyjątków specyficzne dla języka funkcji. Format tych danych jest nieokreślony i całkowicie określany przez określoną procedurę obsługi wyjątków w użyciu.

  • Informacje o rozpiętym łańcuchu

    Jeśli ustawiono flagę UNW_FLAG_CHAININFO, struktura UNWIND_INFO kończy się trzema identyfikatorami UWORD. Te identyfikatory UWORD reprezentują RUNTIME_FUNCTION informacje dotyczące funkcji rozprężonego łańcucha.

struktura UNWIND_CODE

Tablica kodu unwind służy do rejestrowania sekwencji operacji w prologu, które mają wpływ na rejestry nonvolatile i RSP. Każdy element kodu ma następujący format:

Rozmiar Wartość
UBYTE Przesunięcie w prologu
UBYTE: 4 Odwij kod operacji
UBYTE: 4 Informacje o operacji

Tablica jest sortowana według malejącej kolejności przesunięcia w prologu.

Przesunięcie w prologu

Przesunięcie (od początku prologu) końca instrukcji, która wykonuje tę operację, plus 1 (czyli przesunięcie początku następnej instrukcji).

Odwij kod operacji

Uwaga: Niektóre kody operacji wymagają niepodpisanego przesunięcia do wartości w ramce stosu lokalnego. To przesunięcie jest od początku, czyli najniższym adresem alokacji stałego stosu. Jeśli pole Rejestr ramek w UNWIND_INFO ma wartość zero, to przesunięcie pochodzi z dostawcy RSP. Jeśli pole Rejestr ramek nie jestzerowe, to przesunięcie pochodzi z lokalizacji RSP, gdy rejestr FP został ustanowiony. Równa się on rejestr FP minus przesunięcie rejestru FP (16 * skalowany rejestr ramowy przesunięcie w UNWIND_INFO). Jeśli jest używany rejestr FP, każdy kod odwijające przesunięcie musi być używany tylko po utworzeniu rejestru FP w prologu.

Dla wszystkich kodów opcode z wyjątkiem UWOP_SAVE_XMM128 i UWOP_SAVE_XMM128_FAR, przesunięcie jest zawsze wielokrotność 8, ponieważ wszystkie wartości stosu zainteresowania są przechowywane na 8-bajtowych granicach (sam stos jest zawsze wyrównany 16 bajtów). W przypadku kodów operacji, które mają krótkie przesunięcie (mniej niż 512K), ostatni element USHORT w węzłach dla tego kodu przechowuje przesunięcie podzielone przez 8. W przypadku kodów operacji, które mają długie przesunięcie (512K <= przesunięcie < 4 GB), dwa ostatnie węzły USHORT dla tego kodu przechowują przesunięcie (w formacie mało endianu).

W przypadku kodów operacyjnych i UWOP_SAVE_XMM128_FAR, przesunięcie jest zawsze wielokrotną 16, ponieważ wszystkie 128-bitowe operacje UWOP_SAVE_XMM128 XMM muszą być wykonywane na 16-bajtowej pamięci wyrównanej. W związku z tym współczynnik skali 16 jest używany do UWOP_SAVE_XMM128, dopuszczając przesunięcia mniejsze niż 1M.

Kod operacji odwijenia jest jedną z następujących wartości:

  • UWOP_PUSH_NONVOL (0) 1 węzeł

    Wypchnij rejestr liczby całkowitej bezvolatile, co spowoduje dekrementację dostawcy RSP przez 8. Informacje o operacji to liczba rejestrów. Ze względu na ograniczenia dotyczące epilogów UWOP_PUSH_NONVOL kody odwijające muszą pojawiać się najpierw w prologu i odpowiednio, ostatnio w tablicy kodu odwijanego. To względne porządkowanie ma zastosowanie do wszystkich innych kodów odwijania z wyjątkiem UWOP_PUSH_MACHFRAME.

  • UWOP_ALLOC_LARGE (1) 2 lub 3 węzły

    Przydziel duży obszar na stosie. Istnieją dwie formy. Jeśli informacje o operacji są równe 0, rozmiar alokacji podzielonej przez 8 jest rejestrowany w następnym miejscu, co pozwala na alokację do 512K - 8. Jeśli informacje o operacji są równe 1, wówczas nieskalowany rozmiar alokacji jest rejestrowany w następnym dwóch miejscach w formacie little-endian, co pozwala na alokacje do 4 GB – 8.

  • UWOP_ALLOC_SMALL (2) 1 węzeł

    Przydziel mały obszar na stosie. Rozmiar alokacji to pole informacji o operacji * 8 + 8, co umożliwia alokacje z 8 do 128 bajtów.

    Kod odwijenia alokacji stosu powinien zawsze używać najkrótszego możliwego kodowania:

    Rozmiar alokacji Odwij kod
    Od 8 do 128 bajtów UWOP_ALLOC_SMALL
    Od 136 do 512K-8 bajtów UWOP_ALLOC_LARGE, informacje o operacji = 0
    Od 512K do 4G-8 bajtów UWOP_ALLOC_LARGE, informacje o operacji = 1
  • UWOP_SET_FPREG (3) 1 węzeł

    Ustanów rejestr wskaźników ramek, ustawiając rejestr na przesunięcie bieżącego dostawcy usług udostępnionych. Przesunięcie jest równe przesunięciom przesunięcia rejestru ramowego (skalowane) w UNWIND_INFO * 16, co umożliwia przesunięcie z zakresu od 0 do 240. Użycie przesunięcia pozwala na ustanowienie wskaźnika ramki wskazującego środek alokacji stałego stosu, co ułatwia gęstość kodu, umożliwiając większą liczbę dostępu do korzystania z krótkich formularzy instrukcji. Pole informacji o operacji jest zarezerwowane i nie powinno być używane.

  • UWOP_SAVE_NONVOL (4) 2 węzły

    Zapisz rejestr liczb całkowitych bezvolatile na stosie przy użyciu formatu MOV zamiast wypychania. Ten kod jest używany głównie do zawijania zmniejszania, w którym rejestr niewolaty jest zapisywany w stosie w pozycji, która została wcześniej przydzielona. Informacje o operacji to liczba rejestrów. Przesunięcie stosu skalowanego po 8 jest rejestrowane w następnym miejscu kodu operacji odwijanej, zgodnie z opisem w powyższej notatce.

  • UWOP_SAVE_NONVOL_FAR (5) 3 węzły

    Zapisz rejestr liczby całkowitej bezvolatile na stosie z długim przesunięciem przy użyciu elementu MOV zamiast wypychania. Ten kod jest używany głównie do zawijania zmniejszania, w którym rejestr niewolaty jest zapisywany w stosie w pozycji, która została wcześniej przydzielona. Informacje o operacji to liczba rejestrów. Przesunięcie stosu nieskalowanego jest rejestrowane w dwóch następnych miejscach kodu operacji odwijanych zgodnie z opisem w powyższej notatce.

  • UWOP_SAVE_XMM128 (8) 2 węzły

    Zapisz wszystkie 128 bitów niewolnego rejestru XMM na stosie. Informacje o operacji to liczba rejestrów. Przesunięcie stosu skalowane według 16 jest rejestrowane w następnym miejscu.

  • UWOP_SAVE_XMM128_FAR (9) 3 węzły

    Zapisz wszystkie 128 bitów niewolnego rejestru XMM na stosie z długim przesunięciem. Informacje o operacji to liczba rejestrów. Przesunięcie stosu nieskalowanego jest rejestrowane w dwóch następnych miejscach.

  • UWOP_PUSH_MACHFRAME (10) 1 węzeł

    Wypychanie ramki maszyny. Ten kod odwijanego służy do rejestrowania wpływu przerwania sprzętu lub wyjątku. Istnieją dwie formy. Jeśli informacje o operacji są równe 0, jedna z tych ramek została wypchnięta na stos:

    Lokalizacja Wartość
    RSP+32 SS
    RSP+24 Stary program RSP
    RSP+16 EFLAGS
    RSP+8 CS
    RSP ZGRYWAĆ

    Jeśli informacje o operacji są równe 1, jedna z tych ramek została wypchnięta:

    Lokalizacja Wartość
    RSP+40 SS
    RSP+32 Stary program RSP
    RSP+24 EFLAGS
    RSP+16 CS
    RSP+8 ZGRYWAĆ
    RSP Kod błędu

    Ten kod odwijania zawsze pojawia się w fikcyjnym prologu, który nigdy nie jest wykonywany, ale zamiast tego pojawia się przed rzeczywistym punktem wejścia procedury przerwania i istnieje tylko w celu zapewnienia miejsca symulowania wypychania ramki maszyny. UWOP_PUSH_MACHFRAME rejestruje tę symulację, która wskazuje, że maszyna wykonała tę operację koncepcyjnie:

    1. Pop RIP zwraca adres od góry stosu do temp

    2. Wypychanie SS

    3. Wypychanie starego dostawcy RSP

    4. Wypychanie EFLAGS

    5. Wypychanie cs

    6. Wypychanie tymczasowe

    7. Kod błędu wypychania (jeśli informacje o operacji są równe 1)

    Symulowana UWOP_PUSH_MACHFRAME operacja dekreuje RSP przez 40 (informacje o operacji równe 0) lub 48 (informacje o operacji są równe 1).

Informacje o operacji

Znaczenie bitów informacji o operacji zależy od kodu operacji. Aby zakodować rejestr ogólnego przeznaczenia (liczba całkowita), to mapowanie jest używane:

Bit Zarejestruj
0 RAX
1 RCX
2 RDX
3 RBX
100 RSP
5 RBP
6 RSI
7 RDI
Od 8 do 15 Od R8 do R15

Struktury informacji rozprężonych łańcuchem

Jeśli flaga UNW_FLAG_CHAININFO jest ustawiona, struktura informacji odwija się jako pomocnicza, a udostępnione pole obsługi wyjątków/sieciowych informacji zawiera podstawowe informacje o odwijeniu. Ten przykładowy kod pobiera podstawowe informacje o odwijeniu przy założeniu, że unwindInfo jest to struktura, która ma ustawioną flagę UNW_FLAG_CHAININFO.

PRUNTIME_FUNCTION primaryUwindInfo = (PRUNTIME_FUNCTION)&(unwindInfo->UnwindCode[( unwindInfo->CountOfCodes + 1 ) & ~1]);

Informacje łańcuchowe są przydatne w dwóch sytuacjach. Najpierw można go używać w przypadku nieciągliwych segmentów kodu. Korzystając z informacji łańcuchowych, można zmniejszyć rozmiar wymaganych informacji odwijanych, ponieważ nie trzeba duplikować tablicy kodów odwijanych z podstawowych informacji odwijanych.

Można również użyć informacji łańcuchowych do grupowania nietrwałych zapisów rejestru. Kompilator może opóźnić zapisywanie niektórych rejestrów lotnych, dopóki nie znajduje się poza dziennikiem wpisu funkcji. Można je zarejestrować, usuwając podstawowe informacje o części funkcji przed pogrupowanym kodem, a następnie konfigurując informacje łańcuchowe z niezerowym rozmiarem prologu, gdzie kody odwijane w łańcuchowych informacjach odzwierciedlają zapisy rejestrów niezauwolonych. W takim przypadku kody odwijenia są wszystkimi wystąpieniami UWOP_SAVE_NONVOL. Grupowanie, które zapisuje niewolne rejestry przy użyciu wypychania lub modyfikuje rejestr RSP przy użyciu dodatkowej alokacji stosu stałego, nie jest obsługiwane.

Element UNWIND_INFO z zestawem UNW_FLAG_CHAININFO może zawierać wpis RUNTIME_FUNCTION, którego element UNWIND_INFO ma również zestaw UNW_FLAG_CHAININFO, czasami nazywany wieloma zawijaniami zmniejszania. W końcu wyczyszczone wskaźniki informacji o łańcuchu docierają do elementu UNWIND_INFO, który UNW_FLAG_CHAININFO wyczyszczone. Ten element jest podstawowym elementem UNWIND_INFO, który wskazuje rzeczywisty punkt wejścia procedury.

Procedura odwijaj

Tablica kodu odwijania jest sortowana w kolejności malejącej. W przypadku wystąpienia wyjątku pełny kontekst jest przechowywany przez system operacyjny w rekordzie kontekstu. Następnie wywoływana jest logika wysyłania wyjątków, która wielokrotnie wykonuje następujące kroki, aby znaleźć procedurę obsługi wyjątków:

  1. Użyj bieżącego pliku RIP przechowywanego w rekordzie kontekstu, aby wyszukać wpis tabeli RUNTIME_FUNCTION opisujący bieżącą funkcję (lub część funkcji dla wpisów UNWIND_INFO łańcuchowych).

  2. Jeśli nie znaleziono wpisu tabeli funkcji, znajduje się on w funkcji liścia, a program RSP bezpośrednio zwraca wskaźnik. Wskaźnik powrotny [RSP] jest przechowywany w zaktualizowanym kontekście, symulowany dostawca RSP jest zwiększany o 8, a krok 1 jest powtarzany.

  3. Jeśli zostanie znaleziony wpis tabeli funkcji, rip może znajdować się w trzech regionach: a) w epilogu, b) w prologu lub c) w kodzie, który może być objęty procedurą obsługi wyjątków.

    • Przypadek a) Jeśli rip znajduje się w epilogu, kontrolka opuszcza funkcję, nie może istnieć procedura obsługi wyjątków skojarzona z tym wyjątkiem dla tej funkcji, a efekty epilogu muszą być nadal obliczane kontekst funkcji wywołującej. Aby ustalić, czy rip znajduje się w epilogu, strumień kodu z rip do wewnątrz jest badany. Jeśli ten strumień kodu może być dopasowany do końcowej części uzasadnionej epilogu, to jest w epilogu, a pozostała część epilogu jest symulowana, z rekordem kontekstu zaktualizowanym podczas przetwarzania każdej instrukcji. Po wykonaniu tego przetwarzania krok 1 zostanie powtórzony.

    • Przypadek b) Jeśli obiekt RIP znajduje się w prologu, kontrolka nie wprowadziła funkcji, nie może istnieć procedura obsługi wyjątków skojarzona z tym wyjątkiem dla tej funkcji, a efekty prologu muszą zostać cofnięte, aby obliczyć kontekst funkcji wywołującej. Plik RIP znajduje się w prologu, jeśli odległość od uruchomienia funkcji do rip jest mniejsza lub równa rozmiarowi prologu zakodowanego w informacjach odwijanych. Efekty prologu są odwoływany przez skanowanie do przodu przez tablicę kodów odwijania dla pierwszego wpisu z przesunięciem mniejszym lub równym przesunięciem RIP od uruchomienia funkcji, a następnie cofnięcie efektu wszystkich pozostałych elementów w tablicy kodu odwijania. Krok 1 jest następnie powtarzany.

    • Przypadek c) Jeśli rip nie znajduje się w prologu lub epilogu, a funkcja ma program obsługi wyjątków (UNW_FLAG_EHANDLER jest ustawiony), wywoływana jest procedura obsługi specyficzna dla języka. Program obsługi skanuje swoje dane i wywołuje odpowiednie funkcje filtrowania. Program obsługi specyficzny dla języka może zwrócić, że wyjątek został obsłużony lub że wyszukiwanie ma być kontynuowane. Może również bezpośrednio zainicjować odwijanie.

  4. Jeśli program obsługi specyficznej dla języka zwraca obsługiwany stan, wykonywanie będzie kontynuowane przy użyciu oryginalnego rekordu kontekstu.

  5. Jeśli nie ma procedury obsługi specyficznej dla języka lub program obsługi zwraca stan "kontynuuj wyszukiwanie", rekord kontekstu musi być odwoływany do stanu obiektu wywołującego. Odbywa się to przez przetwarzanie wszystkich elementów tablicy kodu odwijania, cofanie efektu każdego z nich. Krok 1 jest następnie powtarzany.

Gdy są zaangażowane informacje o łańcuchu odwijań, te podstawowe kroki są nadal wykonywane. Jedyną różnicą jest to, że podczas chodzenia tablicy kodu odwijanego w celu odwijenia efektów prologu, po osiągnięciu końca tablicy następuje połączenie z informacjami o odwijeniu elementu nadrzędnego i cała tablica kodów rozprężenia znaleziona. To łączenie będzie kontynuowane, dopóki nie zostanie wyświetlony komunikat bez flagi UNW_CHAINED_INFO, a następnie kończy przechodzenie do tablicy kodu odwijania.

Najmniejszy zestaw danych odwijanych to 8 bajtów. Będzie to funkcja, która przydzieliła tylko 128 bajtów stosu lub mniej, i prawdopodobnie zapisano jeden rejestr niewolny. Jest to również rozmiar struktury informacji rozprężonych łańcuchowo dla prologu o zerowej długości bez kodów odwijanych.

Procedura obsługi specyficzna dla języka

Względny adres programu obsługi specyficznego dla języka znajduje się w UNWIND_INFO za każdym razem, gdy są ustawione flagi UNW_FLAG_EHANDLER lub UNW_FLAG_UHANDLER. Zgodnie z opisem w poprzedniej sekcji program obsługi specyficzny dla języka jest wywoływany w ramach wyszukiwania procedury obsługi wyjątków lub w ramach odwijenia. Ma ten prototyp:

typedef EXCEPTION_DISPOSITION (*PEXCEPTION_ROUTINE) (
    IN PEXCEPTION_RECORD ExceptionRecord,
    IN ULONG64 EstablisherFrame,
    IN OUT PCONTEXT ContextRecord,
    IN OUT PDISPATCHER_CONTEXT DispatcherContext
);

ExceptionRecord dostarcza wskaźnik do rekordu wyjątku, który ma standardową definicję Win64.

EstablisherFrame to adres podstawy alokacji stałego stosu dla tej funkcji.

ContextRecord wskazuje kontekst wyjątku w momencie zgłoszenia wyjątku (w przypadku procedury obsługi wyjątków) lub bieżącego kontekstu "odwijania" (w przypadku procedury obsługi zakończenia).

DispatcherContext wskazuje kontekst dyspozytora dla tej funkcji. Ma następującą definicję:

typedef struct _DISPATCHER_CONTEXT {
    ULONG64 ControlPc;
    ULONG64 ImageBase;
    PRUNTIME_FUNCTION FunctionEntry;
    ULONG64 EstablisherFrame;
    ULONG64 TargetIp;
    PCONTEXT ContextRecord;
    PEXCEPTION_ROUTINE LanguageHandler;
    PVOID HandlerData;
} DISPATCHER_CONTEXT, *PDISPATCHER_CONTEXT;

ControlPc jest wartością RIP w tej funkcji. Ta wartość jest adresem wyjątku lub adresem, pod którym kontrolka opuściła funkcję ustanawiania. Rip służy do określania, czy kontrolka znajduje się w jakiejś chronionej konstrukcji wewnątrz tej funkcji, na przykład __try bloku dla __try/__except lub __try/__finally.

ImageBase to baza obrazów (adres ładowania) modułu zawierającego tę funkcję, która ma zostać dodana do 32-bitowych przesunięć używanych w wpisie funkcji i odwij informacje o rekordach względnych.

FunkcjaEntry dostarcza wskaźnik do wpisu funkcji RUNTIME_FUNCTION trzymającego funkcję i odwijanie adresów względnych bazy obrazów dla tej funkcji.

EstablisherFrame to adres podstawy alokacji stałego stosu dla tej funkcji.

TargetIp dostarcza opcjonalny adres instrukcji określający adres kontynuacji odwijenia. Ten adres jest ignorowany, jeśli element EstablisherFrame nie jest określony.

ContextRecord wskazuje kontekst wyjątku do użycia przez kod wysyłania/odwijania wyjątku systemu.

Program LanguageHandler wskazuje wywoływaną procedurę obsługi języka specyficzną dla języka.

HandlerData wskazuje dane obsługi specyficzne dla języka dla tej funkcji.

Odwijanie pomocników dla rozwiązania MASM

Aby napisać odpowiednie procedury zestawów, istnieje zestaw pseudooperacyjności, które mogą być używane równolegle z rzeczywistymi instrukcjami zestawu w celu utworzenia odpowiednich .pdata i .xdata. Istnieje również zestaw makr, które zapewniają uproszczone użycie pseudooperacyjności dla ich najczęściej używanych zastosowań.

Nieprzetworzone pseudo-operacje

Pseudo operacja opis
PROC FRAME [:ehandler] Powoduje, że program MASM generuje wpis tabeli funkcji w pliku .pdata i odwija informacje w pliku xdata na potrzeby zachowania odwijania wyjątku ustrukturyzowanego funkcji. Jeśli program obsługi ehandler jest obecny, ten proc jest wprowadzany w pliku .xdata jako program obsługi specyficzny dla języka.

Gdy jest używany atrybut FRAME, musi być zgodny z . ENDPROLOG, dyrektywa. Jeśli funkcja jest funkcją liścia (zgodnie z definicją w typach funkcji), atrybut FRAME jest niepotrzebny, podobnie jak reszta tych pseudo-operacji.
. Rejestracja PUSHREG Generuje wpis kodu UWOP_PUSH_NONVOL odwijanego dla określonego numeru rejestru przy użyciu bieżącego przesunięcia w prologu.

Należy używać go tylko z rejestrami liczb całkowitych bezvolatile. W przypadku wypychania rejestrów lotnych użyj elementu . Zamiast tego ALLOCSTACK 8
. REJESTR SETFRAME, przesunięcie Wypełnia pole rejestru ramki i przesuwa informacje o odwijeniu przy użyciu określonego rejestru i przesunięcia. Przesunięcie musi mieć wielokrotność 16 i mniejsze niż lub równe 240. Ta dyrektywa generuje również UWOP_SET_FPREG wpis kodu odwijanego dla określonego rejestru przy użyciu bieżącego przesunięcia prologu.
. Rozmiar ALLOCSTACK Generuje UWOP_ALLOC_SMALL lub UWOP_ALLOC_LARGE o określonym rozmiarze dla bieżącego przesunięcia w prologu.

Operand rozmiaru musi mieć wielokrotność 8.
. Rejestrowanie SAVEREG, przesunięcie Generuje UWOP_SAVE_NONVOL lub UWOP_SAVE_NONVOL_FAR wpis kodu odwijanego dla określonego rejestru i przesunięcia przy użyciu bieżącego przesunięcia prologu. Rozwiązanie MASM wybiera najbardziej wydajne kodowanie.

przesunięcie musi być dodatnie, a wielokrotność 8. przesunięcie jest względne względem podstawy ramki procedury, która jest zazwyczaj w programie RSP, lub, jeśli używa wskaźnika ramki, nieskalowany wskaźnik ramki.
. SAVEXMM128 rejestr, przesunięcie Generuje UWOP_SAVE_XMM128 lub UWOP_SAVE_XMM128_FAR odwijać wpis kodu dla określonego rejestru I przesunięcia XMM przy użyciu bieżącego przesunięcia prologu. Rozwiązanie MASM wybiera najbardziej wydajne kodowanie.

przesunięcie musi być dodatnie, a wielokrotność 16. przesunięcie jest względne względem podstawy ramki procedury, która jest zazwyczaj w programie RSP, lub, jeśli używa wskaźnika ramki, nieskalowany wskaźnik ramki.
. PUSHFRAME [kod] Generuje UWOP_PUSH_MACHFRAME odwijać wpis kodu. Jeśli określono opcjonalny kod , wpis kodu odwijającego zostanie nadany modyfikatorowi 1. W przeciwnym razie modyfikator ma wartość 0.
.ENDPROLOG Sygnalizuje koniec deklaracji prologu. Musi występować w pierwszych 255 bajtach funkcji.

Oto przykładowy prolog funkcji z odpowiednim użyciem większości kodów operacji:

sample PROC FRAME
    db      048h; emit a REX prefix, to enable hot-patching
    push rbp
    .pushreg rbp
    sub rsp, 040h
    .allocstack 040h
    lea rbp, [rsp+020h]
    .setframe rbp, 020h
    movdqa [rbp], xmm7
    .savexmm128 xmm7, 020h ;the offset is from the base of the frame
                           ;not the scaled offset of the frame
    mov [rbp+018h], rsi
    .savereg rsi, 038h
    mov [rsp+010h], rdi
    .savereg rdi, 010h ; you can still use RSP as the base of the frame
                       ; or any other register you choose
    .endprolog

; you can modify the stack pointer outside of the prologue (similar to alloca)
; because we have a frame pointer.
; if we didn't have a frame pointer, this would be illegal
; if we didn't make this modification,
; there would be no need for a frame pointer

    sub rsp, 060h

; we can unwind from the next AV because of the frame pointer

    mov rax, 0
    mov rax, [rax] ; AV!

; restore the registers that weren't saved with a push
; this isn't part of the official epilog, as described in section 2.5

    movdqa xmm7, [rbp]
    mov rsi, [rbp+018h]
    mov rdi, [rbp-010h]

; Here's the official epilog

    lea rsp, [rbp+020h] ; deallocate both fixed and dynamic portions of the frame
    pop rbp
    ret
sample ENDP

Aby uzyskać więcej informacji na temat przykładu epilogu, zobacz Kod epilogu w prologu x64 i epilogu.

Makra MASM

Aby uprościć korzystanie z pseudooperacji nieprzetworzonych, istnieje zestaw makr zdefiniowanych w ksamd64.inc, które mogą służyć do tworzenia typowych prologów i epilogów procedur.

Makro opis
alloc_stack(n) Przydziela ramkę stosu n bajtów (przy użyciu sub rsp, nmetody ) i emituje odpowiednie informacje odwijania (allocstack n)
save_reg reg, loc Zapisuje rejestr niewolatyle reg na stosie w lokalizacji przesunięcia RSP i emituje odpowiednie informacje o odwijaniu. (.savereg reg, loc)
push_reg reg Wypycha rejestr nievolatile reg na stosie i emituje odpowiednie informacje odwijania. (.pushreg reg)
rex_push_reg reg Zapisuje rejestr nonvolatile na stosie przy użyciu 2-bajtowego wypychania i emituje odpowiednie informacje odwijania (reg.pushreg). Użyj tego makra, jeśli wypychanie jest pierwszą instrukcją w funkcji, aby upewnić się, że funkcja jest poprawna na gorąco.
save_xmm128 reg, loc Zapisuje rejestr XMM nievolatile na stosie w lokalizacji przesunięcia RSP i emituje odpowiednie informacje odwijane (.savexmm128 reg, loc)
set_frame reg, przesunięcie Ustawia reg rejestru ramek jako RSP + przesunięcie (przy użyciu mov, lub lea), i emituje odpowiednie informacje odwijane (.set_frame reg, przesunięcie)
push_eflags Wypycha eflags z instrukcją pushfq i emituje odpowiednie informacje odwijania (.alloc_stack 8)

Oto przykładowy prolog funkcji z odpowiednim użyciem makr:

sampleFrame struct
    Fill     dq ?; fill to 8 mod 16
    SavedRdi dq ?; Saved Register RDI
    SavedRsi dq ?; Saved Register RSI
sampleFrame ends

sample2 PROC FRAME
    alloc_stack(sizeof sampleFrame)
    save_reg rdi, sampleFrame.SavedRdi
    save_reg rsi, sampleFrame.SavedRsi
    .end_prolog

; function body

    mov rsi, sampleFrame.SavedRsi[rsp]
    mov rdi, sampleFrame.SavedRdi[rsp]

; Here's the official epilog

    add rsp, (sizeof sampleFrame)
    ret
sample2 ENDP

Odwij definicje danych w języku C

Oto opis C danych odwijanych:

typedef enum _UNWIND_OP_CODES {
    UWOP_PUSH_NONVOL = 0, /* info == register number */
    UWOP_ALLOC_LARGE,     /* no info, alloc size in next 2 slots */
    UWOP_ALLOC_SMALL,     /* info == size of allocation / 8 - 1 */
    UWOP_SET_FPREG,       /* no info, FP = RSP + UNWIND_INFO.FPRegOffset*16 */
    UWOP_SAVE_NONVOL,     /* info == register number, offset in next slot */
    UWOP_SAVE_NONVOL_FAR, /* info == register number, offset in next 2 slots */
    UWOP_SAVE_XMM128 = 8, /* info == XMM reg number, offset in next slot */
    UWOP_SAVE_XMM128_FAR, /* info == XMM reg number, offset in next 2 slots */
    UWOP_PUSH_MACHFRAME   /* info == 0: no error-code, 1: error-code */
} UNWIND_CODE_OPS;

typedef unsigned char UBYTE;

typedef union _UNWIND_CODE {
    struct {
        UBYTE CodeOffset;
        UBYTE UnwindOp : 4;
        UBYTE OpInfo   : 4;
    };
    USHORT FrameOffset;
} UNWIND_CODE, *PUNWIND_CODE;

#define UNW_FLAG_EHANDLER  0x01
#define UNW_FLAG_UHANDLER  0x02
#define UNW_FLAG_CHAININFO 0x04

typedef struct _UNWIND_INFO {
    UBYTE Version       : 3;
    UBYTE Flags         : 5;
    UBYTE SizeOfProlog;
    UBYTE CountOfCodes;
    UBYTE FrameRegister : 4;
    UBYTE FrameOffset   : 4;
    UNWIND_CODE UnwindCode[1];
/*  UNWIND_CODE MoreUnwindCode[((CountOfCodes + 1) & ~1) - 1];
*   union {
*       OPTIONAL ULONG ExceptionHandler;
*       OPTIONAL ULONG FunctionEntry;
*   };
*   OPTIONAL ULONG ExceptionData[]; */
} UNWIND_INFO, *PUNWIND_INFO;

typedef struct _RUNTIME_FUNCTION {
    ULONG BeginAddress;
    ULONG EndAddress;
    ULONG UnwindData;
} RUNTIME_FUNCTION, *PRUNTIME_FUNCTION;

#define GetUnwindCodeEntry(info, index) \
    ((info)->UnwindCode[index])

#define GetLanguageSpecificDataPtr(info) \
    ((PVOID)&GetUnwindCodeEntry((info),((info)->CountOfCodes + 1) & ~1))

#define GetExceptionHandler(base, info) \
    ((PEXCEPTION_HANDLER)((base) + *(PULONG)GetLanguageSpecificDataPtr(info)))

#define GetChainedFunctionEntry(base, info) \
    ((PRUNTIME_FUNCTION)((base) + *(PULONG)GetLanguageSpecificDataPtr(info)))

#define GetExceptionDataPtr(info) \
    ((PVOID)((PULONG)GetLanguageSpecificData(info) + 1))

Zobacz też

Konwencje kodowania x64