Udostępnij za pośrednictwem


Obsługa wyjątków ARM

System Windows w usłudze ARM używa tego samego mechanizmu obsługi wyjątków ustrukturyzowanych dla asynchronicznych wyjątków generowanych przez sprzęt i synchronicznych wyjątków generowanych przez oprogramowanie. Programy obsługi wyjątków specyficznych dla języka są oparte na obsłudze wyjątków ustrukturyzowanych systemu Windows przy użyciu funkcji pomocnika języka. W tym dokumencie opisano obsługę wyjątków w systemie Windows w usłudze ARM oraz pomocników języka zarówno asemblera usługi Microsoft ARM, jak i kompilatora MSVC.

Obsługa wyjątków ARM

System Windows w usłudze ARM używa kodów odwijania w celu kontrolowania odwijania stosu podczas obsługi wyjątków strukturalnych (SEH). Kody odwijań to sekwencja bajtów przechowywanych w .xdata sekcji obrazu wykonywalnego. Te kody opisują działanie prologu funkcji i kodu epilogu w abstrakcyjny sposób. Procedura obsługi używa ich do cofania efektów prologu funkcji, gdy odwija się do ramki stosu obiektu wywołującego.

Arm EABI (osadzony interfejs binarny aplikacji) określa model odwijania wyjątków, który używa kodów odwijania. Model nie jest wystarczający do odwijania SEH w systemie Windows. Musi obsługiwać asynchroniczne przypadki, w których procesor znajduje się w środku prologu lub epilogu funkcji. System Windows oddziela również odwijanie kontrolki na poziomie funkcji i odwijanie zakresu specyficznego dla języka, co jest ujednolicone w ramach usługi ARM EABI. Z tych powodów system Windows w usłudze ARM określa więcej szczegółów dotyczących odwijania danych i procedury.

Założenia

Obrazy wykonywalne dla systemu Windows w usłudze ARM używają formatu przenośnego pliku wykonywalnego (PE). Aby uzyskać więcej informacji, zobacz Format PE. Informacje o obsłudze wyjątków są przechowywane w .pdata sekcjach i .xdata obrazu.

Mechanizm obsługi wyjątków wprowadza pewne założenia dotyczące kodu zgodnego z usługą ABI dla systemu Windows w usłudze ARM:

  • Gdy wyjątek występuje w treści funkcji, program obsługi może cofnąć operacje prologu lub wykonać operacje epilogu w przyszłości. Oba powinny generować identyczne wyniki.

  • Prologues i epilogues mają tendencję do dublowania siebie nawzajem. Ta funkcja może służyć do zmniejszenia rozmiaru metadanych potrzebnych do opisania odwijania.

  • Funkcje wydają się być stosunkowo małe. Kilka optymalizacji polega na tej obserwacji w celu wydajnego pakowania danych.

  • Jeśli warunek jest umieszczany na epilogu, stosuje się je równie do każdej instrukcji w epilogu.

  • Jeśli prolog zapisze wskaźnik stosu (SP) w innym rejestrze, ten rejestr musi pozostać niezmieniony w całej funkcji, więc oryginalny sp może zostać odzyskany w dowolnym momencie.

  • O ile sp nie zostanie zapisany w innym rejestrze, wszystkie manipulacje muszą wystąpić ściśle w prologu i epilogu.

  • Aby odwinąć dowolną ramkę stosu, wymagane są następujące operacje:

    • Dopasuj r13 (SP) w przyrostach 4-bajtowych.

    • Pokaż co najmniej jedną liczbę całkowitą rejestru.

    • Pokaż co najmniej jeden rejestr VFP (wirtualny zmiennoprzecinkowa).

    • Skopiuj dowolną wartość rejestru do r13 (SP).

    • Załaduj sp ze stosu przy użyciu małej operacji po dekrementacji.

    • Przeanalizuj jeden z kilku dobrze zdefiniowanych typów ramek.

.pdata Rekordy

Rekordy .pdata w obrazie w formacie PE to uporządkowana tablica elementów o stałej długości, które opisują każdą funkcję manipulowania stosem. Funkcje liścia (funkcje, które nie nazywają innych funkcji) nie wymagają .pdata rekordów, gdy nie manipulują stosem. (Oznacza to, że nie wymagają żadnego magazynu lokalnego i nie muszą zapisywać ani przywracać rejestrów nietrwałych). Rekordy dla tych funkcji można pominąć z sekcji, .pdata aby zaoszczędzić miejsce. Operacja odwijenia z jednej z tych funkcji może po prostu skopiować adres zwrotny z rejestru linków (LR) do licznika programu (PC), aby przejść do obiektu wywołującego.

Każdy .pdata rekord usługi ARM ma długość 8 bajtów. Ogólny format rekordu umieszcza względny adres wirtualny (RVA) funkcji rozpoczyna się w pierwszym 32-bitowym słowie, a następnie drugim wyrazem zawierającym wskaźnik do bloku o zmiennej długości .xdata lub spakowanym wyrazem opisujący sekwencję funkcji kanonicznej, jak pokazano w tej tabeli:

Przesunięcie wyrazu Bity Purpose
0 0-31 Function Start RVA jest 32-bitowym RVA początku funkcji. Jeśli funkcja zawiera kod kciuka, należy ustawić niski bit tego adresu.
1 0-1 Flag jest polem 2-bitowym, które wskazuje, jak interpretować pozostałe 30 bitów drugiego .pdata słowa. Jeśli Flag wartość to 0, pozostałe bity tworzą RVA informacji o wyjątku (z małymi dwoma bitami niejawnie 0). Jeśli Flag nie ma wartości zero, pozostałe bity tworzą strukturę Spakowane dane unwind.
1 2-31 Informacje o wyjątku RVA lub pakowane dane unwind.

Informacje o wyjątku RVA to adres struktury informacji o wyjątkach o zmiennej długości przechowywanej .xdata w sekcji. Te dane muszą być wyrównane do 4 bajtów.

Pakowane dane unwind to skompresowany opis operacji wymaganych do odwijania się z funkcji przy założeniu postaci kanonicznej. W takim przypadku nie .xdata jest wymagany żaden rekord.

Spakowane dane odwijaj

W przypadku funkcji, których prologues i epilogues postępuj zgodnie z kanonicznym formularzem opisanym poniżej, można użyć spakowanych danych rozpakowanych. Eliminuje to potrzebę rekordu .xdata i znacznie zmniejsza ilość miejsca wymaganego do dostarczenia danych odwijanych. Canonical prologues i epilogues zostały zaprojektowane tak, aby spełniały typowe wymagania prostej funkcji, która nie wymaga obsługi wyjątków, i wykonuje operacje konfiguracji i usuwania w standardowej kolejności.

W tej tabeli przedstawiono format rekordu zawierającego .pdata pakowane dane:

Przesunięcie wyrazu Bity Purpose
0 0-31 Function Start RVA jest 32-bitowym RVA początku funkcji. Jeśli funkcja zawiera kod kciuka, należy ustawić niski bit tego adresu.
1 0-1 Flag to pole 2-bitowe, które ma następujące znaczenie:

