Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Windows på ARM64 använder samma strukturerade undantagshanteringsmekanism för asynkrona maskinvarugenererade undantag och synkrona programvarugenererade undantag. Språkspecifika undantagshanterare bygger på Windows strukturerad undantagshantering med hjälp av språkhjälpfunktioner. I det här dokumentet beskrivs undantagshantering i Windows på ARM64. Den illustrerar de språkhjälpare som används av kod som genereras av Microsoft ARM-monteringsverktyget och MSVC-kompilatorn.
Mål och motivation
Avvecklingsdatakonventioner för undantag, och den här beskrivningen, är avsett att vara:
Ange tillräckligt med beskrivning för att möjliggöra avveckling utan kodanalys i alla fall.
För att analysera koden måste koden pagineras in. Det förhindrar avspolning i vissa fall där det är användbart (spårning, sampling, felsökning).
Det är komplext att analysera koden. kompilatorn måste vara noga med att endast generera instruktioner som avkodaren kan avkoda.
Om avspolning inte kan beskrivas fullständigt med hjälp av avspolningskoder måste den i vissa fall återgå till instruktionsdekodning. Avkodning av instruktioner ökar den övergripande komplexiteten och bör helst undvikas.
Stöd för att varva ned i mitten av prolog och mitt i epilog.
- Upprullning används i Windows för mer än undantagshantering. Det är viktigt att koden kan varva ned korrekt även när den är mitt i en prolog- eller epilogkodsekvens.
Ta upp en minimal mängd utrymme.
Varva ned-koderna får inte aggregeras för att avsevärt öka binärstorleken.
Eftersom unwind-koderna sannolikt kommer att vara låsta i minnet, garanterar ett litet fotavtryck minimala omkostnader för varje inläst binär.
Antaganden
Dessa antaganden görs i undantagshanteringsbeskrivningen:
Prologer och epiloger tenderar att spegla varandra. Genom att dra nytta av detta gemensamma drag kan metadata-storleken som behövs för att beskriva upprullning minskas betydligt. I funktionens brödtext spelar det ingen roll om prologens åtgärder ångras eller om epilogens åtgärder utförs på ett framåtblickande sätt. Båda bör ge identiska resultat.
Funktioner tenderar på det hela taget att vara relativt små. Flera optimeringar för utrymme förlitar sig på detta faktum för att uppnå den mest effektiva paketeringen av data.
Det finns ingen villkorlig kod i epiloger.
Register för dedikerad rampekare: Om
sp
sparas i ett annat register (x29
) i prologen förblir det registret orört i hela funktionen. Det innebär att den ursprungligasp
kan återställas när som helst.Om inte
sp
sparas i ett annat register sker all manipulering av stackpekaren strikt inom prolog och epilog.Layouten för stackramen är organiserad enligt beskrivningen i nästa avsnitt.
Layout för ARM64-stackram
För ramlänkade funktioner kan fp
och lr
par sparas på valfri plats i det lokala variabelområdet, beroende på optimeringsöverväganden. Målet är att maximera antalet lokala platser som kan nås med en enda instruktion baserat på bildrutepekaren (x29
) eller stackpekaren (sp
). För alloca
-funktioner måste denna dock vara kedjad och x29
måste peka längst ned i stacken. För att möjliggöra bättre täckning för register-pair-addressing-mode placeras icke-volatila registersparområden överst i den lokala områdesstacken. Här är exempel som illustrerar flera av de mest effektiva prologsekvenserna. För tydlighetens skull och bättre cache-lokalitet är ordningen för att lagra callee-sparade register i alla kanoniska prologer i "stigande" ordning.
#framesz
nedan representerar storleken på hela stacken (exklusive alloca
område).
#localsz
och #outsz
anger lokal områdesstorlek (inklusive sparandeområdet för <x29, lr>
paret) respektive utgående parameterstorlek.
Kedjad, #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)
Kedjad, #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
Okopplad, bladfunktioner (
lr
osparad)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
Alla lokalbefolkningen nås baserat på
sp
.<x29,lr>
pekar på föregående bildruta. För bildrutestorleken <= 512 kansub sp, ...
optimeras bort om det sparade regs-området flyttas till botten av stacken. Nackdelen är att den inte är konsekvent med andra layouter ovan. Och sparade register tar del av intervallet för parregister och för- och postindexerat adresseringsläge.Ohämmade funktioner som inte är löv (sparar
lr
i int sparat område)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
Eller, med jämnt antal sparade Int-register,
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
Endast
x19
har sparats: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
* Allokeringen av registersparområde är inte inkluderad i
stp
eftersom en förindexerad reg-lrstp
inte kan representeras med unwind-koder.Alla lokalbefolkningen nås baserat på
sp
.<x29>
pekar på föregående bildruta.Kedjad, #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
Jämfört med det första exemplet ovan har det här exemplet en fördel: alla instruktioner för att spara register är redo att köras efter endast en stackallokeringsinstruktion. Det innebär att det inte finns något beroende av
sp
som förhindrar parallellitet på instruktionsnivå.Kedjad, ramstorlek är > 512 (valfritt för funktioner utan
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
För optimeringsändamål kan
x29
placeras var som helst i det lokala området för att få bättre täckning för "reg-pair" och för-/postindexerat förskjutningsläge. Lokala variabler under bildrutepekare kan nås baserat påsp
.Kedjad, ramstorlek > 4K, med eller utan 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>
INFORMATION om ARM64-undantagshantering
.pdata
register
De .pdata
posterna är en ordnad matris med objekt med fast längd som beskriver varje stackmanipuleringsfunktion i en PE-binär. Frasen "stackhantering" är betydande: lövfunktioner som inte kräver någon lokal lagring och inte behöver spara/återställa icke-flyktiga register, kräver inte en .pdata
-uppgift. Dessa poster bör medvetet utelämnas för att spara utrymme. En avkoppling från någon av dessa funktioner kan hämta returadressen direkt från lr
för att flytta upp till anroparen.
Varje .pdata
post för ARM64 är 8 byte lång. Det allmänna formatet för varje post placerar 32-bitars RVA för startpunkten av funktionen i det första ordet, följt av ett andra ord som innehåller antingen en pekare till ett .xdata
-block med variabel längd, eller ett packat ord som beskriver en kanonisk funktion som avvecklar sekvensen.
Fälten är följande:
Funktionen startar RVA är 32-bitars RVA för starten av funktionen.
Flagga är ett 2-bitars fält som anger hur du tolkar de återstående 30 bitarna av det andra
.pdata
ordet. Om Flagga är 0, bildar de återstående bitarna en Undantagsinformations-RVA (med de två lägsta bitarna implicit 0). Om Flagga inte är noll, bildar de återstående bitarna en Packad återställningsdatastruktur.Undantagsinformation RVA är adressen till informationsstrukturen för undantag med variabel längd som lagras i avsnittet
.xdata
. Dessa data måste vara 4 byte justerade.Packed Unwind Data är en komprimerad beskrivning av de åtgärder som krävs för att avlägsna sig från en funktion, i en kanonisk form. I det här fallet krävs ingen
.xdata
-post.
.xdata
poster
När det kompakta avrullningsformatet inte är tillräckligt för att beskriva stackavrullningen av en funktion måste en .xdata
-post med variabel längd skapas. Adressen för denna post lagras i det andra dataordet i .pdata
-posten. Formatet för .xdata
är en paketerad uppsättning ord med variabel längd:
Dessa data är uppdelade i fyra avsnitt:
En rubrik på 1 eller 2 ord som beskriver strukturens övergripande storlek och tillhandahåller nyckelfunktionsdata. Det andra ordet finns bara om fälten Epilog Count och Code Words anges till 0. Rubriken har följande bitfält:
a. funktionslängd är ett 18-bitarsfält. Det anger den totala längden på funktionen i byte, dividerat med 4. Om en funktion är större än 1 M måste flera
.pdata
och.xdata
poster användas för att beskriva funktionen. Mer information finns i avsnittet Stora funktioner.b) Vers är ett 2-bitarsfält. Den beskriver versionen av återstående
.xdata
. För närvarande är endast version 0 definierad, så värden för 1–3 tillåts inte.c. X är ett 1-bitarsfält. Det anger förekomsten (1) eller frånvaron (0) av undantagsdata.
d. E är ett 1-bitars fält. Den anger att information som beskriver en enda epilog är packad i rubriken (1) i stället för att kräva fler omfångsord senare (0).
e. Epilog Count är ett 5-bitarsfält som har två betydelser, beroende på tillståndet för E bit:
Om E- är 0 anger det totala antalet epilogområden som beskrivs i avsnitt 2. Om det finns fler än 31 omfång i funktionen måste fältet Code Words anges till 0 för att indikera att ett tilläggsord krävs.
Om E- är 1 anger det här fältet indexet för den första avspolningskoden som beskriver den enda epilogen.
f. Kodord är ett 5-bitarsfält som anger antalet 32-bitars ord som behövs för att innehålla alla avaktiveringskoder i avsnitt 3. Om fler än 31 ord (det vill säga 124 avrullningskoder) krävs måste det här fältet vara 0 för att indikera att ett tilläggsord krävs.
g. Extended Epilog Count och Extended Code Words är 16-bitars respektive 8-bitars fält. De ger mer utrymme för att koda ett ovanligt stort antal epiloger, eller ett ovanligt stort antal varva ned kodord. Tilläggsordet som innehåller dessa fält finns bara om både Epilog Count och Kodord fält i det första rubrikordet är 0.
Om antalet epiloger inte är noll följer en lista med information om epilogavsnitt, packade en per ord, efter rubriken och den valfria utökade rubriken. De lagras i ordning av ökande startförskjutning. Varje omfång innehåller följande bitar:
a. Epilog Start Offset är ett 18-bitars fält som har förskjutningen i byte, delat med 4, av epilogen i förhållande till början av funktionen.
b) Res är ett 4-bitarsfält som är reserverat för framtida expansion. Värdet måste vara 0.
c. Epilog Start Index är ett 10-bitarsfält (2 fler bitar än utökade kodord). Det anger byteindexet för den första avspolningskoden som beskriver den här epilogen.
Efter listan med epilogomfattningar kommer ett fält med byte som innehåller avvecklingskoder, som beskrivs i detalj i ett senare avsnitt. Den här matrisen är vadderad i slutet till närmaste fullständiga ordgräns. Avrullningskoder skrivs till den här matrisen. De börjar med den som är närmast funktionens huvuddel och rör sig ut mot funktionens kanter. Byte för varje avvecklingskod lagras i big-endian ordning så att den mest signifikanta byten hämtas först, vilket identifierar operationen och längden på resten av koden.
Slutligen, efter att kodbytessekvensen har färdigställts, kommer undantagshanterarens information om biten X i huvudet har ställts in på 1. Den består av en enda Undantagshanterare RVA- som tillhandahåller adressen till själva undantagshanteraren. Den följs omedelbart av en mängd data med variabel längd som krävs av undantagshanteraren.
Posten .xdata
är utformad så att du kan hämta de första 8 byteen och använda dem för att beräkna postens fulla storlek, minus längden på undantagsdata i variabelstorlek som följer. Följande kodfragment beräknar poststorleken:
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;
}
Även om prologen och varje epilog har ett eget index i varva ned-koderna delas tabellen mellan dem. Det är fullt möjligt (och inte helt ovanligt) att de alla kan dela samma koder. (Ett exempel finns i Exempel 2 i avsnittet Exempel.) Kompilatorförfattare bör optimera för det här fallet i synnerhet. Det beror på att det största indexet som kan anges är 255, vilket begränsar det totala antalet avrullningskoder för en viss funktion.
Varva ned koder
Mängden av unwind-koder är en pool av sekvenser som exakt beskriver hur man återställer effekterna av prologen. De lagras i samma ordning som operationerna måste ångras. Avkopplingskoderna kan betraktas som en liten instruktionsuppsättning som kodas som en sträng med byte. När körningen är klar finns returadressen till den anropande funktionen i lr
-register. Och alla icke-flyktiga register återställs till sina värden vid den tidpunkt då funktionen anropades.
Om undantag garanterades att endast inträffa i en funktionskropp, och aldrig inom en prolog eller någon epilog, skulle endast en enda sekvens vara nödvändig. Windows-avvecklingsmodellen kräver dock att koden kan avvecklas från en delvis utförd prolog eller epilog. För att uppfylla detta krav har tillbakaknuffningskoderna utformats noggrant så att de entydigt mappar 1:1 till varje relevant opcode i både prologen och epilogen. Den här designen har flera konsekvenser:
Genom att räkna antalet avspolningskoder är det möjligt att beräkna längden på prolog och epilog.
Genom att räkna antalet instruktioner efter början av ett epilogområde går det att hoppa över motsvarande antal avrullningskoder. Vi kan köra resten av en sekvens för att slutföra den delvis utförda uppackningen som epilogen har påbörjat.
Genom att räkna antalet instruktioner före slutet av prologen går det att hoppa över motsvarande antal avrullningskoder. Vi kan köra resten av sekvensen för att bara ångra de delar av prologen som har avslutats.
Avkopplingskoderna kodas enligt tabellen nedan. Alla avrullningskoder är enkel-/dubbelbyte, förutom den som allokerar en stor stack (alloc_l
). Det finns totalt 22 avspolningskoder. Varje avvecklingskod mappar till exakt en instruktion i prologen/epilogen, så att delvis utförda prologer och epiloger kan avvecklas.
Varva ned kod | Bitar och tolkning |
---|---|
alloc_s |
000xxxxx: allokera liten stack med storlek < 512 (2^5 * 16). |
save_r19r20_x |
001zzzzz: spara <x19,x20> par på [sp-#Z*8]! , förindexerad förskjutning >= -248 |
save_fplr |
01zzzzzz: spara <x29,lr> par på [sp+#Z*8] , offset <= 504. |
save_fplr_x |
10zzzzzz: spara <x29,lr> par på [sp-(#Z+1)*8]! , förindexerad förskjutning >= -512 |
alloc_m |
11000xxx'xxxxxxxx: allokera stor stack med storlek < 32K (2^11 * 16). |
save_regp |
110010xx'xxzzzzzz: spara paret x(19+#X) på [sp+#Z*8] , med offset <= 504 |
save_regp_x |
110011xx'xxzzzzzz: spara par x(19+#X) på [sp-(#Z+1)*8]! , med förindexerad offset >= -512 |
save_reg |
110100xx'xxzzzzzz: spara reg. x(19+#X) på [sp+#Z*8] , offset <= 504 |
save_reg_x |
1101010x'xxxzzzzz: spara reg x(19+#X) på [sp-(#Z+1)*8]! , med förindexerad offset >= -256 |
save_lrpair |
1101011x'xxzzzzzz: spara par <x(19+2*#X),lr> på [sp+#Z*8] , offset <= 504 |
save_fregp |
1101100x'xxzzzzzz: spara par d(8+#X) på [sp+#Z*8] , offset <= 504 |
save_fregp_x |
1101101x'xxzzzzzz: spara par d(8+#X) på [sp-(#Z+1)*8]! , förindexerad förskjutning >= -512 |
save_freg |
1101110x'xxzzzzzz: spara reg d(8+#X) på [sp+#Z*8] , offset <= 504 |
save_freg_x |
11011110'xxxzzzzz: spara reg d(8+#X) på [sp-(#Z+1)*8]! , förindexerad förskjutning >= -256 |
alloc_z |
11011111'zzzzzzzz: allokera stack med storlek z * SVE-VL |
alloc_l |
11100000'xxxxxxxx'xxxxxxxx'xxxxxxxx: tilldela stort stackminne med storlek < 256M (2^24 * 16) |
set_fp |
11100001: Konfigurera x29 med mov x29,sp |
add_fp |
11100010'xxxxxxxx: konfigurera x29 med add x29,sp,#x*8 |
nop |
11100011: Ingen åtgärd krävs för att varva ned. |
end |
11100100: slutet på avspolningskoden. Innebär ret i epilog. |
end_c |
11100101: Slutet på avspolningskoden i det aktuella länkade omfånget. |
save_next |
11100110: spara nästa registerpar. |
save_any_xreg |
11100111'0pxrrrrr'00oooooo: spara registeren
|
save_any_dreg |
11100111'0pxrrrrr'01oooooo: spara register(n)
|
save_any_qreg |
11100111'0pxrrrrr'10oooooo: spara register
|
save_zreg |
11100111'0oo0rrrr'11oooooo: spara reg Z(#r+8) på [sp + #o * VL] , (Z8 via Z23 ) |
save_preg |
11100111'0oo1rrrr'11oooooo: spara reg P(#r+8) på [sp + #o * (VL / 8)] , (P4 via P15 ; r värden [0, 3] är reserverade) |
11100111'1yyyyyyy': reserverad | |
11101xxx: reserverad för anpassade stackfall nedan genereras endast för asm-rutiner | |
11101000: Anpassad stack för MSFT_OP_TRAP_FRAME |
|
11101001: Anpassad stack för MSFT_OP_MACHINE_FRAME |
|
11101010: Anpassad stack för MSFT_OP_CONTEXT |
|
11101011: Anpassad stack för MSFT_OP_EC_CONTEXT |
|
11101100: Anpassad stack för MSFT_OP_CLEAR_UNWOUND_TO_CALL |
|
11101101: reserverad | |
11101110: reserverad | |
11101111: reserverad | |
11110xxx: reserverad | |
11111000'yyyyyyyy: reserverad | |
11111001'yyyyyyyyy'yyyyyyyy: reserverad | |
11111010'yyyyyyyy'yyyyyyyy'yyyyyyyy : reserverad | |
11111011'yyyyyyyy'yyyyyyyy'yyyyyyyy'yyyyyyyy : reserverad | |
pac_sign_lr |
11111100: signera returadressen i lr med pacibsp |
11111101: reserverad | |
11111110: reserverad | |
11111111: reserverad |
I instruktioner med stora värden som täcker flera byte lagras de viktigaste bitarna först. Den här designen gör det möjligt att hitta den totala storleken i byte av avspolningskoden genom att bara leta upp kodens första byte. Eftersom varje avrullningskod är exakt mappad till en instruktion i en prolog eller epilog kan du beräkna storleken på prologen eller epilogen. Gå från sekvensstarten till slutet och använd en uppslagstabell eller liknande enhet för att fastställa längden på motsvarande opcode.
Postindexerad förskjutningsadressering är inte tillåten i en prolog. Alla förskjutningsintervall (#Z) matchar kodningen av stp
/str
adressering utom save_r19r20_x
, där 248 är tillräckligt för alla sparområden (10 Int-register + 8 FP-register + 8 indataregister).
save_next
måste följa en spara för ett registerpar: save_regp
, save_regp_x
, save_fregp
, save_fregp_x
, save_r19r20_x
eller en annan save_next
. Det kan också användas tillsammans med save_any_xreg
, save_any_dreg
eller save_any_qreg
men bara när p = 1
. Nästa registerpar sparas i numeriskt ökande ordning till nästa stackutrymme.
save_next
får inte användas utöver det sista registret av samma slag.
Eftersom storleken på vanliga retur- och hoppinstruktioner är desamma behöver du inte ha en avgränsad end
varva ned koden i scenarier med slutsamtal.
end_c
är utformad för att hantera icke-sammanhängande funktionsfragment i optimeringssyfte. En end_c
som anger slutet på avvecklingskoderna i det aktuella omfånget måste följas av en annan serie av avvecklingskoder som slutar med en egentlig end
. Avkopplingskoderna mellan end_c
och end
representerar prologåtgärderna i den överordnade regionen (en "fantom"-prolog). Mer information och exempel beskrivs i avsnittet nedan.
Packad återställningsdata
För funktioner vars prologer och epiloger följer det kanoniska formuläret som beskrivs nedan kan packade avspolningsdata användas. Det eliminerar behovet av en .xdata
post helt och hållet och minskar avsevärt kostnaden för att tillhandahålla varva ned data. De kanoniska prologerna och epilogerna är utformade för att uppfylla de vanliga kraven för en enkel funktion: En som inte kräver en undantagshanterare och som utför sina konfigurations- och teardown-åtgärder i standardordning.
Formatet för en .pdata
post med packade avspolningsdata ser ut så här:
Fälten är följande:
- Start-RVA för funktionen är 32-bitars RVA som markerar början av funktionen.
-
Flagga är ett 2-bitarsfält enligt beskrivningen ovan, med följande betydelser:
- 00 = avvecklingsdata som inte används; återstående bitar pekar på en
.xdata
-post - 01 = packade avrullningsdata som används med en enda prolog och epilog i början och slutet av omfattningen.
- 10 = packad unroll-data som används för kod utan prolog och epilog. Användbart för att beskriva avgränsade funktionssegment
- 11 = reserverad.
- 00 = avvecklingsdata som inte används; återstående bitar pekar på en
-
Funktionslängd är ett 11-bitars fält som anger längden på hela funktionen i byte, dividerat med 4. Om funktionen är större än 8k måste en fullständig
.xdata
-post användas istället. -
Ramstorlek är ett 9-bitars fält som anger antalet byte av stack som allokeras för den här funktionen, dividerat med 16. Funktioner som allokerar mer än (8 000–16 384) byte av stacken måste använda en fullständig
.xdata
-post. Den innehåller det lokala variabelområdet, utgående parameterområde, callee-sparat Int- och FP-område samt hemparameterområdet. Det exkluderar det dynamiska allokeringsområdet. -
CR- är en 2-bitars flagga som anger om funktionen innehåller extra instruktioner för att konfigurera en ramkedja och returlänk:
- 00 = ohämmad funktion,
<x29,lr>
par sparas inte i stacken - 01 = ohämmad funktion,
<lr>
sparas i stacken - 10 = länkad funktion med en returadress signerad med
pacibsp
- 11 = länkad funktion, en instruktion för ett lagrings-/laddningspar används i prolog/epilog
<x29,lr>
- 00 = ohämmad funktion,
- H är en 1-bitars flagga som anger om funktionen placerar heltalsparameterregistren (x0-x7) genom att lagra dem helt i början av funktionen. (0 = inte hem register, 1 = hem register).
- RegI är ett 4-bitars fält som anger antalet icke-flyktiga INT-register (x19-x28) som sparats på den kanoniska stackplatsen.
- RegF är ett 3-bitars fält som anger antalet icke-flyktiga FP-register (d8-d15) som sparats på den kanoniska stackplatsen. (RegF=0: inget FP-register sparas; RegF>0: RegF+1 FP-register sparas). Packad avlindningsdata kan inte användas för en funktion som sparar endast ett FP-register.
Kanoniska prologer som tillhör kategorierna 1, 2 (utan utgående parameterområde), 3 och 4 i avsnittet ovan kan representeras av packat varva ned-format. Epilogerna för kanoniska funktioner följer ett liknande form, förutom att H inte har någon effekt, set_fp
-instruktionen utelämnas, och stegens ordning samt instruktionerna i varje steg är omvända i epilogen. Algoritmen för komprimerade .xdata
följer dessa steg, som beskrivs i följande tabell:
Steg 0: Förberäkning av storleken på varje område.
Steg 1: Signera returadressen.
Steg 2: Bevara heltal callee-bevarade register.
Steg 3: Det här steget är specifikt för typ 4 i tidigare avsnitt.
lr
sparas i slutet av Int-området.
Steg 4: Spara de register som sparas av funktionsanropet (FP).
Steg 5: Spara indataargument i startparameterområdet.
Steg 6: Allokera återstående stack, inklusive lokalt område, <x29,lr>
par och utgående parameterområde. 6a motsvarar kanonisk typ 1. 6b och 6c är för kanonisk typ 2. 6d och 6e är för både typ 3 och typ 4.
Steg # | Flaggvärden | Antal instruktioner | Opcode | Varva ned kod |
---|---|---|---|---|
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 |
1 | 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 |
* Om CR == 01 och RegI är ett udda tal sammanfogas steg 3 och den sista save_reg
i steg 2 till en save_regp
.
** Om RegI == CR == 0, och RegF != 0, gör det första stp
förminskningen för flyttalsoperationen.
Ingen instruktion som motsvarar mov x29,sp
finns i epilogen. Du kan inte använda komprimerad återkopplingsdata om en funktion kräver återställning av sp
från x29
.
Avveckling av partiella prologer och epiloger
I de vanligaste avspolningssituationerna sker undantaget eller anropet i funktionens brödtext, bort från prologen och alla epiloger. I dessa situationer är det enkelt att varva ned: avrullaren utför helt enkelt koderna i avrullningsmatrisen. Den börjar vid index 0 och fortsätter tills en end
opcode identifieras.
Det är svårare att varva ned korrekt om ett undantag eller avbrott inträffar vid körning av en prolog eller epilog. I dessa situationer är stackramen endast delvis konstruerad. Problemet är att fastställa exakt vad som har gjorts för att ångra det på rätt sätt.
Ta till exempel den här prolog- och epilogsekvensen:
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
Bredvid varje opcode finns lämplig avspolningskod som beskriver den här åtgärden. Du kan se hur serien med avspolningskoder för prolog är en exakt speglingsbild av avspolningskoderna för epilog (räknar inte den slutliga instruktionen i epilogen). Det är vanligt: Det är därför vi alltid antar att avrullningskoderna för prologen lagras i omvänd ordning från prologens körningsordning.
För både prolog och epilog har vi därför en gemensam uppsättning upprullningskoder.
set_fp
, save_regp 0,240
, save_fregp,0,224
, save_fplr_x_256
, end
Epilogfallet är enkelt, eftersom det är i normal ordning. Med början vid offset 0 inom epilogen (som startar vid offset 0x100 i funktionen) förväntar vi oss att hela avrullningssekvensen körs, eftersom ingen rensning har gjorts ännu. Om vi befinner oss i en instruktion (vid förskjutning 2 i epilogen) kan vi avveckla framgångsrikt genom att hoppa över den första avvecklingskoden. Vi kan generalisera den här situationen och anta en 1:1-mappning mellan opcodes och unwind-koder. För att sedan börja avveckla från instruktionen n i epilogen bör vi hoppa över de första n avvecklingskoderna och börja exekvera därifrån.
Det visar sig att en liknande logik fungerar för prologen, förutom i omvänd ordning. Om vi börjar varva ned från förskjutningen 0 i prologen vill vi inte köra någonting. Om vi avrullar från förskjutning 2, som är en instruktion in, vill vi börja köra avrullningssekvensen en avrullningskod från slutet. (Kom ihåg att koderna lagras i omvänd ordning.) Även här kan vi generalisera: om vi börjar avveckla från instruktion n i prologen, bör vi börja köra n avvecklingskoder från slutet av koderna.
Prolog- och epilogkoder matchar inte alltid exakt, vilket är anledningen till att avrullningsmatrisen kan behöva innehålla flera sekvenser med koder. Använd följande logik för att fastställa förskjutningen av var du ska börja bearbeta koder:
Om du avlindrar från funktionens kropp börjar du köra avlindningskoder vid index 0 och fortsätt tills du når en
end
operationskod.Om du varvar ned från insidan av en epilog använder du det epilogspecifika startindex som tillhandahålls med epilogens omfång som utgångspunkt. Beräkna hur många byte den berörda datorn är från början av epilogen. Gå sedan vidare genom avlindningskoderna och hoppa över avlindningskoder tills alla redan utförda instruktioner har redovisats. Kör sedan från och med den punkten.
Om du varvar ned inifrån prologen använder du index 0 som startpunkt. Beräkna längden på prologkoden från sekvensen och beräkna sedan hur många byte datorn i fråga är från slutet av prologen. Fortsätt sedan framåt genom avvecklingskoderna och hoppa över avvecklingskoder tills alla ännu inte körda instruktioner har redovisats. Kör sedan från och med den tidpunkten.
Dessa regler innebär att avrullningskoderna för prolog alltid måste vara de första i matrisen. Och de är också de koder som används för att lösa upp i det allmänna fallet där man löser upp från insidan av textens innehåll. Alla epilogspecifika kodsekvenser bör följa omedelbart efter.
Funktionsfragment
Av kodoptimeringsskäl och andra orsaker kan det vara bättre att dela upp en funktion i avgränsade fragment (kallas även regioner). Vid delning kräver varje resulterande funktionsfragment en egen separat .pdata
(och eventuellt .xdata
) post.
För varje avgränsat sekundärt fragment som har en egen prolog förväntas det att ingen stackjustering görs i dess prolog. Allt stackutrymme som krävs av en sekundär region måste förallokeras av dess överordnade region (eller kallas värdregion). Den här förallokeringen håller stackpekarens manipulering strikt i funktionens ursprungliga prolog.
Ett typiskt fall av funktionsfragment är "kodavgränsning", där kompilatorn kan flytta en kodregion från värdfunktionen. Det finns tre ovanliga fall som kan bero på kodseparering.
Exempel
(region 1: börja)
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 ...
(region 1: slut)
(region 3: börja)
...
(region 3: slut)
(region 2: börja)
... 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
(region 2: slut)
Endast prolog (region 1: alla epiloger finns i avgränsade regioner):
Endast prologen måste beskrivas. Den här prologen kan inte representeras i det kompakta
.pdata
formatet. I det fullständiga.xdata
fallet kan det representeras genom att ange Epilog Count = 0. Se region 1 i exemplet ovan.Varva ned koder:
set_fp
,save_regp 0,240
,save_fplr_x_256
,end
.Endast epiloger (region 2: prolog finns i värdregionen)
Det antas att när tidskontrollen hoppar in i den här regionen har alla prologkoder körts. Delvis varva ned kan ske i epiloger på samma sätt som i en normal funktion. Den här typen av region kan inte representeras av kompakta
.pdata
. I en fullständig.xdata
post kan den kodas med en "fantom" prolog, hakparenteserad av ettend_c
ochend
varva ned kodpar. Den inledandeend_c
anger att storleken på prolog är noll. Epilogs startindex för den enskilda epilogen pekar påset_fp
.Varva ned kod för region 2:
end_c
,set_fp
,save_regp 0,240
,save_fplr_x_256
,end
.Inga prologer eller epiloger (region 3: prologer och alla epiloger finns i andra fragment):
Kompakt
.pdata
format kan användas via inställningen Flagga = 10. Med fullständig.xdata
-post, Epilogräkning = 1. Varva ned koden är samma som koden för region 2 ovan, men Epilog Start Index pekar också påend_c
. Delvis varva ned sker aldrig i den här kodregionen.
Ett annat mer komplicerat fall av funktionsfragment är "shrink-wrapping". Kompilatorn kan välja att fördröja sparandet av vissa callee-sparade register tills utanför funktionens entréprolog.
(region 1: börja)
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 ...
(region 2: börja)
stp x21,x22,[sp,#224] // save_regp 2, 224 ... ldp x21,x22,[sp,#224] // save_regp 2, 224
(region 2: avslutning)
... 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
(region 1: slut)
I förloggen för region 1 är stackutrymmet förallokerat. Du kan se att region 2 har samma avspolningskod även om den flyttas från värdfunktionen.
Region 1: set_fp
, save_regp 0,240
, save_fplr_x_256
, end
. Epilog Start Index pekar på set_fp
som vanligt.
Region 2: save_regp 2, 224
, end_c
, set_fp
, save_regp 0,240
, save_fplr_x_256
, end
. Epilog Start Index pekar på första avspolningskoden save_regp 2, 224
.
Stora funktioner
Fragment kan användas för att beskriva funktioner som är större än 1M-gränsen som har införts av bitfälten i .xdata
-huvudet. För att beskriva en ovanligt stor funktion som denna måste den delas upp i fragment som är mindre än 1 miljon. Varje fragment bör justeras så att det inte delar upp ett epilog i flera delar.
Endast det första fragmentet av funktionen innehåller en prolog. alla andra fragment markeras som att de inte har någon prolog. Beroende på antalet epiloger som finns kan varje fragment innehålla noll eller fler epiloger. Tänk på att varje epilogomfång i ett fragment anger dess startförskjutning i förhållande till början av fragmentet, inte början av funktionen.
Om ett fragment inte har någon prolog och ingen epilog, kräver det fortfarande en egen .pdata
(och eventuellt .xdata
) post, för att beskriva hur du varvar ner inifrån funktionens brödtext.
Exempel
Exempel 1: Ramkedjad, kompakt form
|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]
Exempel 2: Ramkedjad, fullformat med spegling 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
Epilog Start Index [0] pekar på samma sekvens med Prolog unwind-kod.
Exempel 3: Variadisk oförankrad Funktion
|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
Epilog Start Index [4] pekar på mitten av Prolog upprullningskod (delvis återanvänder upprullningsmatrisen).