Ošetření výjimek ARM64
Systém Windows v ARM64 používá stejný mechanismus strukturovaného zpracování výjimek pro asynchronní výjimky generované hardwarem a synchronní výjimky generované softwarem. Obslužné rutiny výjimek specifické pro jazyk jsou založené na strukturovaném zpracování výjimek systému Windows pomocí pomocných funkcí jazyka. Tento dokument popisuje zpracování výjimek ve Windows v ARM64. Ukazuje pomocné rutiny jazyka používané kódem vygenerovaným assemblerem Microsoft ARM a kompilátorem MSVC.
Cíle a motivace
Výjimky odvíjejí konvence dat a tento popis jsou určeny k:
Zadejte dostatečný popis, který umožňuje odvíjení bez sondování kódu ve všech případech.
Analýza kódu vyžaduje, aby kód byl stránkován. Za určitých okolností zabraňuje odvíjení, kdy je užitečné (trasování, vzorkování, ladění).
Analýza kódu je složitá; kompilátor musí být opatrný, aby vygeneroval pouze instrukce, které může dekódovat unwinder.
Pokud se odvíjení nedá plně popsat pomocí kódů odvíjení, pak se v některých případech musí vrátit k dekódování instrukcí. Dekódování instrukcí zvyšuje celkovou složitost a v ideálním případě byste se měli vyhnout.
Podpora odvíjení v mid-prologu a mid-epilogu.
- Unwinding se používá ve Windows pro zpracování více než výjimek. Je důležité, aby kód mohl přesně odvíjet i v případě, že uprostřed sekvence kódu prologu nebo epilogu.
Zaberte minimální množství místa.
Kódy odvíjení nesmí agregovat, aby se binární velikost výrazně zvětšila.
Vzhledem k tomu, že kódy odvíjení budou pravděpodobně uzamčeny v paměti, malé nároky zajišťují minimální režii pro každý načtený binární soubor.
Předpoklady
Tyto předpoklady jsou provedeny v popisu zpracování výjimek:
Prology a epilogy se vzájemně zrcadlí. Díky využití této běžné vlastnosti může být velikost metadat potřebných k popisu odvíjení výrazně snížena. V těle funkce nezáleží na tom, jestli se operace prologu vrátí zpět, nebo se operace epilogu provádějí dopředu. Oba by měly mít identické výsledky.
Funkce mají tendenci k celku být relativně malé. Několik optimalizací prostoru závisí na této skutečnosti, aby bylo dosaženo nejúčinnějšího balení dat.
Vepich
Registr vyhrazeného ukazatele rámce: Pokud
sp
je uložen v jiném registru (x29
) v prologu, zůstane tento registr nedotčený v celé funkci. Znamená to, že originálsp
může být kdykoli obnoven.sp
Pokud není uložen v jiném registru, veškerá manipulace s ukazatelem zásobníku se provádí výhradně v rámci prologu a epilogu.Rozložení rámečku zásobníku je uspořádané podle popisu v další části.
Rozložení rámečku zásobníku ARM64
U zřetězených funkcí fp
rámce lze v závislosti na aspektech optimalizace uložit dvojici lr
na libovolné pozici v oblasti místní proměnné. Cílem je maximalizovat počet místních hodnot, které lze dosáhnout jediným pokynem na základě ukazatele rámce (x29
) nebo ukazatele zásobníku (sp
). U alloca
funkcí však musí být zřetězený a x29
musí odkazovat na konec zásobníku. Aby bylo možné zlepšit pokrytí režimu adresování párů registrů, jsou oblasti uložení nevolatilního registru umístěny v horní části zásobníku místní oblasti. Tady jsou příklady, které ilustrují několik nejúčinnějších sekvencí prologu. Kvůli přehlednosti a lepší lokalitě mezipaměti je pořadí ukládání volaných registrů ve všech kanonických prologech v "rostoucím" pořadí. #framesz
níže představuje velikost celého zásobníku (s výjimkou alloca
oblasti). #localsz
a #outsz
označte velikost místní oblasti (včetně oblasti uložení pro <x29, lr>
dvojici) a velikost výstupního parametru.
Zřetězený, #localsz <= 512
stp x19,x20,[sp,#-96]! // pre-indexed, save in 1st FP/INT pair stp d8,d9,[sp,#16] // save in FP regs (optional) stp x0,x1,[sp,#32] // home params (optional) stp x2,x3,[sp,#48] stp x4,x5,[sp,#64] stp x6,x7,[sp,#82] stp x29,lr,[sp,#-localsz]! // save <x29,lr> at bottom of local area mov x29,sp // x29 points to bottom of local sub sp,sp,#outsz // (optional for #outsz != 0)
Zřetězený > , #localsz 512
stp x19,x20,[sp,#-96]! // pre-indexed, save in 1st FP/INT pair stp d8,d9,[sp,#16] // save in FP regs (optional) stp x0,x1,[sp,#32] // home params (optional) stp x2,x3,[sp,#48] stp x4,x5,[sp,#64] stp x6,x7,[sp,#82] sub sp,sp,#(localsz+outsz) // allocate remaining frame stp x29,lr,[sp,#outsz] // save <x29,lr> at bottom of local area add x29,sp,#outsz // setup x29 points to bottom of local area
Unchained, listové funkce (
lr
neuložené)stp x19,x20,[sp,#-80]! // pre-indexed, save in 1st FP/INT reg-pair stp x21,x22,[sp,#16] str x23,[sp,#32] stp d8,d9,[sp,#40] // save FP regs (optional) stp d10,d11,[sp,#56] sub sp,sp,#(framesz-80) // allocate the remaining local area
Všechna místní prostředí jsou přístupná na
sp
základě .<x29,lr>
odkazuje na předchozí rámec. Velikost rámce <= 512 je možné optimalizovat,sub sp, ...
pokud je uložená oblast regů přesunuta do dolní části zásobníku. Nevýhodou je, že není konzistentní s jinými rozloženími výše. Uložené regy jsou součástí rozsahu pro párové regulární výrazy a před indexovaný režim adresování posunu.Nechainované, jiné než listové funkce (uloží
lr
se do uložené oblasti Int)stp x19,x20,[sp,#-80]! // pre-indexed, save in 1st FP/INT reg-pair stp x21,x22,[sp,#16] // ... stp x23,lr,[sp,#32] // save last Int reg and lr stp d8,d9,[sp,#48] // save FP reg-pair (optional) stp d10,d11,[sp,#64] // ... sub sp,sp,#(framesz-80) // allocate the remaining local area
Nebo se sudým číslem uloženým v registrech Int,
stp x19,x20,[sp,#-80]! // pre-indexed, save in 1st FP/INT reg-pair stp x21,x22,[sp,#16] // ... str lr,[sp,#32] // save lr stp d8,d9,[sp,#40] // save FP reg-pair (optional) stp d10,d11,[sp,#56] // ... sub sp,sp,#(framesz-80) // allocate the remaining local area
Pouze
x19
uloženo:sub sp,sp,#16 // reg save area allocation* stp x19,lr,[sp] // save x19, lr sub sp,sp,#(framesz-16) // allocate the remaining local area
* Přidělení oblasti uložení regu není přeloženo do
stp
oblasti, protože předindexovaný reg-lrstp
nemůže být reprezentován kódy odvíjení.Všechna místní prostředí jsou přístupná na
sp
základě .<x29>
odkazuje na předchozí rámec.Zřetězený, #framesz <= 512, #outsz = 0
stp x29,lr,[sp,#-framesz]! // pre-indexed, save <x29,lr> mov x29,sp // x29 points to bottom of stack stp x19,x20,[sp,#(framesz-32)] // save INT pair stp d8,d9,[sp,#(framesz-16)] // save FP pair
Ve srovnání s prvním příkladem prologu výše má tento příklad výhodu: všechny pokyny k uložení registru jsou připravené ke spuštění po pouze jedné instrukci přidělení zásobníku. To znamená, že neexistuje žádná antizávislosti na
sp
tom, že brání paralelismu na úrovni instrukce.Zřetězení, velikost > rámce 512 (volitelné pro funkce bez
alloca
)stp x29,lr,[sp,#-80]! // pre-indexed, save <x29,lr> stp x19,x20,[sp,#16] // save in INT regs stp x21,x22,[sp,#32] // ... stp d8,d9,[sp,#48] // save in FP regs stp d10,d11,[sp,#64] mov x29,sp // x29 points to top of local area sub sp,sp,#(framesz-80) // allocate the remaining local area
Pro účely
x29
optimalizace je možné umístit na libovolnou pozici v místní oblasti, aby se zajistilo lepší pokrytí pro "reg-pair" a pre-/post-indexed offset adresní režim. Místní hodnoty pod ukazateli rámce lze přistupovat nasp
základě .Zřetězený, velikost > rámu 4K, s nebo bez alloca(),
stp x29,lr,[sp,#-80]! // pre-indexed, save <x29,lr> stp x19,x20,[sp,#16] // save in INT regs stp x21,x22,[sp,#32] // ... stp d8,d9,[sp,#48] // save in FP regs stp d10,d11,[sp,#64] mov x29,sp // x29 points to top of local area mov x15,#(framesz/16) bl __chkstk sub sp,sp,x15,lsl#4 // allocate remaining frame // end of prolog ... sub sp,sp,#alloca // more alloca() in body ... // beginning of epilog mov sp,x29 // sp points to top of local area ldp d10,d11,[sp,#64] ... ldp x29,lr,[sp],#80 // post-indexed, reload <x29,lr>
Informace o zpracování výjimek ARM64
.pdata
záznamy
Záznamy .pdata
jsou uspořádané pole položek s pevnou délkou, které popisují každou funkci manipulace se zásobníkem v binárním souboru PE. Fráze "manipulace se zásobníkem" je významná: funkce typu list, které nevyžadují žádné místní úložiště a nevyžadují žádné .pdata
místní úložiště, a nevyžadují záznam. Tyto záznamy by měly být explicitně vynechány, aby se ušetřilo místo. Odvíjení jedné z těchto funkcí může získat zpáteční adresu přímo od lr
volajícího.
Každý .pdata
záznam pro ARM64 má délku 8 bajtů. Obecný formát každého záznamu umístí 32bitovou RVA funkce začínat v prvním slově, za kterým následuje druhé slovo, které obsahuje ukazatel na blok s proměnnou délkou .xdata
, nebo zabalené slovo popisující kanonický sekvenci odvíjení funkce.
Pole jsou následující:
Funkce Start RVA je 32bitová hodnota RVA začátku funkce.
Příznak je 2bitové pole, které označuje, jak interpretovat zbývající 30 bitů druhého
.pdata
slova. Pokud je příznak 0, zbývající bity tvoří RVA informace o výjimce (se dvěma nejnižšími bity implicitně 0). Pokud je příznak nenulový, zbývající bity tvoří strukturu Packed Unwind Data .Informace o výjimce RVA je adresa struktury informací o výjimce proměnné délky uložená
.xdata
v části. Tato data musí být zarovnaná se 4 bajty.Zabalená data Unwind jsou komprimovaný popis operací potřebných k odvíjení funkce za předpokladu, že jde o kanonický formulář. V tomto případě se nevyžaduje žádný
.xdata
záznam.
.xdata
záznamy
Pokud zabalený formát unwind není dostatečný k popisu odvíjení funkce, je nutné vytvořit záznam o proměnné délce .xdata
. Adresa tohoto záznamu je uložena ve druhém slově záznamu .pdata
. .xdata
Formát je sada slov s zabalenou proměnnou délkou:
Tato data jsou rozdělená do čtyř částí:
Záhlaví 1 nebo 2 slov popisující celkovou velikost struktury a poskytnutí klíčových dat funkce. Druhé slovo je přítomné pouze v případě, že jsou pole Počet epilogu i Slova kódu nastavena na hodnotu 0. Záhlaví obsahuje tato bitová pole:
a. Délka funkce je 18bitové pole. Označuje celkovou délku funkce v bajtech rozdělenou 4. Pokud je funkce větší než 1M, musí se k popisu funkce použít více
.pdata
záznamů a.xdata
záznamy. Další informace najdete v části Velké funkce .b. Vers je 2bitové pole. Popisuje verzi zbývajícího
.xdata
souboru . V současné době je definována pouze verze 0, takže hodnoty 1–3 nejsou povolené.c. X je 1bitové pole. Označuje přítomnost (1) nebo nepřítomnost (0) údajů o výjimce.
d. E je 1bitové pole. Označuje, že informace popisující jeden epilog se zabalí do hlavičky (1) a nevyžadují více slov oboru později (0).
e. Epilog Count je 5bitové pole, které má dva významy v závislosti na stavu bitu E :
Pokud je E 0, určuje počet celkového počtu rozsahů epilogu popsaných v části 2. Pokud ve funkci existuje více než 31 oborů, musí být pole Code Words nastaveno na hodnotu 0, aby bylo možné určit, že je požadováno slovo rozšíření.
Pokud je E 1, pak toto pole určuje index prvního odvinutí kódu, který popisuje jeden a jediný epilog.
f. Code Words je 5bitové pole, které určuje počet 32bitových slov potřebných k zahrnutí všech kódů odvíjení v oddílu 3. Pokud je požadováno více než 31 slov (tj. 124 kódů odvíjení), musí být toto pole 0, aby bylo možné označit, že je požadováno slovo přípony.
g. Rozšířené počty epilogů a rozšířená slova kódu jsou 16bitová a 8bitová pole. Poskytují více místa pro kódování neobvykle velkého počtu epilogů nebo neobvykle velkého počtu odvíjecích slov kódu. Slovo rozšíření, které obsahuje tato pole, je přítomné pouze v případě, že pole Epilog Count (Počet epilogu) a Code Words (Slova kódu) v prvním záhlaví jsou 0.
Pokud počet epilogů není nulový, zobrazí se seznam informací o oborech epilogu, který je zabalený na slovo, přijde za záhlavím a volitelnou rozšířenou hlavičkou. Ukládají se v pořadí zvýšení počátečního posunu. Každý obor obsahuje následující bity:
a. Počáteční posun epilogu je 18bitové pole, které má posun v bajtech dělené 4, epilogu vzhledem ke začátku funkce.
b. Res je 4bitové pole vyhrazené pro budoucí rozšíření. Jeho hodnota musí být 0.
c. Počáteční index epilogu je 10bitové pole (více než 2 bity než rozšířená slova kódu). Označuje bajtový index prvního odvíjení kódu, který popisuje tento epilog.
Jakmile seznam oborů epilogu přijde na pole bajtů, které obsahují odvíjecí kódy, popsané podrobně v další části. Toto pole je vycpané na konci k nejbližší hranici celého slova. Kódy unwind jsou zapsány do tohoto pole. Začínají tím, co je nejblíže k těle funkce, a pohybují se směrem k okrajům funkce. Bajty pro každý odvinutý kód jsou uloženy ve velkém pořadí, takže nejvýznamnější bajt se načte jako první, který identifikuje operaci a délku zbytku kódu.
Nakonec po odvinutí bajtů kódu, pokud byl bit X v hlavičce nastaven na hodnotu 1, přijde informace obslužné rutiny výjimky. Skládá se z jediné obslužné rutiny výjimky RVA , která poskytuje adresu samotné obslužné rutiny výjimky. Následuje okamžitě množství dat vyžadovaných obslužnou rutinou výjimky s proměnlivou délkou.
Záznam .xdata
je navržený tak, aby bylo možné načíst prvních 8 bajtů a použít je k výpočtu plné velikosti záznamu, minus délku dat výjimky s proměnlivou velikostí, která následuje. Následující fragment kódu vypočítá velikost záznamu:
ULONG ComputeXdataSize(PULONG Xdata)
{
ULONG Size;
ULONG EpilogScopes;
ULONG UnwindWords;
if ((Xdata[0] >> 22) != 0) {
Size = 4;
EpilogScopes = (Xdata[0] >> 22) & 0x1f;
UnwindWords = (Xdata[0] >> 27) & 0x1f;
} else {
Size = 8;
EpilogScopes = Xdata[1] & 0xffff;
UnwindWords = (Xdata[1] >> 16) & 0xff;
}
if (!(Xdata[0] & (1 << 21))) {
Size += 4 * EpilogScopes;
}
Size += 4 * UnwindWords;
if (Xdata[0] & (1 << 20)) {
Size += 4; // Exception handler RVA
}
return Size;
}
I když prolog a každý epilog má svůj vlastní index do kódů odvíjení, tabulka se mezi nimi sdílí. Je to zcela možné (a ne úplně neobvyklé), že můžou všechny sdílet stejné kódy. (Příklad viz příklad 2 v části Příklady oddílu.) Pro tento případ by se měly optimalizovat zejména zapisovače kompilátoru. Je to proto, že největší index, který lze zadat, je 255, což omezuje celkový počet kódů odvíjení pro konkrétní funkci.
Unwind codes
Pole odvíjecích kódů je fond sekvencí, které přesně popisují, jak vrátit zpět účinky prologu. Ukládají se ve stejném pořadí, v jakém musí být operace vráceny. Kódy unwind lze považovat za malou instrukční sadu kódovanou jako řetězec bajtů. Po dokončení provádění se návratová adresa volající funkce nachází v lr
registru. A všechny nevolatelní registry se obnoví na jejich hodnoty v době, kdy byla funkce volána.
Pokud by výjimky byly zaručeny pouze někdy v těle funkce, a nikdy v prologu nebo v žádném epilogu, bude nutné pouze jednu sekvenci. Model odvíjení windows však vyžaduje, aby se kód mohl odvíjet od částečně spuštěného prologu nebo epilogu. Pro splnění tohoto požadavku byly kódy unwind pečlivě navrženy, aby jednoznačně mapovaly 1:1 na každý relevantní opcode v prologu a epilogu. Tento návrh má několik důsledků:
Když spočítáte počet kódů odvíjení, je možné vypočítat délku prologu a epilogu.
Když spočítáte počet instrukcí po začátku oboru epilogu, je možné přeskočit ekvivalentní počet odvíjených kódů. Zbytek sekvence můžeme provést, abychom dokončili částečně spuštěné odvíjení provedené epilogem.
Když spočítáte počet instrukcí před koncem prologu, je možné přeskočit ekvivalentní počet odvíjených kódů. Zbytek sekvence můžeme spustit a vrátit zpět pouze ty části prologu, které dokončily provádění.
Kódy unwind jsou kódovány podle následující tabulky. Všechny kódy odvíjení jsou jeden/dvojitý bajt s výjimkou těch, které přidělují obrovský zásobník (alloc_l
). Celkem je 22 kódů odvíjení. Každý odvíjecí kód mapuje přesně jednu instrukci v prologu/epilogu, aby bylo možné uvolnit částečně spuštěné prology a epilogy.
Unwind code | Bity a interpretace |
---|---|
alloc_s |
000xxxxx: přidělit malý zásobník s velikostí < 512 (2^5 * 16). |
save_r19r20_x |
001zzzzzzz: save <x19,x20> pair at [sp-#Z*8]! , pre-indexed offset >= -248 |
save_fplr |
01zzzzzz: save <x29,lr> pair at [sp+#Z*8] , offset <= 504. |
save_fplr_x |
10zzzzzz: save <x29,lr> pair at [sp-(#Z+1)*8]! , pre-indexed offset >= -512 |
alloc_m |
11000xxx'xxxxxxxx: přidělení velkého zásobníku s velikostí < 32 K (2^11 * 16). |
save_regp |
110010xx'xxzzzzzz: save x(19+#X) pair at [sp+#Z*8] , offset <= 504 |
save_regp_x |
110011xx'xxzzzzzz: save pair x(19+#X) at [sp-(#Z+1)*8]! , pre-indexed offset >= -512 |
save_reg |
110100xx'xxzzzzzz: save reg x(19+#X) at [sp+#Z*8] , offset <= 504 |
save_reg_x |
1101010x'xxxzzzzz: save reg x(19+#X) at [sp-(#Z+1)*8]! , pre-indexed offset >= -256 |
save_lrpair |
1101011x'xxzzzzzz: save pair <x(19+2*#X),lr> at [sp+#Z*8] , offset <= 504 |
save_fregp |
1101100x'xxzzzzzz: save pair d(8+#X) at [sp+#Z*8] , offset <= 504 |
save_fregp_x |
1101101x'xxzzzzzz: save pair d(8+#X) at [sp-(#Z+1)*8]! , pre-indexed offset >= -512 |
save_freg |
1101110x'xxzzzzzz: save reg d(8+#X) at [sp+#Z*8] , offset <= 504 |
save_freg_x |
11011110'xxxzzzzz: save reg d(8+#X) at [sp-(#Z+1)*8]! , pre-indexed offset >= -256 |
alloc_l |
11100000'xxxxxxxx'xxxxxxxx'xxxxxxxx:přidělení velkého zásobníku s velikostí < 256M (2^24 * 16) |
set_fp |
11100001: nastavení x29 pomocí mov x29,sp |
add_fp |
11100010'xxxxxxxx: nastaveno x29 pomocí add x29,sp,#x*8 |
nop |
11100011: Nevyžaduje se žádná operace odvíjení. |
end |
11100100: konec odvíjeného kódu. Implikuje ret to v epilogu. |
end_c |
11100101: konec odvíjeného kódu v aktuálním zřetězeným oboru. |
save_next |
11100110: Uložte další nevolatilní dvojici Int nebo FP register. |
11100111: rezervováno | |
11101xxx: Vyhrazeno pro vlastní případy zásobníku níže generované pouze pro rutiny asm | |
11101000: Vlastní zásobník pro MSFT_OP_TRAP_FRAME |
|
11101001: Vlastní zásobník pro MSFT_OP_MACHINE_FRAME |
|
11101010: Vlastní zásobník pro MSFT_OP_CONTEXT |
|
11101011: Vlastní zásobník pro MSFT_OP_EC_CONTEXT |
|
11101100: Vlastní zásobník pro MSFT_OP_CLEAR_UNWOUND_TO_CALL |
|
11101101: rezervováno | |
11101110: rezervováno | |
11101111: rezervováno | |
11110xxx: rezervováno | |
11111000'yyy: reserved | |
11111001'y'y: reserved | |
11111010'y'y'y: reserved | |
11111011'y'y'y'y: reserved | |
pac_sign_lr |
11111100: Podepište zpáteční adresu pomocí lr pacibsp |
11111101: rezervováno | |
11111110: rezervováno | |
11111111: rezervováno |
V pokynech s velkými hodnotami, které pokrývají více bajtů, jsou nejdůležitější bity uloženy jako první. Tento návrh umožňuje najít celkovou velikost v bajtech odvíjeného kódu vyhledáním pouze prvního bajtu kódu. Vzhledem k tomu, že každý kód odvíjení je přesně namapován na instrukce v prologu nebo epilogu, můžete vypočítat velikost prologu nebo epilogu. Projděte si posloupnost od začátku do konce a pomocí vyhledávací tabulky nebo podobného zařízení určete délku odpovídajícího operačního kódu.
Adresování posunu po indexu není v prologu povolené. Všechny rozsahy posunu (#Z) odpovídají kódování stp
/str
adres s výjimkou save_r19r20_x
případů, kdy 248 je dostačující pro všechny oblasti úspory (10 Int registrů + 8 FP registrů + 8 vstupních registrů).
save_next
musí dodržovat uložení páru int nebo FP volatile register: , , , save_fregp_x
, , save_r19r20_x
nebo jiné save_next
. save_fregp
save_regp_x
save_regp
Uloží další dvojici registrů v dalším 16 bajtovém slotu v "rostoucím" pořadí. A save_next
odkazuje na první pár registrace FP, když následuje save-next
, který označuje poslední dvojici Int register.
Vzhledem k tomu, že velikosti pravidelných návratových a přeskakovaných instrukcí jsou stejné, není nutné oddělit odvíjecí end
kód ve scénářích tail-call.
end_c
je navržený tak, aby zpracovával nesouvislé fragmenty funkcí pro účely optimalizace. Za end_c
koncem odvíjecích kódů v aktuálním rozsahu musí následovat další řada odvíjecích kódů končících skutečným end
kódem . Kódy odvíjení mezi end_c
a end
představují operace prologu v nadřazené oblasti ("fantomový" prolog). Další podrobnosti a příklady jsou popsány v následující části.
Zabalená data odvíjení
Pro funkce, jejichž prology a epilogy se řídí kanonickým formulářem popsaným níže, lze použít zabalená data unwind. Eliminuje potřebu záznamu .xdata
zcela a výrazně snižuje náklady na poskytování odvíjecích dat. Kanonické prology a epilogy jsou navrženy tak, aby splňovaly běžné požadavky jednoduché funkce: Jedna, která nevyžaduje obslužnou rutinu výjimky a která provede operace nastavení a odstranění ve standardním pořadí.
Formát záznamu .pdata
s zabalenými daty unwind vypadá takto:
Pole jsou následující:
- Funkce Start RVA je 32bitová hodnota RVA začátku funkce.
- Příznak je 2bitové pole, jak je popsáno výše, s následujícími významy:
- 00 = zabalená unwindová data se nepoužívají; zbývající bity ukazují na
.xdata
záznam - 01 = zabalená unwind data používaná s jedním prologem a epilogem na začátku a na konci oboru
- 10 = zabalená unwind data použitá pro kód bez jakéhokoli prologu a epilogu. Užitečné pro popis oddělených segmentů funkcí
- 11 = rezervováno.
- 00 = zabalená unwindová data se nepoužívají; zbývající bity ukazují na
- Délka funkce je 11bitové pole poskytující délku celé funkce v bajtech děleno 4. Pokud je funkce větší než 8 tisíc, je nutné místo toho použít úplný
.xdata
záznam. - Velikost rámce je 9bitové pole označující počet bajtů zásobníku, který je přidělen pro tuto funkci, dělený číslem 16. Funkce, které přidělují více než (8k-16) bajtů zásobníku, musí používat úplný
.xdata
záznam. Zahrnuje oblast místních proměnných, oblast odchozích parametrů, oblast volaného int a oblast FP a domovskou oblast parametrů. Vyloučí oblast dynamického přidělování. - CR je 2bitový příznak označující, jestli funkce obsahuje další pokyny k nastavení řetězu rámu a návratového propojení:
- 00 = nechainovaná funkce,
<x29,lr>
pár není uložen v zásobníku - 01 = unchained function,
<lr>
is saved in stack - 10 = zřetězený funkce se podepsanou
pacibsp
zpáteční adresou - 11 = zřetězená funkce, instrukce páru úložiště/načtení se používá v prologu/epilogu.
<x29,lr>
- 00 = nechainovaná funkce,
- H je 1bitový příznak označující, jestli funkce ukládá celočíselné registry (x0-x7) jejich uložením na začátku funkce. (0 = nemá domácí registry, 1 = registry domů).
- RegI je 4bitové pole označující počet nevolatilních registrů INT (x19-x28) uložených v kanonickém umístění zásobníku.
- RegF je 3bitové pole označující počet nevolatilních registrů FP (d8-d15) uložených v kanonickém umístění zásobníku. (RegF=0: neuloží se žádný registr FP; RegF>0: Registru RegF+1 FP se uloží. Zabalená data odvíjení se nedají použít pro funkci, která ukládá jenom jeden registr FP.
Kanonické prology, které spadají do kategorií 1, 2 (bez oblasti výstupních parametrů), 3 a 4 v oddílu výše mohou být reprezentovány zabaleným unwind formátem. Epilogy pro kanonické funkce se řídí podobným formulářem, s výjimkou H nemá žádný vliv, set_fp
instrukce se vynechá a pořadí kroků a pokyny v každém kroku jsou v epilogu obrácené. Algoritmus pro zabalení .xdata
se řídí následujícími kroky, které jsou podrobně popsané v následující tabulce:
Krok 0: Předem vypočítá velikost každé oblasti.
Krok 1: Podepište zpáteční adresu.
Krok 2: Uložení volaných registrů volaných volaných
Krok 3: Tento krok je specifický pro typ 4 v počátečních částech. lr
je uložen na konci oblasti Int.
Krok 4: Uložení volaných registrů FP
Krok 5: Uložení vstupních argumentů v oblasti domovského parametru
Krok 6: Přidělení zbývajícího zásobníku, včetně místní oblasti, <x29,lr>
páru a oblasti výstupních parametrů 6a odpovídá kanonickému typu 1. 6b a 6c jsou určené pro kanonický typ 2. 6d a 6e jsou pro typ 3 i typ 4.
Krok # | Hodnoty příznaku | # of instructions | Opcode | Unwind code |
---|---|---|---|---|
0 | #intsz = RegI * 8; if (CR==01) #intsz += 8; // lr #fpsz = RegF * 8; if(RegF) #fpsz += 8; #savsz=((#intsz+#fpsz+8*8*H)+0xf)&~0xf) #locsz = #famsz - #savsz |
|||
1 | CR == 10 | 1 | pacibsp |
pac_sign_lr |
2 | 0 <RegI<= 10 | RegI / 2 + RegI % 2 |
stp x19,x20,[sp,#savsz]! stp x21,x22,[sp,#16] ... |
save_regp_x save_regp ... |
3 | CR == 01* | 1 | str lr,[sp,#(intsz-8)] * |
save_reg |
4 | 0 <RegF<= 7 | (RegF + 1) / 2 + (RegF + 1) % 2) |
stp d8,d9,[sp,#intsz] **stp d10,d11,[sp,#(intsz+16)] ... str d(8+RegF),[sp,#(intsz+fpsz-8)] |
save_fregp ... save_freg |
5 | H == 1 | 4 | stp x0,x1,[sp,#(intsz+fpsz)] stp x2,x3,[sp,#(intsz+fpsz+16)] stp x4,x5,[sp,#(intsz+fpsz+32)] stp x6,x7,[sp,#(intsz+fpsz+48)] |
nop nop nop nop |
6a | (CR == 10 || CR == 11) &&#locsz <= 512 |
2 | stp x29,lr,[sp,#-locsz]! mov x29,sp *** |
save_fplr_x set_fp |
6b | (CR == 10 || CR == 11) && 512 < #locsz <= 4080 |
3 | sub sp,sp,#locsz stp x29,lr,[sp,0] add x29,sp,0 |
alloc_m save_fplr set_fp |
6c | (CR == 10 || CR == 11) &&#locsz > 4080 |
4 | sub sp,sp,4080 sub sp,sp,#(locsz-4080) stp x29,lr,[sp,0] add x29,sp,0 |
alloc_m alloc_s /alloc_m save_fplr set_fp |
6d | (CR == 00 || CR == 01) &&#locsz <= 4080 |
0 | sub sp,sp,#locsz |
alloc_s /alloc_m |
6e | (CR == 00 || CR == 01) &&#locsz > 4080 |
2 | sub sp,sp,4080 sub sp,sp,#(locsz-4080) |
alloc_m alloc_s /alloc_m |
* Pokud CR == 01 a RegI je liché číslo, krok 3 a poslední save_reg
v kroku 2 se sloučí do jednoho save_regp
.
** Pokud regI == CR == 0 a RegF != 0, stp
první pro plovoucí desetinnou čárku provede předběžné zvýšení.
V epilogu se nenachází žádná instrukce odpovídající mov x29,sp
. Zabalená data unwind nelze použít, pokud funkce vyžaduje obnovení sp
z x29
.
Uvolnění částečných prologů a epilogů
V nejběžnějších situacích odvíjení dojde k výjimce nebo volání v těle funkce, mimo prolog a všechny epilogy. V těchto situacích je odvíjení jednoduché: unwinder jednoduše provede kódy v poli unwind. Začíná indexem 0 a pokračuje, dokud end
se nezjistí opcode.
V případě, že při provádění prologu nebo epilogu dojde k výjimce nebo přerušení, je obtížnější se správně uvolnit. V těchto situacích je rám zásobníku vytvořen pouze částečně. Problémem je přesně určit, co bylo provedeno, aby se správně vrátil zpět.
Například tuto posloupnost prologu a epilogu:
0000: stp x29,lr,[sp,#-256]! // save_fplr_x 256 (pre-indexed store)
0004: stp d8,d9,[sp,#224] // save_fregp 0, 224
0008: stp x19,x20,[sp,#240] // save_regp 0, 240
000c: mov x29,sp // set_fp
...
0100: mov sp,x29 // set_fp
0104: ldp x19,x20,[sp,#240] // save_regp 0, 240
0108: ldp d8,d9,[sp,224] // save_fregp 0, 224
010c: ldp x29,lr,[sp],#256 // save_fplr_x 256 (post-indexed load)
0110: ret lr // end
Vedle každého opcode je vhodný odvíjecí kód popisující tuto operaci. Můžete vidět, jak série odvíjecí kódy prolog je přesný zrcadlový obraz odvíjecí kódy pro epilog (bez počítání konečné instrukce epilogu). Je to běžná situace: Proto vždy předpokládáme, že kódy prologu prowind jsou uložené v opačném pořadí než pořadí provádění prologu.
Takže pro prolog i epilog jsme zůstali se společnou sadou kódů odvíjení:
set_fp
, save_regp 0,240
, save_fregp,0,224
, , save_fplr_x_256
end
Případ epilogu je jednoduchý, protože je v normálním pořadí. Počínaje posunem 0 v epilogu (který začíná na posunu 0x100 ve funkci), očekáváme, že se spustí úplná sekvence odvíjení, protože ještě nebylo provedeno žádné vyčištění. Pokud zjistíme, že máme jednu instrukci (na posunu 2 v epilogu), můžeme úspěšně odvinout přeskočením prvního odvinutí kódu. Tuto situaci můžeme generalizovat a předpokládat mapování 1:1 mezi opcodes a unwind kódy. Potom, abychom mohli začít odvíjení od instrukce n v epilogu, měli bychom přeskočit první n odvíjecí kódy a začít spouštět odsud.
Ukázalo se, že prolog funguje podobná logika, s výjimkou obrácené. Pokud začneme odvíjení od posunu 0 v prologu, chceme provést nic. Pokud odpovítáme od posunu 2, což je jedna instrukce, chceme začít spouštět sekvenci odvíjení jednoho odvíjecího kódu od konce. (Nezapomeňte, že kódy jsou uloženy v obráceném pořadí.) A tady můžeme také generalizovat: pokud začneme odvíjení z instrukce n v prologu, měli bychom začít spouštět n unwind kódy od konce seznamu kódů.
Kódy prologu a epilogu se vždy neshodují přesně, proto může být nutné pole unwind obsahovat několik sekvencí kódů. K určení posunu místa, kde začít zpracovávat kódy, použijte následující logiku:
Pokud se odvíjeje od těla funkce, začněte spouštět odvíjecí kódy v indexu 0 a pokračujte, dokud nenasáhnete
end
opcode.Pokud se odvíjeje od epilogu, použijte počáteční index specifický pro epilog, který je součástí oboru epilogu jako výchozí bod. Vypočítá, kolik bajtů je příslušný počítač od začátku epilogu. Pak pokračujte vpřed přes kódy odvíjení a přeskočte kódy odvíjení, dokud se nezapočítávají všechny již provedené pokyny. Pak spusťte spuštění od tohoto okamžiku.
Pokud se odvíjeje z prologu, použijte jako výchozí bod index 0. Vypočítá délku kódu prologu ze sekvence a pak vypočítá, kolik bajtů je příslušný počítač od konce prologu. Pak pokračujte vpřed přes kódy unwind a přeskočte kódy odvíjení, dokud nebudou zohledněny všechny dosud provedené pokyny. Pak spusťte spuštění od tohoto okamžiku.
Tato pravidla znamenají, že kódy prologu odvíjení musí být vždy první v poli. A také kódy používané k odvíjení v obecném případě odvíjení z těla. Všechny sekvence kódu specifické pro epilog by měly následovat hned po.
Fragmenty funkcí
Pro účely optimalizace kódu a další důvody může být vhodnější rozdělit funkci na oddělené fragmenty (označované také jako oblasti). Při rozdělení vyžaduje každý výsledný fragment funkce vlastní samostatný .pdata
(a případně .xdata
) záznam.
U každého odděleného sekundárního fragmentu, který má svůj vlastní prolog, se očekává, že se v jeho prologu neprovede žádná úprava zásobníku. Veškerý prostor zásobníku vyžadovaný sekundární oblastí musí být předem přidělen nadřazenou oblastí (nebo označovaný jako hostitelská oblast). Tato preallocation udržuje manipulaci s ukazateli zásobníku výhradně v původním prologu funkce.
Typickým případem fragmentů funkcí je oddělení kódu, kde kompilátor může přesunout oblast kódu z hostitelské funkce. Existují tři neobvyklé případy, které by mohly mít za následek oddělení kódu.
Příklad
(oblast 1: začátek)
stp x29,lr,[sp,#-256]! // save_fplr_x 256 (pre-indexed store) stp x19,x20,[sp,#240] // save_regp 0, 240 mov x29,sp // set_fp ...
(oblast 1: konec)
(oblast 3: začátek)
...
(oblast 3: konec)
(oblast 2: začátek)
... mov sp,x29 // set_fp ldp x19,x20,[sp,#240] // save_regp 0, 240 ldp x29,lr,[sp],#256 // save_fplr_x 256 (post-indexed load) ret lr // end
(oblast 2: konec)
Pouze prolog (oblast 1: všechny epilogy jsou v oddělených oblastech):
Musí být popsán pouze prolog. Tento prolog nemůže být reprezentován v kompaktním
.pdata
formátu. V plném.xdata
případě to může být reprezentováno nastavením Epilog Count = 0. Viz oblast 1 v příkladu výše.Odvíjení kódů:
set_fp
,save_regp 0,240
,save_fplr_x_256
,end
.Pouze epilogy (oblast 2: Prolog je v hostitelské oblasti)
Předpokládá se, že ovládací prvek času přeskočí do této oblasti, všechny kódy prologu byly provedeny. Částečné odvíjení může probíhat v epilogech stejným způsobem jako v normální funkci. Tento typ oblasti nemůže být reprezentován kompaktním
.pdata
. V úplném.xdata
záznamu lze kódovat pomocí "fantomového" prologu, který je závorkou páruend_c
kódu aend
odvíjejený. Úvodníend_c
hodnota značí, že velikost prologu je nula. Počáteční index epilogu jednoho epilogu odkazuje naset_fp
.Unwind code for region 2:
end_c
,set_fp
,save_regp 0,240
, ,save_fplr_x_256
, .end
Žádné prology ani epilogy (oblast 3: prology a všechny epilogy jsou v jiných fragmentech):
Kompaktní
.pdata
formát lze použít pomocí nastavení Příznak = 10. S úplným.xdata
záznamem, Epilog Count = 1. Unwind kód je stejný jako kód pro oblast 2 výše, ale Epilog Start Index také odkazuje naend_c
. V této oblasti kódu nikdy nedojde k částečnému odvíjení.
Dalším složitějším případem fragmentů funkcí je "obtékání zmenšení". Kompilátor se může rozhodnout zpozdit ukládání některých volaných registrů, dokud se neschová položka funkce.
(oblast 1: začátek)
stp x29,lr,[sp,#-256]! // save_fplr_x 256 (pre-indexed store) stp x19,x20,[sp,#240] // save_regp 0, 240 mov x29,sp // set_fp ...
(oblast 2: začátek)
stp x21,x22,[sp,#224] // save_regp 2, 224 ... ldp x21,x22,[sp,#224] // save_regp 2, 224
(oblast 2: konec)
... mov sp,x29 // set_fp ldp x19,x20,[sp,#240] // save_regp 0, 240 ldp x29,lr,[sp],#256 // save_fplr_x 256 (post-indexed load) ret lr // end
(oblast 1: konec)
V prologu oblasti 1 je prostor zásobníku předem přidělený. Uvidíte, že oblast 2 bude mít stejný odvíjecí kód, i když se přesune mimo svou hostitelskou funkci.
Oblast 1: set_fp
, save_regp 0,240
, save_fplr_x_256
, end
. Epilog Start Index odkazuje jako set_fp
obvykle.
Oblast 2: save_regp 2, 224
, end_c
, set_fp
, save_regp 0,240
, save_fplr_x_256
, , end
. Epilog Start Index odkazuje na první odvíjení kódu save_regp 2, 224
.
Velké funkce
Fragmenty lze použít k popisu funkcí větších než limit 1M uložený bitovými poli v .xdata
hlavičce. Aby bylo možné popsat neobvykle velkou funkci, je potřeba ji rozdělit na fragmenty menší než 1 M. Každý fragment by měl být upraven tak, aby nerozdělil epilog na více kusů.
Pouze první fragment funkce bude obsahovat prolog; všechny ostatní fragmenty jsou označené jako bez prologu. V závislosti na počtu výskytů epilogů může každý fragment obsahovat nula nebo více epilogů. Mějte na paměti, že každý rozsah epilogu v fragmentu určuje počáteční posun vzhledem ke začátku fragmentu, nikoli začátek funkce.
Pokud fragment nemá žádný prolog a žádný epilog, stále vyžaduje vlastní .pdata
(a případně .xdata
) záznam, který popisuje, jak se odvinout z těla funkce.
Příklady
Příklad 1: Zřetězený rámec, kompaktní tvar
|Foo| PROC
|$LN19|
str x19,[sp,#-0x10]! // save_reg_x
sub sp,sp,#0x810 // alloc_m
stp fp,lr,[sp] // save_fplr
mov fp,sp // set_fp
// end of prolog
...
|$pdata$Foo|
DCD imagerel |$LN19|
DCD 0x416101ed
;Flags[SingleProEpi] functionLength[492] RegF[0] RegI[1] H[0] frameChainReturn[Chained] frameSize[2080]
Příklad 2: Rámeček zřetězený, plný formulář se zrcadlem Prolog & Epilog
|Bar| PROC
|$LN19|
stp x19,x20,[sp,#-0x10]! // save_regp_x
stp fp,lr,[sp,#-0x90]! // save_fplr_x
mov fp,sp // set_fp
// end of prolog
...
// begin of epilog, a mirror sequence of Prolog
mov sp,fp
ldp fp,lr,[sp],#0x90
ldp x19,x20,[sp],#0x10
ret lr
|$pdata$Bar|
DCD imagerel |$LN19|
DCD imagerel |$unwind$cse2|
|$unwind$Bar|
DCD 0x1040003d
DCD 0x1000038
DCD 0xe42291e1
DCD 0xe42291e1
;Code Words[2], Epilog Count[1], E[0], X[0], Function Length[6660]
;Epilog Start Index[0], Epilog Start Offset[56]
;set_fp
;save_fplr_x
;save_r19r20_x
;end
Počáteční index epilogu [0] odkazuje na stejnou sekvenci kódu prologu.
Příklad 3: Variadic unchained – funkce
|Delegate| PROC
|$LN4|
sub sp,sp,#0x50
stp x19,lr,[sp]
stp x0,x1,[sp,#0x10] // save incoming register to home area
stp x2,x3,[sp,#0x20] // ...
stp x4,x5,[sp,#0x30]
stp x6,x7,[sp,#0x40] // end of prolog
...
ldp x19,lr,[sp] // beginning of epilog
add sp,sp,#0x50
ret lr
AREA |.pdata|, PDATA
|$pdata$Delegate|
DCD imagerel |$LN4|
DCD imagerel |$unwind$Delegate|
AREA |.xdata|, DATA
|$unwind$Delegate|
DCD 0x18400012
DCD 0x200000f
DCD 0xe3e3e3e3
DCD 0xe40500d6
DCD 0xe40500d6
;Code Words[3], Epilog Count[1], E[0], X[0], Function Length[18]
;Epilog Start Index[4], Epilog Start Offset[15]
;nop // nop for saving in home area
;nop // ditto
;nop // ditto
;nop // ditto
;save_lrpair
;alloc_s
;end
Počáteční index epilogu [4] odkazuje na střed kódu prologu odvíjení (částečně znovu použít pole unwind).