- 00 = nieużytkowane dane unwind; pozostałe bity wskazują .xdata na rekord.
- 01 = spakowane dane unwind.
- 10 = spakowane dane unwind, w których zakłada się, że funkcja nie ma prologu. Jest to przydatne w przypadku opisywania fragmentów funkcji, które są rozbieżne od początku funkcji.
- 11 = zarezerwowane.
1 2-12 Function Length jest polem 11-bitowym, które zapewnia długość całej funkcji w bajtach podzielonych przez 2. Jeśli funkcja jest większa niż 4K bajtów, należy zamiast tego użyć pełnego .xdata rekordu.
1 13-14 Ret jest polem 2-bitowym wskazującym, jak funkcja zwraca:

- 00 = return via pop {pc} (bit flagi L musi być ustawiony na 1 w tym przypadku).
- 01 = zwracany przy użyciu gałęzi 16-bitowej.
- 10 = zwracana przy użyciu gałęzi 32-bitowej.
- 11 = żaden epilog w ogóle. Jest to przydatne do opisywania rozdystrybucyjnego fragmentu funkcji, który może zawierać tylko prolog, ale którego epilog jest gdzie indziej.
1 15 H to flaga 1-bitowa, która wskazuje, czy funkcja "homes" rejestru parametrów całkowitych (r0-r3) przez wypchnięcie ich na początku funkcji i cofnięcie przydziału 16 bajtów stosu przed zwróceniem. (0 = nie rejestruje domu, 1 = rejestry domów).
1 16-18 Reg jest polem 3-bitowym wskazującym indeks ostatniego zapisanego rejestru nietrwałego. R Jeśli bit ma wartość 0, zapisywane są tylko rejestry całkowite i przyjmuje się, że znajdują się w zakresie r4-rN, gdzie N jest równe 4 + Reg. R Jeśli bit ma wartość 1, zapisywane są tylko rejestry zmiennoprzecinkowe i zakłada się, że znajdują się w zakresie d8-dN, gdzie N jest równe 8 + Reg. Specjalna kombinacja R = 1 i Reg = 7 wskazuje, że nie zapisano żadnych rejestrów.
1 19 R to flaga 1-bitowa wskazująca, czy zapisane rejestry nietrwałe są rejestrami całkowitymi (0) lub rejestrami zmiennoprzecinkowych (1). Jeśli R ustawiono wartość 1, a Reg pole ma wartość 7, nie zostały wypchnięte żadne rejestry nietrwałe.
1 20 L to flaga 1-bitowa wskazująca, czy funkcja zapisuje/przywraca LR, wraz z innymi rejestrami wskazanymi przez Reg pole. (0 = nie zapisuje/przywraca, 1 = zapisuje/przywraca).
1 21 C to flaga 1-bitowa wskazująca, czy funkcja zawiera dodatkowe instrukcje dotyczące konfigurowania łańcucha ramek na potrzeby szybkiego chodzenia stosu (1) lub nie (0). Jeśli ten bit jest ustawiony, r11 jest niejawnie dodawany do listy zapisanych rejestrów nietrwałych liczb całkowitych. (Zobacz ograniczenia poniżej, jeśli flaga C jest używana).
1 22-31 Stack Adjust jest polem 10-bitowym, które wskazuje liczbę bajtów stosu przydzielonych dla tej funkcji, podzieloną przez 4. Jednak tylko wartości między 0x000-0x3F3 mogą być zakodowane bezpośrednio. Funkcje przydzielające więcej niż 4044 bajty stosu muszą używać pełnego .xdata rekordu. Stack Adjust Jeśli pole jest 0x3F4 lub większe, małe 4 bity mają specjalne znaczenie:

- Bity 0-1 wskazują liczbę wyrazów korekty stosu (1–4) minus 1.
- Bit 2 jest ustawiony na 1, jeśli prolog połączył to dostosowanie do operacji wypychania.
- Bit 3 jest ustawiony na 1, jeśli epilog połączył tę korektę w jego operacji pop.

Ze względu na możliwe nadmiarowości w powyższych kodowaniach obowiązują następujące ograniczenia:

  • Jeśli flaga jest ustawiona C na 1:

    • Flaga L musi być również ustawiona na 1, ponieważ łączenie ramek wymaga zarówno r11, jak i LR.

    • R11 nie może być uwzględniony w zestawie rejestrów opisanych przez Reg. Oznacza to, że jeśli r4-r11 są wypychane, Reg należy opisać tylko r4-r10, ponieważ flaga C oznacza r11.

  • Ret Jeśli pole ma wartość 0, flaga musi być ustawiona L na 1.

Naruszenie tych ograniczeń powoduje nieobsługiwaną sekwencję.

Na potrzeby poniższej dyskusji dwa pseudo-flagi pochodzą z Stack Adjust:

  • PF lub "składanie prologu" wskazuje, że Stack Adjust jest 0x3F4 lub większy i bit 2 jest ustawiony.

  • EF lub "składanie epilogu" wskazuje, że Stack Adjust jest 0x3F4 lub większy i bit 3 jest ustawiony.

Prologues dla funkcji kanonicznych może mieć maksymalnie 5 instrukcji (zwróć uwagę, że 3a i 3b wzajemnie się wykluczają):

Instrukcja Przyjmuje się, że kod opcode jest obecny, jeśli: Rozmiar Opcode Odwij kody
1 H==1 16 push {r0-r3} 04
2 C==1 lub ==1 lub LR==0 lub PF==1 16/32 push {registers} 80-BF/D0-DF/EC-ED
3a C==1 i (R==1 i PF==0) 16 mov r11,sp FB
3b C==1 i (R==0 lub PF==1) 32 add r11,sp,#xx FC
100 R==1 i Reg != 7 32 vpush {d8-dE} E0-E7
5 Stack Adjust != 0 i PF==0 16/32 sub sp,sp,#xx 00-7F/E8-EB

Instrukcja 1 jest zawsze obecna, jeśli H bit jest ustawiony na 1.

Aby skonfigurować łańcuch ramek, instrukcja 3a lub 3b jest obecna, jeśli C bit jest ustawiony. Jest to 16-bitowy mov , jeśli nie są wypychane żadne rejestry inne niż r11 i LR; w przeciwnym razie jest to 32-bitowy add.

Jeśli określono nieskładaną korektę, instrukcja 5 jest jawną korektą stosu.

Instrukcje 2 i 4 są ustawiane na podstawie tego, czy wypychanie jest wymagane. Ta tabela zawiera podsumowanie, które rejestry są zapisywane na Cpodstawie pól , , LRi PF . We wszystkich przypadkach N wartość jest równa Reg + 4, E jest równa Reg + 8 i S jest równa (~Stack Adjust) i 3.

C L R PF Liczba całkowita rejestrów wypchnięta Wypychane rejestry VFP
0 0 0 0 r4 - r*N* Brak
0 0 0 1 r*S* - r*N* Brak
0 0 1 0 Brak d8 - d*E*
0 0 1 1 r*S* — r3 d8 - d*E*
0 1 0 0 r4 - r*N*, LR Brak
0 1 0 1 r*S* - r*N*, LR Brak
0 1 1 0 LR d8 - d*E*
0 1 1 1 r*S* - r3, LR d8 - d*E*
1 0 0 0 (nieprawidłowe kodowanie) (nieprawidłowe kodowanie)
1 0 0 1 (nieprawidłowe kodowanie) (nieprawidłowe kodowanie)
1 0 1 0 (nieprawidłowe kodowanie) (nieprawidłowe kodowanie)
1 0 1 1 (nieprawidłowe kodowanie) (nieprawidłowe kodowanie)
1 1 0 0 r4 - r**N, r11, LR Brak
1 1 0 1 r*S* - r*N*, r11, LR Brak
1 1 1 0 r11, LR d8 - d*E*
1 1 1 1 r*S* - r3, r11, LR d8 - d*E*

