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 języku Microsoft C++.

Odwij dane na potrzeby obsługi wyjątków i obsługi debugera

Aby odzyskać rejestry nieulotne w przypadku obsługi wyjątku, funkcje nieliściaste są oznaczone danymi statycznymi. Te dane, często określane jako „informacje o rozwijaniu stosu funkcji”, opisują, jak prawidłowo rozwijać stos funkcji na poziomie dowolnej instrukcji. Te dane są przechowywane jako dane pdata lub dane procedury, które z kolei odnoszą się do xdata, danych obsługi wyjątków.

Funkcja Unwind Information składa się z kilku struktur danych opisanych w dalszej części.

Aby uzyskać informacje o funkcji Unwind obsługujące rozszerzenia Intel APX (Advanced Performance Extensions), zobacz Specyfikację unwind V3 Preview.

struktura RUNTIME_FUNCTION

Obsługa wyjątków opartych na tabelach wymaga wpisu tabeli dla wszystkich funkcji, które przydzielają przestrzeń stosu lub wywołują inną funkcję (na przykład funkcje niebędące funkcjami liścia). 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ć DWORD wyrównana w pamięci. Wszystkie adresy są względne względem obrazu, innymi słowy, 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 [kompilatorów JIT] środowisko uruchomieniowe obsługujące te funkcje musi albo używać RtlInstallFunctionTableCallback, albo RtlAddFunctionTable, aby udostępnić te informacje systemowi operacyjnemu. Niepowodzenie w tym celu powoduje zawodną obsługę wyjątków i debugowanie procesów.

struktura UNWIND_INFO

Struktura informacji o danych odwijanych rejestruje wpływ funkcji na wskaźnik stosu i miejsce, w którym rejestry nonvolatile są zapisywane na stosie:

