Dela via


ARM64-undantagshantering

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 ursprungliga sp 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

Diagram som visar stackramens layout för funktioner.

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.

  1. 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)
    
  2. 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
    
  3. 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 kan sub 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.

  4. 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-lr stp inte kan representeras med unwind-koder.

    Alla lokalbefolkningen nås baserat på sp. <x29> pekar på föregående bildruta.

  5. 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å.

  6. 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.

  7. 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.

postlayout för .pdata.

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:

postlayout för .xdata.

Dessa data är uppdelade i fyra avsnitt:

  1. 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:

    1. 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.

    2. 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.

  2. 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.

  3. 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.

  4. 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)[sp+#Z*8], med offset <= 504
save_regp_x 110011xx'xxzzzzzz: spara par x(19+#X)[sp-(#Z+1)*8]!, med förindexerad offset >= -512
save_reg 110100xx'xxzzzzzz: spara reg. x(19+#X)[sp+#Z*8], offset <= 504
save_reg_x 1101010x'xxxzzzzz: spara reg x(19+#X)[sp-(#Z+1)*8]!, med förindexerad offset >= -256
save_lrpair 1101011x'xxzzzzzz: spara par <x(19+2*#X),lr>[sp+#Z*8], offset <= 504
save_fregp 1101100x'xxzzzzzz: spara par d(8+#X)[sp+#Z*8], offset <= 504
save_fregp_x 1101101x'xxzzzzzz: spara par d(8+#X)[sp-(#Z+1)*8]!, förindexerad förskjutning >= -512
save_freg 1101110x'xxzzzzzz: spara reg d(8+#X)[sp+#Z*8], offset <= 504
save_freg_x 11011110'xxxzzzzz: spara reg d(8+#X)[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
  • p: 0/1 => enkel X(#r) jämfört med par X(#r) + X(#r+1)
  • x: 0/1 => positiv eller negativ förindexerad stackförskjutning
  • o: offset = o * 16, om x=1 eller p=1, annars o * 8
(Windows >= 11 krävs)
save_any_dreg 11100111'0pxrrrrr'01oooooo: spara register(n)
  • p: 0/1 => enkel D(#r) jämfört med par D(#r) + D(#r+1)
  • x: 0/1 => positiv eller negativ förindexerad stackförskjutning
  • o: offset = o * 16, om x=1 eller p=1, annars o * 8
(Windows >= 11 krävs)
save_any_qreg 11100111'0pxrrrrr'10oooooo: spara register
  • p: 0/1 => enkel Q(#r) jämfört med par Q(#r) + Q(#r+1)
  • x: 0/1 => positiv eller negativ förindexerad stackförskjutning
  • o: offset = o * 16
(Windows >= 11 behövs)
save_zreg 11100111'0oo0rrrr'11oooooo: spara reg Z(#r+8)[sp + #o * VL], (Z8 via Z23)
save_preg 11100111'0oo1rrrr'11oooooo: spara reg P(#r+8)[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_xeller 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:

.pdata-post med packad avveckling av data.

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.
  • 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>
  • 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:

  1. 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.

  2. 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.

  3. 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)

  1. 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.

  2. 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 ett end_c och end varva ned kodpar. Den inledande end_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.

  3. 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).

Se även

Översikt över ARM64 ABI-konventioner
ARM-undantagshantering