Epilogie dla funkcji kanonicznych mają podobną postać, ale odwrotnie i z kilkoma dodatkowymi opcjami. Epilog może trwać do 5 instrukcji, a jego forma jest ściśle dyktowana przez formę prologu.

Instrukcja Przyjmuje się, że kod opcode jest obecny, jeśli: Rozmiar Opcode
6 Stack Adjust!=0 i EF==0 16/32 add sp,sp,#xx
7 R==1 i Reg!=7 32 vpop {d8-dE}
8 C==1 lub (==1 i (LH==0 lub Ret !=0)) lub R==0 lub EF==1 16/32 pop {registers}
9a H==1 i (L==0 lub Ret!=0) 16 add sp,sp,#0x10
9b H==1 i L==1 i Ret==0 32 ldr pc,[sp],#0x14
10a Ret==1 16 bx reg
10b Ret==2 32 b address

Instrukcja 6 jest jawnym dostosowaniem stosu, jeśli określono nieskładane dostosowanie. Ponieważ PF jest niezależny od EFprogramu , można mieć instrukcję 5 bez instrukcji 6 lub odwrotnie.

Instrukcje 7 i 8 używają tej samej logiki co prolog, aby określić, które rejestry są przywracane ze stosu, ale z tymi trzema zmianami: pierwszy, jest używany zamiast PF; drugi, EF jeśli Ret = 0 i = 0, następnie LR jest zastępowany komputerem na liście rejestrów, a epilog kończy się natychmiast; trzeci, jeśli Ret = 0 i HH = 1, następnie LR zostanie pominięty z listy rejestrów i zwieńczony przez instrukcję 9b.

Jeśli H jest ustawiona, instrukcja 9a lub 9b jest obecna. Instrukcja 9a jest używana, gdy Ret jest nonzero, co oznacza również obecność 10a lub 10b. Jeśli L=1, LR został wyświetlony w ramach instrukcji 8. Instrukcja 9b jest używana, gdy L ma wartość 1 i Ret ma wartość zero, aby wskazać wczesny koniec epilogu oraz wrócić i dostosować stos w tym samym czasie.

Jeśli epilog nie został jeszcze zakończony, instrukcja 10a lub 10b jest obecna, aby wskazać 16-bitową lub 32-bitową gałąź na podstawie wartości Ret.

.xdata Rekordy

Gdy spakowany format unwind jest niewystarczający do opisania odwijania funkcji, należy utworzyć rekord o zmiennej długości .xdata . Adres tego rekordu jest przechowywany w drugim słowie rekordu .pdata . Format obiektu .xdata to zapakowany zestaw słów o zmiennej długości, który ma cztery sekcje:

  1. Nagłówek 1 lub 2-wyrazowy, który opisuje ogólny rozmiar .xdata struktury i udostępnia kluczowe dane funkcji. Drugie słowo jest obecne tylko wtedy, gdy pola Liczba epilogów i Wyrazy kodu są ustawione na 0. Pola są podzielone w tej tabeli:

    Word Bity Purpose
    0 0-17 Function Length jest polem 18-bitowym wskazującym łączną długość funkcji w bajtach podzieloną przez 2. Jeśli funkcja jest większa niż 512 KB, należy użyć wielu .pdata rekordów i .xdata do opisania funkcji. Aby uzyskać szczegółowe informacje, zobacz sekcję Large Functions (Duże funkcje) w tym dokumencie.
    0 18-19 Vers to pole 2-bitowe, które opisuje wersję pozostałej.xdata wersji. Obecnie zdefiniowano tylko wersję 0; wartości 1–3 są zarezerwowane.
    0 20 X to pole 1-bitowe, które wskazuje obecność (1) lub brak (0) danych wyjątku.
    0 21 E jest polem 1-bitowym wskazującym, że informacje opisujące pojedynczy epilog są pakowane do nagłówka (1), a nie wymagają dodatkowych słów zakresu później (0).
    0 22 F to pole 1-bitowe wskazujące, że ten rekord opisuje fragment funkcji (1) lub pełną funkcję (0). Fragment oznacza, że nie ma prologu i że wszystkie przetwarzanie prologu powinno być ignorowane.
    0 23-27 Epilog Count to pole 5-bitowe, które ma dwa znaczenia, w zależności od stanu bitu E :

    - Jeśli E ma wartość 0, to pole jest liczbą całkowitej liczby zakresów epilogu opisanych w sekcji 2. Jeśli w funkcji istnieje więcej niż 31 zakresów, to to pole i pole Wyrazy kodu muszą być ustawione na 0, aby wskazać, że słowo rozszerzenia jest wymagane.
    - Jeśli E ma wartość 1, to pole określa indeks pierwszego odwijanego kodu, który opisuje jedyny epilog.
    0 28-31 Słowa kodu to 4-bitowe pole, które określa liczbę 32-bitowych wyrazów wymaganych do przechowywania wszystkich kodów odwijanych w sekcji 4. Jeśli do więcej niż 63 bajtów kodu jest wymaganych więcej niż 15 wyrazów, to pole i pole Liczba epilogów musi mieć wartość 0, aby wskazać, że wymagany jest wyraz rozszerzenia.
    1 0-15 Rozszerzona liczba epilogów to 16-bitowe pole, które zapewnia więcej miejsca na kodowanie niezwykle dużej liczby epilogów. Słowo rozszerzenia zawierające to pole jest obecne tylko wtedy, gdy pola Liczba epilogów i Wyrazy kodu w pierwszym słowie nagłówka są ustawione na 0.
    1 16-23 Rozszerzone wyrazy kodu to 8-bitowe pole, które zapewnia więcej miejsca na kodowanie niezwykle dużej liczby rozprężonych wyrazów kodu. Słowo rozszerzenia zawierające to pole jest obecne tylko wtedy, gdy pola Liczba epilogów i Wyrazy kodu w pierwszym słowie nagłówka są ustawione na 0.
    1 24-31 Zarezerwowana
  2. Po danych wyjątku (jeśli E bit w nagłówku został ustawiony na 0) jest listą informacji o zakresach epilogu, które są pakowane jeden do słowa i przechowywane w celu zwiększenia przesunięcia początkowego. Każdy zakres zawiera następujące pola:

    Bity Purpose
    0-17 Przesunięcie początkowe epilogu jest 18-bitowym polem, które opisuje przesunięcie epilogu, w bajtach podzielonych przez 2, względem początku funkcji.
    18-19 Res to pole 2-bitowe zarezerwowane do przyszłego rozszerzenia. Jego wartość musi być 0.
    20-23 Warunek jest polem 4-bitowym, które daje warunek, w którym jest wykonywany epilog. W przypadku bezwarunkowych epilogów należy ustawić 0xE, co oznacza "zawsze". (Epilog musi być całkowicie warunkowy lub całkowicie bezwarunkowy, a w trybie Thumb-2 epilog zaczyna się od pierwszej instrukcji po kodzie operacji IT).
    24-31 Epilogue Start Index to 8-bitowe pole, które wskazuje indeks bajtów pierwszego kodu odwijanego, który opisuje ten epilog.
  3. Po utworzeniu listy zakresów epilogu znajduje się tablica bajtów zawierających kody odwijające, które zostały szczegółowo opisane w sekcji Unwind Codes w tym artykule. Ta tablica jest dopełniona na końcu najbliższej pełnej granicy słowa. Bajty są przechowywane w małej kolejności endian, aby można je było pobrać bezpośrednio w trybie little-endian.

  4. Jeśli pole X w nagłówku wynosi 1, bajty kodu odwijanego są zgodne z informacjami obsługi wyjątków. Składa się z jednego punktu odzyskiwania RVA programu obsługi wyjątków, który zawiera adres programu obsługi wyjątków, a następnie natychmiast (zmienna długość) ilości danych wymaganych przez program obsługi wyjątków.