Rozmiar Wartość
UBYTE: 3 Wersja
UBYTE: 5 Flagi
UBYTE Rozmiar prologu
UBYTE Liczba kodów zwijania
UBYTE: 4 Rejestr ramek
UBYTE: 4 Przesunięcie rejestru ramki (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ć DWORD wyrównana w pamięci. Oto, co oznacza każde pole:

  • Wersja

    Numer wersji danych rozprzęgania, obecnie 1.

  • Flagi

    Obecnie zdefiniowano trzy flagi:

    Flaga opis
    UNW_FLAG_EHANDLER Funkcja ma program obsługi wyjątków, który system operacyjny wywołuje w celu zbadania stanu wyjątku i potencjalnie go obsłuży. Funkcje językowe, takie jak klauzula C __try , rejestrują taką procedurę obsługi.
    UNW_FLAG_UHANDLER Funkcja ma procedurę obsługi zakończenia, którą system operacyjny wywołuje podczas odwijania stosu. Ta procedura obsługi może zwolnić zasoby przydzielone przez funkcję w bezpiecznym kodzie wyjątku. Funkcje języka, takie jak lokalne destruktory obiektów języka C++ i klauzule języka C __finally , rejestrują taką procedurę obsługi zakończenia.
    UNW_FLAG_CHAININFO Ta struktura informacji nie jest podstawową strukturą procedury. Zamiast tego wpis informacji o łańcuchu jest zawartością poprzedniego RUNTIME_FUNCTION wpisu. Aby uzyskać informacje, zapoznaj się z Łańcuchowymi strukturami danych do rozwijania. Jeśli ta flaga jest ustawiona, należy wyczyścić flagi UNW_FLAG_EHANDLER i UNW_FLAG_UHANDLER. Ponadto pola rejestru ramek i alokacji stosu stałego muszą mieć takie same wartości, jak w podstawowych informacjach dotyczących odwęzłowania.
  • Rozmiar prologu

    Długość prologu funkcji w bajtach.

  • Liczba kodów odwijania

    Liczba slotów w tablicy kodów odwijania. Niektóre kody odwijania, takie jak UWOP_SAVE_NONVOL, wymagają więcej niż jednego elementu w tablicy.

  • Rejestr ramek

    Jeśli wartość jest niezerowa, funkcja używa wskaźnika ramki (FP), a to pole zawiera numer rejestru niewolatilnego używanego jako wskaźnik ramki, przy użyciu tego samego kodowania co dla pola informacji o operacji w węzłach UNWIND_CODE.

  • Przesunięcie rejestru ramowego (skalowane)

    To pole jest przeskalowanym przesunięciem między wartością RSP rejestru a wybraną wartością rejestru wskaźnika ramowego (FP). Wybrany rejestr FP jest ustawiony na RSP + 16 * tej liczby, co oznacza, że można użyć przesunięć z zakresu od 0 do 240. To przesunięcie ustawia rejestr FP na środku obszaru lokalnej alokacji na stosie dla dynamicznych ramek stosu, dzięki czemu można uzyskać większą 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, które wyjaśniają wpływ prologu na rejestry nonvolatile i RSP. Zobacz sekcję dotyczącą kodu operacji odwijania, aby poznać znaczenie poszczególnych elementów. Aby zachować odpowiednie wyrównanie danych, ta tablica zawsze zawiera parzystą liczbę wpisów, a ostateczny wpis może być 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 i 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 danej 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 flaga UNW_FLAG_CHAININFO jest ustawiona, struktura UNWIND_INFO kończy się trzema elementami UWORD. Te UWORD reprezentują informacje RUNTIME_FUNCTION dla funkcji łańcuchowego odwijania.

struktura UNWIND_CODE

Użyj tablicy kodu unwind, aby zarejestrować sekwencję operacji w prologu, które mają wpływ na niewolne rejestry 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

Niektóre kody operacji wymagają niepodpisanego przesunięcia do wartości w ramce stosu lokalnego. To przesunięcie rozpoczyna się od początku, czyli najniższego adresu przydziału stałego stosu. Jeśli pole Frame Register w UNWIND_INFO ma wartość zero, to przesunięcie jest liczone względem RSP. Jeśli pole rejestru ramki jest niezerowe, to to przesunięcie jest liczone od miejsca, w którym znajdował się RSP w chwili ustanowienia rejestru FP. Jest równy rejestrowi FP minus przesunięcie rejestru FP (16 * przeskalowane przesunięcie rejestru ramki w elemencie UNWIND_INFO). Jeśli jest używany rejestr FP, każdy kod rozwijający z przesunięciem musi być używany tylko po ustanowieniu 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ścią 8, ponieważ wszystkie ważne wartości stosu są przechowywane na 8-bajtowych granicach (sam stos jest zawsze wyrównany do 16 bajtów). W przypadku kodów operacyjnych, które przyjmują krótkie przesunięcie (mniejsze niż 512K), końcowy element USHORT w węzłach dla tego kodu zawiera przesunięcie podzielone przez 8. W przypadku kodów operacyjnych, które przyjmują duże przesunięcie (512K <= przesunięcie < 4 GB), ostatnie dwa węzły USHORT dla tego kodu przechowują przesunięcie (w formacie little-endian).

W przypadku kodów operacji UWOP_SAVE_XMM128 i UWOP_SAVE_XMM128_FAR przesunięcie jest zawsze wielokrotnością 16, ponieważ wszystkie 128-bitowe operacje XMM muszą być wykonywane w pamięci wyrównanej do granicy 16 bajtów. W związku z tym do UWOP_SAVE_XMM128 używany jest współczynnik skali 16, co pozwala na 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ę RSP przez 8. Informacja o operacji to numer rejestru. Ze względu na ograniczenia dotyczące epilogów UWOP_PUSH_NONVOL kody odwijania muszą pojawiać się najpierw w prologu i odpowiednio, ostatnie w tablicy kodów odwijania. 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, to rozmiar alokacji podzielony przez 8 jest rejestrowany w następnym slocie, umożliwiając alokację do 512K - 8. Jeśli informacja o operacji jest równa 1, wówczas nieskalowany rozmiar alokacji jest rejestrowany w następnych dwóch miejscach w formacie little-endian, co pozwala na alokacje do 4 GB minus 8.

  • UWOP_ALLOC_SMALL (2) 1 węzeł

    Przydziel niewielki 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ł

    Ustaw rejestr wskaźnika ramki, ustawiając go na wartość będącą przesunięciem względem bieżącego RSP. Przesunięcie jest równe polu przesunięcia rejestru ramowego (skalowanemu) w UNWIND_INFO, pomnożonemu przez 16, co daje zakres przesunięcia od 0 do 240. Użycie przesunięcia pozwala na ustanowienie wskaźnika ramek, wskazującego na środek alokacji stosu stałego, co zwiększa gęstość kodu, umożliwiając większej liczbie dostępów korzystanie z krótkich form instrukcji. Pole informacji o operacji jest zarezerwowane i nie powinno być używane.

  • UWOP_SAVE_NONVOL (4) 2 węzły

    Zapisz nieulotny rejestr liczb całkowitych na stosie, używając instrukcji MOV zamiast PUSH. Ten kod jest używany głównie do owijania kurczowego, gdzie rejestr nieulotny jest zapisywany na stosie w pozycji, która została wcześniej przydzielona. Informacja o operacji to numer rejestru. 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 nieulotny rejestr liczby całkowitej na stosie z dużym przesunięciem przy użyciu MOV zamiast PUSH. Ten kod jest używany głównie do owijania kurczowego, gdzie rejestr nieulotny jest zapisywany na stosie w pozycji, która została wcześniej przydzielona. Informacja o operacji to numer rejestru. Przesunięcie stosu nieskalowanego jest rejestrowane w dwóch następnych slotach kodu operacji odwijania zgodnie z opisem w powyższej notatce.

  • UWOP_SAVE_XMM128 (8) 2 węzły

    Zapisz wszystkie 128 bitów nielotnego rejestru XMM na stosie. Informacja o operacji to numer rejestru. Przesunięcie stosu skalowane przez 16 jest rejestrowane w następnym gnieździe.

  • UWOP_SAVE_XMM128_FAR (9) 3 węzły

    Zapisz wszystkie 128 bitów nieulotnego rejestru XMM na stosie z dużym przesunięciem. Informacja o operacji to numer rejestru. Przesunięcie stosu nieskalowanego jest rejestrowane w dwóch następnych miejscach.

  • UWOP_PUSH_MACHFRAME (10) 1 węzeł

    Przesuń ramę maszyny. Ten kod odwijania rejestruje skutki przerwania sprzętowego lub wyjątku. Ma dwie formy. Wartość 0 wskazuje, że sprzęt umieścił na stosie ramkę taką jak ta:

    Lokalizacja Wartość
    RSP+32 SS
    RSP+24 Stary RSP
    RSP+16 EFLAGS
    RSP+8 CS
    RSP RIP

    Wartość 1 wskazuje, że sprzęt umieścił na stosie ramkę taką jak ta:

    Lokalizacja Wartość
    RSP+40 SS
    RSP+32 Stary RSP
    RSP+24 EFLAGS
    RSP+16 CS
    RSP+8 RIP
    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. Pobierz adres powrotny RIP ze szczytu stosu do Temp

    2. Naciśnij SS

    3. Wypchnij stare RSP

    4. Naciśnij EFLAGS

    5. Naciśnij CS

    6. Push Temp

    7. Kod błędu push (jeśli informacje operacyjne są równe 1)

    Symulowana operacja UWOP_PUSH_MACHFRAME zmniejsza wartość RSP o 40 (jeśli informacja o operacji ma wartość 0) lub o 48 (jeśli informacja o operacji ma wartość 1).

Informacje o operacji

Znaczenie bitów informacji o operacji zależy od kodu operacji. Aby zakodować rejestr ogólnego przeznaczenia, stosuje się to mapowanie:

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

Struktury informacji z odwijania łańcuchowego

Jeśli flaga UNW_FLAG_CHAININFO jest ustawiona, struktura informacji odwijania jest strukturą pomocniczą, a współdzielone pole adresowe programu obsługi wyjątków/informacji łańcuchowej zawiera podstawowe informacje odwijania. Ten przykładowy kod pobiera podstawowe informacje o odwijeniu przy założeniuunwindInfo, że UNW_FLAG_CHAININFO jest to struktura, która zawiera zestaw flag.

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 połączonych informacji, można zmniejszyć rozmiar wymaganych informacji o odwijaniu, ponieważ nie trzeba duplikować tablicy kodów odwijania z podstawowych informacji o odwijaniu.

Można również użyć informacji łańcuchowych do grupowania nietrwałych zapisów rejestru. Kompilator może opóźnić zapisanie niektórych rejestrów ulotnych do momentu wyjścia poza prolog funkcji. Można je zarejestrować, mając podstawowe informacje rozwijania stosu dla części funkcji poprzedzającej zgrupowany kod, a następnie konfigurując informacje łańcuchowe z niezerowym rozmiarem prologu, gdzie kody rozwijania stosu w informacjach łańcuchowych odzwierciedlają zapisy rejestrów nieulotnych. W takim przypadku wszystkie kody rozwijania stosu są wystąpieniami elementu UWOP_SAVE_NONVOL. Grupowanie, które zapisuje rejestry nieulotne przy użyciu PUSH lub modyfikuje rejestr RSP przy użyciu dodatkowej stałej alokacji stosu, nie jest obsługiwane.

Element UNWIND_INFO, dla którego ustawiono UNW_FLAG_CHAININFO, może zawierać wpis RUNTIME_FUNCTION, którego element UNWIND_INFO również ma ustawione UNW_FLAG_CHAININFO, co bywa nazywane multiple shrink-wrapping. Ostatecznie łańcuchowe wskaźniki do informacji unwind prowadzą do elementu UNWIND_INFO, w którym pole UNW_FLAG_CHAININFO jest wyczyszczone. Ten element jest elementem podstawowym UNWIND_INFO , który wskazuje rzeczywisty punkt wejścia procedury.

Procedura odwijania

Tablica kodów odwijania jest uporządkowana w malejącym porządku. W przypadku wystąpienia wyjątku system operacyjny przechowuje pełny kontekst 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żącej wartości RIP przechowywanej w rekordzie kontekstu, aby odszukać wpis w tabeli opisujący bieżącą funkcję RUNTIME_FUNCTION (lub jej część, w przypadku łańcuchowych wpisów UNWIND_INFO).

  2. Jeśli wyszukiwanie nie znajdzie wpisu tabeli funkcji, przyjmuje się, że kod jest częścią funkcji liścia i RSP bezpośrednio adresuje wskaźnik zwracany. Wskaźnik powrotu pod adresem [RSP] jest zapisywany w zaktualizowanym kontekście, symulowany wskaźnik RSP jest inkrementowany o 8, a następnie powtarza się krok 1.

  3. Jeśli wyszukiwanie znajdzie 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 element znajduje się w epilogu, kontrolka opuszcza funkcję. Nie może istnieć program obsługi wyjątków skojarzony z tym wyjątkiem dla tej funkcji. Efekty epilogu muszą nadal wyznaczać kontekst funkcji wywołującej. Aby ustalić, czy element RIP znajduje się w epilogu, analizuje się strumień kodu począwszy od RIP. Jeśli ten strumień kodu pasuje do końcowego fragmentu prawidłowego epilogu, oznacza to, że znajduje się w epilogu. 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 RIP znajduje się w prologu, sterowanie nie weszło jeszcze do funkcji. Nie może istnieć program obsługi wyjątków skojarzony z tym wyjątkiem dla tej funkcji. Efekty prologu należy cofnąć, aby obliczyć kontekst funkcji wywołującej. Element RIP znajduje się w prologu, jeśli odległość od uruchomienia funkcji do RIP wartości jest mniejsza lub równa rozmiarowi prologu zakodowanego w informacjach unwind. Mechanizm odwijania przeszukuje tablicę kodów odwijania w przód w poszukiwaniu pierwszego wpisu o przesunięciu mniejszym lub równym przesunięciu elementu RIP względem początku funkcji, a następnie cofa działanie wszystkich pozostałych elementów w tablicy kodów odwijania. Krok 1 jest następnie powtarzany.
    • Przypadek c) Jeśli element 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 specyficzny 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 właściwej dla danego języka lub procedura obsługi zwraca stan „kontynuuj wyszukiwanie”, rekord kontekstu musi zostać przywrócony do stanu wywołującego. Procedura odwijania odwraca działanie każdego elementu w tablicy kodów odwijania. 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 przechodzenia przez tablicę kodów odwijania w celu cofnięcia skutków prologu, gdy proces dotrze do końca tablicy, przechodzi do nadrzędnych informacji odwijania i przechodzi przez całą znajdującą się tam tablicę kodów odwijania. To łączenie będzie kontynuowane do momentu uzyskania informacji o odwijeniu bez UNW_CHAINED_INFO flagi, a następnie kończy chodzenie po tablicy kodu odwijania.

