Uwaga
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
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ż flagaC
oznacza r11.
Ret
Jeśli pole ma wartość 0, flaga musi być ustawionaL
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, żeStack Adjust
jest 0x3F4 lub większy i bit 2 jest ustawiony.EF
lub "składanie epilogu" wskazuje, żeStack 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 L R ==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 C
podstawie pól , , L
R
i 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 (L H ==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 EF
programu , 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 H
H
= 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:
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śliE
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śliE
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 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. 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.
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:
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.
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.
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 .pdata
rekordu — 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
, B
i 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 epiloguFunction Length
= 0x31 (= 0x62/2)Ret
= 1, wskazujący 16-bitowy zwrot gałęziH
= 0, wskazując, że parametry nie zostały w domuR
= 0 iReg
= 1, wskazując push/pop r4-r5L
= 0, wskazując brak zapisywania/przywracania LRC
= 0, wskazując brak łańcucha ramekStack 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 epiloguFunction Length
= 0x35 (= 0x6A/2)Ret
= 0, wskazując pop {pc} zwracanyH
= 0, wskazując, że parametry nie zostały w domuR
= 0 iReg
= 3, wskazując wypychanie/pop r4-r7L
= 1, wskazując, że LR został zapisany/przywróconyC
= 0, wskazując brak łańcucha ramekStack 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 epiloguFunction Length
= 0x2A (= 0x54/2)Ret
= 0, wskazując pop {pc}-style return (w tym przypadku zwracanyldr pc,[sp],#0x14
)H
= 1, wskazując, że parametry zostały w domuR
= 0 iReg
= 2, wskazując push/pop r4-r6L
= 1, wskazując, że LR został zapisany/przywróconyC
= 0, wskazując brak łańcucha ramekStack 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ątkuE
= 0, wskazując listę zakresów epiloguF
= 0, wskazując pełny opis funkcji, w tym prologEpilogue Count
= 0x04 wskazujący 4 całkowite zakresy epiloguCode 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ątkuE
= 0, wskazując listę zakresów epiloguF
= 0, wskazując pełny opis funkcji, w tym prologEpilogue Count
= 0x001 wskazujący 1 całkowity zakres epiloguCode 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ą obecneE
= 1, wskazujący pojedynczy epilogF
= 0, wskazując pełny opis funkcji, w tym prologEpilogue Count
= 0x00, wskazując epilog unlogue odwijać kody zaczynają się od przesunięcia 0x00Code 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 epiloguFunction Length
= 0x0B (= 0x16/2)Ret
= 0, wskazując pop {pc} zwracanyH
= 0, wskazując, że parametry nie zostały w domuR
= 0 iReg
= 7, wskazując, że nie zapisano/przywrócono żadnych rejestrówL
= 1, wskazując, że LR został zapisany/przywróconyC
= 0, wskazując brak łańcucha ramekStack Adjust
= 1, wskazując 1 × 4 korektę stosu bajtów
Zobacz też
Przegląd konwencji ABI ARM
Typowe problemy przy migracji Visual C++ ARM