Rekord .xdata jest zaprojektowany tak, aby można było pobrać pierwsze 8 bajtów i obliczyć pełny rozmiar rekordu, nie uwzględniając długości danych wyjątków o zmiennym rozmiarze, które następują poniżej. Ten fragment kodu oblicza rozmiar rekordu:

ULONG ComputeXdataSize(PULONG Xdata)
{
    ULONG Size;
    ULONG EpilogueScopes;
    ULONG UnwindWords;

    if ((Xdata[0] >> 23) != 0) {
        Size = 4;
        EpilogueScopes = (Xdata[0] >> 23) & 0x1f;
        UnwindWords = (Xdata[0] >> 28) & 0x0f;
    } else {
        Size = 8;
        EpilogueScopes = Xdata[1] & 0xffff;
        UnwindWords = (Xdata[1] >> 16) & 0xff;
    }

    if (!(Xdata[0] & (1 << 21))) {
        Size += 4 * EpilogueScopes;
    }

    Size += 4 * UnwindWords;

    if (Xdata[0] & (1 << 20)) {
        Size += 4;  // Exception handler RVA
    }

    return Size;
}

Chociaż prolog i każdy epilog ma indeks w kodach odwijanych, tabela jest udostępniana między nimi. Nie jest niczym niezwykłym, że wszystkie mogą współdzielić te same kody odwijań. Zalecamy zoptymalizowanie składników zapisywania kompilatora pod kątem tego przypadku, ponieważ największy indeks, który można określić, wynosi 255, i ogranicza łączną liczbę kodów odwijanych dla określonej funkcji.

Odwij kody

Tablica kodów unwind jest pulą sekwencji instrukcji, które opisują dokładnie, jak cofnąć efekty prologu, w kolejności, w której operacje muszą zostać cofnięte. Kody odwijań to mini zestaw instrukcji zakodowany jako ciąg bajtów. Po zakończeniu wykonywania zwracany adres do funkcji wywołującej znajduje się w rejestrze LR, a wszystkie rejestry nietrwałe są przywracane do ich wartości w momencie wywołania funkcji.

Gdyby wyjątki były gwarantowane tylko w obrębie treści funkcji i nigdy w prologu lub epilogu, konieczne byłoby tylko jedno odwijenie sekwencji. Jednak model odwijania systemu Windows wymaga możliwości odwijania się z poziomu częściowo wykonanego prologu lub epilogu. Aby spełnić to wymaganie, kody odwijające zostały starannie zaprojektowane, aby mieć jednoznaczne mapowanie jeden do jednego dla każdego odpowiedniego kodu opcode w prologu i epilogu. Ma to kilka konsekwencji:

  • Istnieje możliwość obliczenia długości prologu i epilogu przez zliczanie liczby kodów odwijania. Jest to możliwe nawet w przypadku instrukcji Thumb-2 o zmiennej długości, ponieważ istnieją różne mapowania dla 16-bitowych i 32-bitowych kodów operacji.

  • Zliczając liczbę instrukcji przed rozpoczęciem zakresu epilogu, można pominąć równoważną liczbę kodów odwijania i wykonać resztę sekwencji, aby ukończyć częściowo wykonane unwind, że epilog działa.

  • Zliczając liczbę instrukcji przed końcem prologu, można pominąć równoważną liczbę kodów odwijania i wykonać resztę sekwencji, aby cofnąć tylko te części prologu, które zakończyły wykonywanie.

W poniższej tabeli przedstawiono mapowanie z kodów odwijanych do kodów operacji. Najbardziej typowe kody to tylko jeden bajt, a mniej typowe wymagają dwóch, trzech, a nawet czterech bajtów. Każdy kod jest przechowywany z najbardziej znaczących bajtów do najmniej znaczących bajtów. Struktura kodu odwijania różni się od kodowania opisanego w architekturze ARM EABI, ponieważ te kody odwijania zostały zaprojektowane tak, aby miały mapowanie jeden do jednego w prologu i epilogu, aby umożliwić odwijanie częściowo wykonanych prologów i epilogów.

Bajt 1 Bajt 2 Bajt 3 Bajt 4 Opsize Wyjaśnienie
00-7F 16 add sp,sp,#X

gdzie X to (kod i 0x7F) * 4
80-BF 00-FF 32 pop {r0-r12, lr}

gdzie LR jest zwinięte, jeśli kod i 0x2000 i r0-r12 są wyświetlane, jeśli odpowiedni bit jest ustawiony w kodzie i 0x1FFF
C0-CF 16 mov sp,rX

gdzie X to Kod i 0x0F
D0-D7 16 pop {r4-rX,lr}

gdzie X to (Kod i 0x03) + 4 i LR jest zwinięty, jeśli kod i 0x04
D8-DF 32 pop {r4-rX,lr}

gdzie X to (Kod i 0x03) + 8 i LR jest zwinięty, jeśli kod i 0x04
E0-E7 32 vpop {d8-dX}

gdzie X to (kod i 0x07) + 8
E8-EB 00-FF 32 addw sp,sp,#X

gdzie X to (kod i 0x03FF) * 4
EC-ED 00-FF 16 pop {r0-r7,lr}

gdzie LR jest zwinięty, jeśli kod i 0x0100 i r0-r7 są zwinięte, jeśli odpowiedni bit jest ustawiony w kodzie i 0x00FF
EE 00-0F 16 specyficzne dla firmy Microsoft
EE 10-FF 16 Dostępny
EF 00-0F 32 ldr lr,[sp],#X

gdzie X to (kod i 0x000F) * 4
EF 10-FF 32 Dostępny
F0-F4 - Dostępny
F5 00-FF 32 vpop {dS-dE}

gdzie S to (kod i 0x00F0) >> 4, a E to Kod i 0x000F
F6 00-FF 32 vpop {dS-dE}

gdzie S to (Kod i 0x00F0) >> 4) + 16 i E to (Kod i 0x000F) + 16
F7 00-FF 00-FF 16 add sp,sp,#X

gdzie X to (kod i 0x00FFFF) * 4
F8 00-FF 00-FF 00-FF 16 add sp,sp,#X

gdzie X to (kod i 0x00FFFFFF) * 4
F9 00-FF 00-FF 32 add sp,sp,#X

gdzie X to (kod i 0x00FFFF) * 4
FA 00-FF 00-FF 00-FF 32 add sp,sp,#X

gdzie X to (kod i 0x00FFFFFF) * 4
FB 16 nop (16-bitowy)
FC 32 nop (32-bitowy)
FD 16 end + 16-bitowy nop w epilogu
FE 32 end + 32-bitowy nop w epilogu
FF - end