Najmniejszy zestaw danych odwijanych to 8 bajtów. Taki zestaw reprezentuje funkcję, która alokuje tylko 128 bajtów stosu lub mniej i prawdopodobnie zapisuje jeden rejestr nieulotny. Jest to również rozmiar struktury informacji o rozwijaniu łańcuchowo dla prologu o zerowej długości bez kodów rozwijania.

Mechanizm obsługi specyficzny dla języka

Struktura UNWIND_INFO udostępnia względny adres procedury obsługi specyficznej dla języka, gdy ustawiono flagę UNW_FLAG_EHANDLER lub UNW_FLAG_UHANDLER. Zgodnie z opisem w poprzedniej sekcji wyszukiwanie programu obsługi wyjątków lub proces odwijania wywołuje procedurę obsługi specyficznej dla języka. Funkcja obsługi ma następujący 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 podstawowy adres 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 ramach tej funkcji. Ta wartość jest adresem lokalizacji wyjątku lub adresem, pod którym kontrola opuściła funkcję ustanawiającą. Element RIP służy do określania, czy przepływ sterowania znajduje się w jakiejś konstrukcji chronionej wewnątrz tej funkcji, na przykład w bloku __try dla __try/__except lub __try/__finally.

ImageBase to baza obrazów (adres ładowania) modułu zawierającego tę funkcję. 32-bitowe przesunięcia używane we wpisie funkcji i odwijeniu informacji powinny zostać dodane do bazy obrazów uzyskiwania końcowego adresu.

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

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