Spowoduje to wyświetlenie zakresu wartości szesnastkowej dla każdego bajtu w kodzie kodu odwijanego wraz z rozmiarem kodu opcode Opsize i odpowiednią oryginalną interpretacją instrukcji. Puste komórki wskazują krótsze kody odwijania. W instrukcjach, które mają duże wartości obejmujące wiele bajtów, najważniejsze bity są przechowywane jako pierwsze. Pole Opsize zawiera niejawny rozmiar kodu opcode skojarzony z każdą operacją Thumb-2. Widoczne zduplikowane wpisy w tabeli z różnymi kodowaniami służą do rozróżniania różnych rozmiarów kodu opcode.

Kody odwijanie są zaprojektowane tak, aby pierwszy bajt kodu informuje zarówno całkowity rozmiar w bajtach kodu, jak i rozmiar odpowiedniego kodu opcode w strumieniu instrukcji. Aby obliczyć rozmiar prologu lub epilogu, przejdź przez kody od początku sekwencji do końca i użyj tabeli odnośników lub podobnej metody, aby określić, jak długo jest odpowiedni kod opcode.

Kody odwijaj 0xFD i 0xFE są równoważne zwykłym kodom końcowym 0xFF, ale stanowią jeden dodatkowy kod opcode nop w przypadku epilogu, 16-bitowego lub 32-bitowego. W przypadku prologów kody 0xFD, 0xFE i 0xFF są dokładnie równoważne. Stanowi to typowe zakończenia epilogu bx lr lub b <tailcall-target>, które nie mają równoważnej instrukcji prologu. Zwiększa to prawdopodobieństwo, że sekwencje odwijające mogą być udostępniane między prologiem a epilogiami.

W wielu przypadkach powinno być możliwe użycie tego samego zestawu kodów rozprężenia dla prologu i wszystkich epilogów. Jednak aby obsłużyć odwijanie częściowo wykonanych prologów i epilogów, może być konieczne posiadanie wielu sekwencji kodu odwijania, które różnią się w kolejności lub zachowaniu. Dlatego każdy epilog ma własny indeks w tablicy odwijania, aby pokazać, gdzie rozpocząć wykonywanie.

Odwijanie częściowych prologues i epilogues

Najczęstszy przypadek odwijania polega na tym, że wyjątek występuje w treści funkcji, z dala od prologu i wszystkich epilogów. W takim przypadku unwinder wykonuje kody w tablicy odwijanej rozpoczynającej się od indeksu 0 i kontynuuje do momentu wykrycia końcowego kodu opcode.

Gdy wystąpi wyjątek podczas wykonywania prologu lub epilogu, ramka stosu jest tylko częściowo skonstruowana, a odwijanie musi określić dokładnie to, co zostało zrobione, aby poprawnie go cofnąć.

Rozważmy na przykład tę prologię i sekwencję epilogiczną:

0000:   push  {r0-r3}         ; 0x04
0002:   push  {r4-r9, lr}     ; 0xdd
0006:   mov   r7, sp          ; 0xc7
...
0140:   mov   sp, r7          ; 0xc7
0142:   pop   {r4-r9, lr}     ; 0xdd
0146:   add   sp, sp, #16     ; 0x04
0148:   bx    lr

Obok każdego kodu opcode jest odpowiedni kod odwijaj, aby opisać tę operację. Sekwencja kodów odwijania dla prologu jest lustrzanym obrazem kodów unwind dla epilogu, nie licząc ostatecznej instrukcji. Ten przypadek jest powszechny i jest powodem, dla którego kody odwijające prologu są zawsze zakładane, że są przechowywane w odwrotnej kolejności od zamówienia wykonania prologu. Zapewnia to wspólny zestaw kodów odwijanych:

0xc7, 0xdd, 0x04, 0xfd

Kod 0xFD jest specjalnym kodem na końcu sekwencji, co oznacza, że epilog jest jedną instrukcją 16-bitową dłuższą niż prolog. Dzięki temu możliwe jest większe udostępnianie kodów odwijanych.

W przykładzie, jeśli wystąpi wyjątek, gdy treść funkcji między prologiem a epilogiem jest wykonywana, odwijanie zaczyna się od przypadku epilogu, z przesunięciem 0 w kodzie epilogu. Odpowiada to 0x140 przesunięcia w przykładzie. Unwinder wykonuje pełną sekwencję odwijaczania, ponieważ nie wykonano oczyszczania. Jeśli zamiast tego wyjątek występuje jedna instrukcja po początku kodu epilogu, odwijanie może pomyślnie odwinąć, pomijając pierwszy odwijania kodu. Biorąc pod uwagę mapowanie jeden do jednego między kodami opcodes i unwind codes, jeśli odwijanie się z instrukcji n w epilogu, unwinder powinien pominąć pierwsze n kody odwijania.

Podobna logika działa odwrotnie dla prologu. Jeśli odwijanie od przesunięcia 0 w prologu, nie trzeba wykonywać żadnych czynności. W przypadku odwijania z jednej instrukcji w programie sekwencja odwijania powinna uruchomić jeden kod odwijania od końca, ponieważ kody odwijania prologu są przechowywane w odwrotnej kolejności. W ogólnym przypadku, jeśli odwijanie z instrukcji n w prologu, odwijanie powinno rozpocząć wykonywanie od n unwind codes z końca listy kodów.

Prolog i epilog unwind codes nie zawsze pasują dokładnie. W takim przypadku tablica kodu unwind może wymagać kilku sekwencji kodów. Aby określić przesunięcie w celu rozpoczęcia przetwarzania kodów, użyj następującej logiki:

  1. Jeśli rozwiniesz się z poziomu treści funkcji, rozpocznij wykonywanie kodów odwijania w indeksie 0 i kontynuuj do momentu osiągnięcia końcowego kodu opcode.

  2. Jeśli unwinding z wewnątrz epilogu, użyj epilogu specyficznego dla indeksu początkowego dostarczonego przez zakres epilogu. Oblicz liczbę bajtów komputera od początku epilogu. Pomiń kody odwijań do momentu, aż zostaną uwzględnione wszystkie już wykonane instrukcje. Wykonaj sekwencję odwijającą, zaczynając od tego momentu.

  3. Jeśli rozwiniesz się z poziomu prologu, zacznij od indeksu 0 w kodach odwijania. Oblicz długość kodu prologu z sekwencji, a następnie oblicz liczbę bajtów komputera z końca prologu. Przejdź do przodu przez kody odwijań, dopóki nie zostaną uwzględnione wszystkie niewyciągniętych instrukcji. Wykonaj sekwencję odwijającą, zaczynając od tego momentu.

Kody odwijające dla prologu muszą zawsze być pierwszymi w tablicy. są to również kody używane do odwijania się w ogólnym przypadku odwijania się z wewnątrz ciała. Wszystkie sekwencje kodu specyficzne dla epilogu powinny być zgodne bezpośrednio po sekwencji kodu prologu.

Fragmenty funkcji

W przypadku optymalizacji kodu przydatne może być podzielenie funkcji na części nietręczne. Po wykonaniu tej czynności każdy fragment funkcji wymaga osobnego .pdatarekordu — i ewentualnie .xdata— rekordu.