TargetIp dostarcza opcjonalny adres instrukcji, który określa adres kontynuacji odwijenia. Ten adres jest ignorowany, jeśli element EstablisherFrame nie jest określony.

ContextRecord wskazuje kontekst wyjątku, używany przez systemowy kod obsługi/odwijania wyjątków.

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

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

Odwijanie pomocników dla rozwiązania MASM

Aby pisać poprawne procedury w asemblerze, należy używać zestawu pseudodyrektyw obok właściwych instrukcji asemblera. Te pseudo-operacje tworzą odpowiednie .pdata i .xdata. Ponadto należy użyć zestawu makr, które upraszczają korzystanie z tych pseudooperacyjności w ich najbardziej typowych zastosowaniach.

Surowe pseudo-operacje

Pseudo operacja opis
PROC FRAME [:ehandler] Powoduje, że MASM generuje wpis tabeli funkcji w .pdata oraz informacje rozwijania stosu w .xdata dotyczące zachowania rozwijania stosu dla obsługi wyjątków strukturalnych funkcji. Jeśli program obsługi ehandler jest obecny, ta procedura jest wprowadzana do pliku .xdata jako program obsługi specyficzny dla języka.

Jeśli używasz atrybutu FRAME, po nim użyj dyrektywy .ENDPROLOG. Jeśli funkcja jest funkcją liścia (zgodnie z definicją w typach funkcji), atrybut FRAME jest niepotrzebny, podobnie jak reszta tych pseudo-operacji.
.PUSHREG rejestr Generuje wpis kodu odwijanego UWOP_PUSH_NONVOL dla określonego numeru rejestru przy użyciu bieżącego przesunięcia w prologu.

Używaj go tylko z nieulotnymi rejestrami liczb całkowitych. W przypadku odkładania rejestrów ulotnych na stos użyj zamiast tego dyrektywy .ALLOCSTACK 8.
.SETFRAME rejestr, 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ż wpis kodu unwind UWOP_SET_FPREG dla określonego rejestru z użyciem bieżącego przesunięcia prologu.
.ALLOCSTACK rozmiar Generuje wartość UWOP_ALLOC_SMALL lub UWOP_ALLOC_LARGE o określonym rozmiarze dla bieżącego przesunięcia w prologu.

Operand rozmiaru musi mieć wielokrotność 8.
.SAVEREG rejestr, przesunięcie Generuje wpis kodu odwijania UWOP_SAVE_NONVOL lub UWOP_SAVE_NONVOL_FAR 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 liczone względem podstawy ramki procedury, która zazwyczaj znajduje się w RSP, lub, jeśli używany jest wskaźnik ramki, względem nieskalowanego wskaźnika ramki.
. SAVEXMM128 rejestr, przesunięcie Generuje wpis kodu odwijania UWOP_SAVE_XMM128 albo UWOP_SAVE_XMM128_FAR dla określonego rejestru XMM i przesunięcia z użyciem bieżącego przesunięcia prologu. Rozwiązanie MASM wybiera najbardziej wydajne kodowanie.