Zakładając, że prolog funkcji znajduje się na początku funkcji i nie można go podzielić, istnieją cztery przypadki fragmentów funkcji:

  • Tylko prolog; wszystkie epilogie w innych fragmentach.

  • Prolog i co najmniej jeden epilog; więcej epilogów w innych fragmentach.

  • Brak prologu ani epilogów; prolog i co najmniej jeden epilog w innych fragmentach.

  • Tylko epiloge; prolog i ewentualnie więcej epilogów w innych fragmentach.

W pierwszym przypadku należy opisać tylko prolog. Można to zrobić w formie kompaktowej .pdata , opisując prolog normalnie i określając Ret wartość 3, aby wskazać żaden epilog. W pełnej .xdata formie można to zrobić, podając prologowe kody odwijania na indeksie 0 jak zwykle i określając liczbę epilogów 0.

Drugi przypadek jest jak normalna funkcja. Jeśli fragment zawiera tylko jeden epilog i znajduje się na końcu fragmentu, można użyć kompaktowego .pdata rekordu. W przeciwnym razie należy użyć pełnego .xdata rekordu. Należy pamiętać, że przesunięcia określone dla początku epilogu są względem początku fragmentu, a nie pierwotnego początku funkcji.

Trzecie i czwarte przypadki są odpowiednio wariantami pierwszych i drugich przypadków, z wyjątkiem tego, że nie zawierają prologu. W takich sytuacjach zakłada się, że istnieje kod przed rozpoczęciem epilogu i jest uważany za część ciała funkcji, która normalnie byłaby niezgarniana przez cofnięcie skutków prologu. W związku z tym przypadki te muszą być zakodowane za pomocą pseudoprologiu, który opisuje sposób odwijania się z ciała, ale który jest traktowany jako 0-długość podczas określania, czy wykonać częściowe odwijanie na początku fragmentu. Alternatywnie, ten pseudo-prolog może być opisany przy użyciu tych samych kodów odwijacza co epilog, ponieważ prawdopodobnie wykonują równoważne operacje.

W trzecim i czwartym przypadku obecność pseudoprologiu jest określona przez ustawienie Flag pola rekordu kompaktowego .pdata na 2 lub ustawienie flagi F w nagłówku .xdata na 1. W obu przypadkach sprawdzanie częściowego prologu unwind jest ignorowane, a wszystkie unlogue unlogue unwinds są uważane za pełne.

Duże funkcje

Fragmenty mogą służyć do opisywania funkcji większych niż limit 512 KB narzucony przez pola bitowe w nagłówku .xdata . Aby opisać większą funkcję, wystarczy podzielić ją na fragmenty mniejsze niż 512 KB. Każdy fragment należy dostosować, aby nie dzielił epilogu na wiele fragmentów.

Tylko pierwszy fragment funkcji zawiera prolog. Wszystkie inne fragmenty są oznaczone jako bez prologu. W zależności od liczby epilogów każdy fragment może zawierać zero lub więcej epilogów. Należy pamiętać, że każdy zakres epilogu w fragmentie określa jego przesunięcie początkowe względem początku fragmentu, a nie początku funkcji.

Jeśli fragment nie ma prologu i nie epilogu, nadal wymaga własnego .pdata— i ewentualnie .xdata— rekordu, aby opisać sposób odwijenia się z treści funkcji.

Zawijanie zmniejszania

Bardziej złożony specjalny przypadek fragmentów funkcji jest nazywany zawijaniem zmniejszania. Jest to technika odroczenia zapisywania rejestru od początku funkcji do późniejszej części funkcji. Jest zoptymalizowany pod kątem prostych przypadków, które nie wymagają zapisywania rejestru. Ten przypadek ma dwie części: istnieje region zewnętrzny, który przydziela miejsce stosu, ale zapisuje minimalny zestaw rejestrów oraz region wewnętrzny, który zapisuje i przywraca inne rejestry.

ShrinkWrappedFunction
    push   {r4, lr}          ; A: save minimal non-volatiles
    sub    sp, sp, #0x100    ; A: allocate all stack space up front
    ...                      ; A:
    add    r0, sp, #0xE4     ; A: prepare to do the inner save
    stm    r0, {r5-r11}      ; A: save remaining non-volatiles
    ...                      ; B:
    add    r0, sp, #0xE4     ; B: prepare to do the inner restore
    ldm    r0, {r5-r11}      ; B: restore remaining non-volatiles
    ...                      ; C:
    pop    {r4, pc}          ; C:

Funkcje opakowane w ścięcie są zwykle oczekiwane, aby wstępnie przydzielić miejsce na dodatkowe zapisy rejestru w regularnym prologu, a następnie zapisać rejestry przy użyciu str lub stm zamiast push. Ta akcja zachowuje wszystkie manipulacje wskaźnikiem stosu w oryginalnym prologu funkcji.

Przykładowa funkcja opakowana mniejszą musi być podzielona na trzy regiony, które są oznaczone jako A, Bi C w komentarzach. Pierwszy A region obejmuje początek funkcji przez koniec dodatkowych nietrwałych zapisów. Rekord .pdata lub .xdata musi być skonstruowany, aby opisać ten fragment jako prolog i nie epilogów.

Region środkowy B dostaje własny .pdata lub .xdata rekord, który opisuje fragment, który nie ma prologu i nie epilogu. Jednak kody odwiju dla tego regionu muszą być nadal obecne, ponieważ są uważane za treść funkcji. Kody muszą opisywać złożony prolog reprezentujący zarówno oryginalne rejestry zapisane w prologu regionu A , jak i dodatkowe rejestry zapisane przed wejściem do regionu B, tak jakby zostały utworzone przez jedną sekwencję operacji.

Zapisy rejestru dla regionu B nie mogą być traktowane jako "wewnętrzny prolog", ponieważ złożony prolog opisany dla regionu B musi opisywać zarówno prolog regionu A , jak i dodatkowe rejestry zapisane. Gdyby fragment B miał prolog, kody odwijające oznaczałyby również rozmiar tego prologu i nie ma możliwości opisania złożonego prologu w sposób mapujący jeden do jednego z kodami opcode, które zapisują tylko dodatkowe rejestry.

Dodatkowe zapisy rejestru muszą być traktowane jako część regionu A, ponieważ dopóki nie zostaną ukończone, prolog złożony nie opisuje dokładnie stanu stosu.

Ostatni C region dostaje własny .pdata lub .xdata rekord, opisując fragment, który nie ma prologu, ale ma epilog.

Alternatywna metoda może również działać, jeśli manipulowanie stosem przed wejściem do regionu B można zmniejszyć do jednej instrukcji:

ShrinkWrappedFunction
    push   {r4, lr}          ; A: save minimal non-volatile registers
    sub    sp, sp, #0xE0     ; A: allocate minimal stack space up front
    ...                      ; A:
    push   {r4-r9}           ; A: save remaining non-volatiles
    ...                      ; B:
    pop    {r4-r9}           ; B: restore remaining non-volatiles
    ...                      ; C:
    pop    {r4, pc}          ; C: restore non-volatile registers

Kluczową analizą jest to, że na każdej granicy instrukcji stos jest w pełni zgodny z kodami odwijanymi dla regionu. Jeśli unwind występuje przed wewnętrznym wypchnięciem w tym przykładzie, jest to uważane za część regionu A. Tylko prolog regionu A jest unwound. Jeśli odwijanie występuje po wypchnięciu wewnętrznym, jest uważany za część regionu B, który nie ma prologu. Jednak zawiera on kody odwijania, które opisują zarówno wewnętrzny wypychanie, jak i oryginalny prolog z regionu A. Podobna logika jest przechowywana dla wewnętrznego okna podręcznego.

Optymalizacje kodowania

Bogactwo kodów odwijania oraz możliwość korzystania z kompaktowych i rozszerzonych form danych, zapewnia wiele możliwości optymalizacji kodowania w celu dalszego zmniejszenia przestrzeni. W przypadku agresywnego użycia tych technik można zminimalizować obciążenie netto opisujące funkcje i fragmenty przy użyciu kodów odwijania.

Najważniejszy pomysł optymalizacji: Nie należy mylić prologu i granic epilogu do odwijania celów z logicznym prologiem i epilogiem granic z perspektywy kompilatora. Odwijające się granice mogą być skurczone i ściślejsze w celu poprawy wydajności. Na przykład prolog może zawierać kod po skonfigurowaniu stosu w celu przeprowadzenia kontroli weryfikacji. Ale po zakończeniu całej manipulacji stosem nie ma potrzeby kodowania dalszych operacji i niczego poza tym, co można usunąć z odwijania prologu.

Ta sama reguła ma zastosowanie do długości funkcji. Jeśli istnieją dane (takie jak pula literałów), które są zgodne z epilogiem w funkcji, nie powinny być uwzględniane jako część długości funkcji. Zmniejszając funkcję tylko do kodu, który jest częścią funkcji, prawdopodobieństwo jest znacznie większe, że epilog znajduje się na samym końcu i można użyć kompaktowego .pdata rekordu.

W prologu, gdy wskaźnik stosu zostanie zapisany w innym rejestrze, zwykle nie ma potrzeby rejestrowania żadnych dalszych kodów operacji. Aby odwinąć funkcję, pierwszą rzeczą, która jest wykonywana, jest odzyskanie sp z zapisanego rejestru. Dalsze operacje nie mają żadnego wpływu na odwijęcie.

Epiloge z jedną instrukcją nie muszą być w ogóle kodowane jako zakresy lub jako kody odwijaczania. Jeśli przed wykonaniem tej instrukcji nastąpi odwijenie, można bezpiecznie założyć, że znajduje się ona w treści funkcji. Wystarczy tylko wykonanie kodów odwijania prologu. Po wykonaniu pojedynczej instrukcji odwijając się, następuje to z definicji w innym regionie.

Epiloge z wieloma instrukcjami nie muszą kodować pierwszej instrukcji epilogu, z tego samego powodu co poprzedni punkt: jeśli unwind ma miejsce przed wykonaniem tej instrukcji, pełny prolog unwind jest wystarczający. Jeśli odwija się po tej instrukcji, należy wziąć pod uwagę tylko późniejsze operacje.

Ponowne użycie kodu odwija się powinno być agresywne. Indeksowanie każdego zakresu epilogu określa punkty do dowolnego punktu początkowego w tablicy kodów odwijanych. Nie musi wskazywać początku poprzedniej sekwencji; może wskazywać w środku. Najlepszym rozwiązaniem jest wygenerowanie sekwencji kodu odwijanych. Następnie przeskanuj dokładne dopasowanie bajtów w już zakodowanej puli sekwencji. Użyj dowolnego idealnego dopasowania jako punktu wyjścia do ponownego użycia.

Po epilogu z jedną instrukcją są ignorowane, jeśli nie ma pozostałych epilogów, rozważ użycie kompaktowej .pdata formy; staje się znacznie bardziej prawdopodobne w przypadku braku epilogu.

Przykłady

W tych przykładach baza obrazów znajduje się w 0x00400000.

Przykład 1: Funkcja liścia, brak ustawień lokalnych

Prologue:
  004535F8: B430      push        {r4-r5}
Epilogue:
  00453656: BC30      pop         {r4-r5}
  00453658: 4770      bx          lr

.pdata (stałe, 2 wyrazy):

  • Word 0

    • Function Start RVA = 0x000535F8 (= 0x004535F8-0x00400000)
  • Word 1

    • Flag = 1, wskazując kanoniczny prolog i formaty epilogu

    • Function Length = 0x31 (= 0x62/2)

    • Ret = 1, wskazujący 16-bitowy zwrot gałęzi

    • H = 0, wskazując, że parametry nie zostały w domu

    • R = 0 i Reg = 1, wskazując push/pop r4-r5

    • L = 0, wskazując brak zapisywania/przywracania LR

    • C = 0, wskazując brak łańcucha ramek

    • Stack Adjust = 0, wskazując brak korekty stosu

Przykład 2: funkcja zagnieżdżona z alokacją lokalną

Prologue:
  004533AC: B5F0      push        {r4-r7, lr}
  004533AE: B083      sub         sp, sp, #0xC
Epilogue:
  00453412: B003      add         sp, sp, #0xC
  00453414: BDF0      pop         {r4-r7, pc}

.pdata (stałe, 2 wyrazy):

  • Word 0

    • Function Start RVA = 0x000533AC (= 0x004533AC -0x00400000)
  • Word 1

    • Flag = 1, wskazując kanoniczny prolog i formaty epilogu

    • Function Length = 0x35 (= 0x6A/2)

    • Ret = 0, wskazując pop {pc} zwracany

    • H = 0, wskazując, że parametry nie zostały w domu

    • R = 0 i Reg = 3, wskazując wypychanie/pop r4-r7

    • L = 1, wskazując, że LR został zapisany/przywrócony

    • C = 0, wskazując brak łańcucha ramek

    • Stack Adjust = 3 (= 0x0C/4)

Przykład 3. Zagnieżdżona funkcja Variadic

Prologue:
  00453988: B40F      push        {r0-r3}
  0045398A: B570      push        {r4-r6, lr}
Epilogue:
  004539D4: E8BD 4070 pop         {r4-r6}
  004539D8: F85D FB14 ldr         pc, [sp], #0x14