przesunięcie musi być dodatnie i musi być wielokrotnością 16. przesunięcie jest liczone względem bazy ramki procedury, która zwykle znajduje się w RSP, lub — jeśli używany jest wskaźnik ramki — względem nieskalowanego wskaźnika ramki.
.PUSHFRAME [kod] Generuje wpis kodu odwijania UWOP_PUSH_MACHFRAME. Jeśli określisz opcjonalny kod, wpis kodu unwind otrzymuje modyfikator 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 operacyjnych.

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 Epilog code w Prolog i epilog dla x64.

Makra MASM

Aby uprościć korzystanie z surowych pseudooperacji, użyj zestawu makr zdefiniowanych w pliku ksamd64.inc. Te makra ułatwiają tworzenie typowych prologów i epilogów procedur.

Makro opis
alloc_stack(n) Przydziela ramkę stosu o rozmiarze n bajtów (przy użyciu sub rsp, n) i generuje odpowiednie informacje o odwijaniu stosu (.allocstack n)
save_reg reg, loc Zapisuje niewolatylny rejestr reg na stosie pod przesunięciem RSPloc i generuje odpowiednie informacje rozwijania stosu (.savereg reg, loc)
push_reg reg Umieszcza nieulotny rejestr reg na stos i emituje odpowiednie informacje rozwijania stosu (.pushreg reg)
rex_push_reg reg Zapisuje nieulotny rejestr na stosie przy użyciu 2-bajtowej instrukcji push i generuje odpowiednie informacje do odwijania stosu (.pushreg reg). Użyj tego makra, jeśli push jest pierwszą instrukcją w funkcji, aby upewnić się, że funkcja jest podatna na gorące łatanie.
save_xmm128 reg, loc Zapisuje nieulotny rejestr XMMreg na stosie pod RSP przesunięciem loc i emituje odpowiednie informacje rozwijania stosu (.savexmm128 reg, loc)
set_frame reg, przesunięcie Ustawia rejestr ramki reg na wartość RSP + przesunięcia (przy użyciu mov lub lea) i generuje odpowiednie informacje unwind (.set_frame reg, offset)
push_eflags Wypycha eflags przy użyciu pushfq instrukcji 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

Rozwiń definicje danych w języku C

Oto opis C danych dotyczących odwijania:

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