.pdata (stałe, 2 wyrazy):

  • Word 0

    • Function Start RVA = 0x00053988 (= 0x00453988-0x00400000)
  • Word 1

    • Flag = 1, wskazując kanoniczny prolog i formaty epilogu

    • Function Length = 0x2A (= 0x54/2)

    • Ret = 0, wskazując pop {pc}-style return (w tym przypadku zwracany ldr pc,[sp],#0x14 )

    • H = 1, wskazując, że parametry zostały w domu

    • R = 0 i Reg = 2, wskazując push/pop r4-r6

    • L = 1, wskazując, że LR został zapisany/przywrócony

    • C = 0, wskazując brak łańcucha ramek

    • Stack Adjust = 0, wskazując brak korekty stosu

Przykład 4. Funkcja z wieloma epilogami

Prologue:
  004592F4: E92D 47F0 stmdb       sp!, {r4-r10, lr}
  004592F8: B086      sub         sp, sp, #0x18
Epilogues:
  00459316: B006      add         sp, sp, #0x18
  00459318: E8BD 87F0 ldm         sp!, {r4-r10, pc}
  ...
  0045943E: B006      add         sp, sp, #0x18
  00459440: E8BD 87F0 ldm         sp!, {r4-r10, pc}
  ...
  004595D4: B006      add         sp, sp, #0x18
  004595D6: E8BD 87F0 ldm         sp!, {r4-r10, pc}
  ...
  00459606: B006      add         sp, sp, #0x18
  00459608: E8BD 87F0 ldm         sp!, {r4-r10, pc}
  ...
  00459636: F028 FF0F bl          KeBugCheckEx     ; end of function

.pdata (stałe, 2 wyrazy):

  • Word 0

    • Function Start RVA = 0x000592F4 (= 0x004592F4-0x00400000)
  • Word 1

    • Flag = 0, wskazując .xdata obecny rekord (wymagany dla wielu epilogów)

    • .xdata adres — 0x00400000

.xdata (zmienna, 6 słów):

  • Word 0

    • Function Length = 0x0001A3 (= 0x000346/2)

    • Vers = 0, wskazując pierwszą wersję.xdata

    • X = 0, wskazując brak danych wyjątku

    • E = 0, wskazując listę zakresów epilogu

    • F = 0, wskazując pełny opis funkcji, w tym prolog

    • Epilogue Count = 0x04 wskazujący 4 całkowite zakresy epilogu

    • Code Words = 0x01, wskazując jeden 32-bitowy wyraz unwind codes

  • Słowa 1-4, opisujące 4 zakresy epilogu w 4 miejscach. Każdy zakres ma wspólny zestaw kodów odwijania, współużytkowany z prologiem, na 0x00 przesunięcia i jest bezwarunkowy, określając warunek 0x0E (zawsze).

  • Odwij kody, począwszy od programu Word 5: (udostępniane między prologiem/epilogem)

    • Odwij kod 0 = 0x06: sp += (6 << 2)

    • Odwij kod 1 = 0xDE: pop {r4-r10, lr}

    • Odwij kod 2 = 0xFF: koniec

Przykład 5: Funkcja z dynamicznym stosem i wewnętrznym epilogiem

Prologue:
  00485A20: B40F      push        {r0-r3}
  00485A22: E92D 41F0 stmdb       sp!, {r4-r8, lr}
  00485A26: 466E      mov         r6, sp
  00485A28: 0934      lsrs        r4, r6, #4
  00485A2A: 0124      lsls        r4, r4, #4
  00485A2C: 46A5      mov         sp, r4
  00485A2E: F2AD 2D90 subw        sp, sp, #0x290
Epilogue:
  00485BAC: 46B5      mov         sp, r6
  00485BAE: E8BD 41F0 ldm         sp!, {r4-r8, lr}
  00485BB2: B004      add         sp, sp, #0x10
  00485BB4: 4770      bx          lr
  ...
  00485E2A: F7FF BE7D b           #0x485B28    ; end of function

.pdata (stałe, 2 wyrazy):

  • Word 0

    • Function Start RVA = 0x00085A20 (= 0x00485A20-0x00400000)
  • Word 1

    • Flag = 0, wskazując .xdata obecny rekord (wymagany dla wielu epilogów)

    • .xdata adres — 0x00400000

.xdata (zmienna, 3 słowa):

  • Word 0

    • Function Length = 0x0001A3 (= 0x000346/2)

    • Vers = 0, wskazując pierwszą wersję.xdata

    • X = 0, wskazując brak danych wyjątku

    • E = 0, wskazując listę zakresów epilogu

    • F = 0, wskazując pełny opis funkcji, w tym prolog

    • Epilogue Count = 0x001 wskazujący 1 całkowity zakres epilogu

    • Code Words = 0x01, wskazując jeden 32-bitowy wyraz unwind codes

  • Word 1: Zakres epilogu w 0xC6 przesunięcia (= 0x18C/2), uruchamianie indeksu kodu odwijanego w 0x00 i z warunkiem 0x0E (zawsze)

  • Odwij kody, począwszy od programu Word 2: (udostępniane między prologiem/epilogem)

    • Odwij kod 0 = 0xC6: sp = r6

    • Odwij kod 1 = 0xDC: pop {r4-r8, lr}

    • Odwij kod 2 = 0x04: sp += (4 << 2)

    • Unwind code 3 = 0xFD: end, liczy się jako instrukcja 16-bitowa dla epilogu

Przykład 6. Funkcja z programem obsługi wyjątków

Prologue:
  00488C1C: 0059 A7ED dc.w  0x0059A7ED
  00488C20: 005A 8ED0 dc.w  0x005A8ED0
FunctionStart:
  00488C24: B590      push        {r4, r7, lr}
  00488C26: B085      sub         sp, sp, #0x14
  00488C28: 466F      mov         r7, sp
Epilogue:
  00488C6C: 46BD      mov         sp, r7
  00488C6E: B005      add         sp, sp, #0x14
  00488C70: BD90      pop         {r4, r7, pc}

.pdata (stałe, 2 wyrazy):

  • Word 0

    • Function Start RVA = 0x00088C24 (= 0x00488C24-0x00400000)
  • Word 1

    • Flag = 0, wskazując .xdata obecny rekord (wymagany dla wielu epilogów)

    • .xdata adres — 0x00400000

.xdata (zmienna, 5 słów):

  • Word 0

    • Function Length =0x000027 (= 0x00004E/2)

    • Vers = 0, wskazując pierwszą wersję.xdata

    • X = 1, wskazując, że dane wyjątku są obecne

    • E = 1, wskazujący pojedynczy epilog

    • F = 0, wskazując pełny opis funkcji, w tym prolog

    • Epilogue Count = 0x00, wskazując epilog unlogue odwijać kody zaczynają się od przesunięcia 0x00

    • Code Words = 0x02, wskazując dwa 32-bitowe wyrazy kodów odwijenia

  • Odwij kody, począwszy od programu Word 1:

    • Odwij kod 0 = 0xC7: sp = r7

    • Odwij kod 1 = 0x05: sp += (5 << 2)

    • Odwij kod 2 = 0xED/0x90: pop {r4, r7, lr}

    • Odwij kod 4 = 0xFF: koniec

  • Program Word 3 określa procedurę obsługi wyjątków = 0x0019A7ED (= 0x0059A7ED — 0x00400000)

  • Wyrazy 4 i inne są wbudowanymi danymi wyjątku

Przykład 7: Funclet

Function:
  00488C72: B500      push        {lr}
  00488C74: B081      sub         sp, sp, #4
  00488C76: 3F20      subs        r7, #0x20
  00488C78: F117 0308 adds        r3, r7, #8
  00488C7C: 1D3A      adds        r2, r7, #4
  00488C7E: 1C39      adds        r1, r7, #0
  00488C80: F7FF FFAC bl          target
  00488C84: B001      add         sp, sp, #4
  00488C86: BD00      pop         {pc}

.pdata (stałe, 2 wyrazy):

  • Word 0

    • Function Start RVA = 0x00088C72 (= 0x00488C72-0x00400000)
  • Word 1

    • Flag = 1, wskazując kanoniczny prolog i formaty epilogu

    • Function Length = 0x0B (= 0x16/2)

    • Ret = 0, wskazując pop {pc} zwracany

    • H = 0, wskazując, że parametry nie zostały w domu

    • R = 0 i Reg = 7, wskazując, że nie zapisano/przywrócono żadnych rejestrów

    • L = 1, wskazując, że LR został zapisany/przywrócony

    • C = 0, wskazując brak łańcucha ramek

    • Stack Adjust = 1, wskazując 1 × 4 korektę stosu bajtów

Zobacz też

Przegląd konwencji ABI ARM
Typowe problemy przy migracji Visual C++ ARM