Condividi tramite


Gestione delle eccezioni ARM64

Windows in ARM64 usa lo stesso meccanismo di gestione delle eccezioni strutturate per le eccezioni generate dall'hardware asincrone e le eccezioni generate dal software sincrone. I gestori di eccezioni specifici del linguaggio sono costruiti sulla base della gestione strutturata delle eccezioni di Windows mediante le funzioni helper del linguaggio. Questo documento descrive la gestione delle eccezioni in Windows in ARM64. Illustra gli helper del linguaggio usati dal codice generato dall'assembler Microsoft ARM e dal compilatore MSVC.

Obiettivi e motivazione

Le convenzioni dei dati di rimozione delle eccezioni e questa descrizione sono destinate a:

  • Fornire una descrizione sufficiente per consentire la rimozione senza eseguire il probe del codice in tutti i casi.

    • L'analisi del codice richiede che il codice venga inserito nel paging. Impedisce la rimozione in alcune circostanze in cui è utile (traccia, campionamento, debug).

    • L'analisi del codice è complessa; il compilatore deve prestare attenzione a generare solo istruzioni che il selvitore può decodificare.

    • Se la rimozione non può essere descritta completamente usando i codici di rimozione, in alcuni casi deve eseguire il fallback alla decodifica delle istruzioni. La decodifica delle istruzioni aumenta la complessità complessiva e idealmente dovrebbe essere evitata.

  • Supporto della rimozione nel prologo intermedio e nell'epilogo intermedio.

    • La rimozione viene usata in Windows per più di una gestione delle eccezioni. È fondamentale che il codice possa rimuovere accuratamente anche quando si trova al centro di una sequenza di codice prologo o epilogo.
  • Occupare una quantità minima di spazio.

    • I codici di rimozione non devono essere aggregati per aumentare significativamente le dimensioni binarie.

    • Poiché è probabile che i codici di rimozione siano bloccati in memoria, un footprint ridotto garantisce un sovraccarico minimo per ogni file binario caricato.

Presupposti

Questi presupposti vengono effettuati nella descrizione della gestione delle eccezioni:

  • I prologi e gli epilogi tendono a rispecchiarsi tra loro. Sfruttando questo tratto comune, le dimensioni dei metadati necessari per descrivere la rimozione possono essere notevolmente ridotte. All'interno del corpo della funzione, non importa se le operazioni del prologo vengono annullate o le operazioni dell'epilogo vengono eseguite in modo forward. Entrambi devono produrre risultati identici.

  • Le funzioni tendono a essere relativamente piccole. Diverse ottimizzazioni per lo spazio si basano su questo fatto per ottenere la compressione più efficiente dei dati.

  • Non esiste codice condizionale negli epilogi.

  • Registro puntatore frame dedicato: se l'oggetto sp viene salvato in un altro registro (x29) nel prologo, tale registro rimane invariato in tutta la funzione. Significa che l'originale sp può essere recuperato in qualsiasi momento.

  • A meno che non sp venga salvato in un altro registro, tutte le modifiche del puntatore dello stack si verificano rigorosamente all'interno del prologo e dell'epilogo.

  • Il layout dello stack frame è organizzato come descritto nella sezione successiva.

Layout dello stack frame ARM64

Diagram that shows the stack frame layout for functions.

Per le funzioni concatenati di frame, la fp coppia e lr può essere salvata in qualsiasi posizione nell'area variabile locale, a seconda delle considerazioni sull'ottimizzazione. L'obiettivo è ottimizzare il numero di variabili locali che possono essere raggiunte da una singola istruzione in base al puntatore frame () o al puntatore dello stack (x29sp). Tuttavia, per alloca le funzioni, deve essere concatenato e x29 deve puntare alla parte inferiore dello stack. Per consentire una migliore copertura register-pair-addressing-mode, le aree di salvataggio del registro non volatile vengono posizionate nella parte superiore dello stack di aree locali. Ecco alcuni esempi che illustrano diverse delle sequenze di prologo più efficienti. Per motivi di chiarezza e migliore localizzazione della cache, l'ordine di archiviazione dei registri salvati dal chiamato in tutti i prolog canonici è in ordine "crescente". #framesz sotto rappresenta le dimensioni dell'intero stack (escluso l'area alloca ). #localsz e #outsz indicano le dimensioni dell'area locale (inclusa l'area di salvataggio per la coppia) e le dimensioni dei <x29, lr> parametri in uscita, rispettivamente.

  1. Concatenato, #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. Concatenato, #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. Funzioni foglia senza portamento (lr non salvate)

        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
    

    Tutte le variabili locali sono accessibili in base a sp. <x29,lr> punta al frame precedente. Per le dimensioni <del frame = 512, l'oggetto sub sp, ... può essere ottimizzato se l'area salvata regs viene spostata nella parte inferiore dello stack. Lo svantaggio è che non è coerente con altri layout precedenti. Inoltre, i reg salvati fanno parte dell'intervallo per la modalità di indirizzamento pair-regs e pre-indicizzata e post-indicizzata.

  4. Funzioni non foglia non portabili (salva lr nell'area salvata 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
    

    Oppure, con un numero pari di registri Int salvati,

        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
    

    Salvato solo x19 :

        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
    

    * L'allocazione dell'area di salvataggio reg non viene piegata in stp perché non è possibile rappresentare un reg-lr stp pre-indicizzato con i codici di rimozione.

    Tutte le variabili locali sono accessibili in base a sp. <x29> punta al frame precedente.

  5. Concatenato, #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
    

    Rispetto al primo esempio di prologo precedente, questo esempio presenta un vantaggio: tutte le istruzioni di salvataggio del registro sono pronte per l'esecuzione dopo una sola istruzione di allocazione dello stack. Ciò significa che non c'è alcuna dipendenza da sp che impedisce il parallelismo a livello di istruzione.

  6. Concatenato, dimensioni > frame 512 (facoltativo per le funzioni senza 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
    

    A scopo di ottimizzazione, x29 può essere inserito in qualsiasi posizione nell'area locale per offrire una copertura migliore per la modalità di indirizzamento di offset pre-/post-indicizzato. È possibile accedere alle variabili locali al di sotto dei puntatori ai fotogrammi in base a sp.

  7. Concatenato, dimensioni > frame 4K, con o senza 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>
    

Informazioni sulla gestione delle eccezioni ARM64

.pdata Record

I .pdata record sono una matrice ordinata di elementi a lunghezza fissa che descrivono ogni funzione di modifica dello stack in un file binario PE. La frase "modifica dello stack" è significativa: le funzioni foglia che non richiedono alcuna risorsa di archiviazione locale e non devono salvare/ripristinare registri non volatili, non richiedono un .pdata record. Questi record devono essere omessi in modo esplicito per risparmiare spazio. Una rimozione da una di queste funzioni può ottenere l'indirizzo restituito direttamente da lr per passare al chiamante.

Ogni .pdata record per ARM64 ha una lunghezza di 8 byte. Il formato generale di ogni record inserisce l'RVA a 32 bit della funzione inizia nella prima parola, seguita da una seconda parola contenente un puntatore a un blocco a lunghezza .xdata variabile o una parola compressa che descrive una sequenza di rimozione di una funzione canonica.

.pdata record layout.

I campi sono i seguenti:

  • RVA start della funzione è l'RVA a 32 bit dell'inizio della funzione.

  • Flag è un campo a 2 bit che indica come interpretare i 30 bit rimanenti della seconda .pdata parola. Se Flag è 0, i bit rimanenti formano un RVA delle informazioni sulle eccezioni (con i due bit più bassi in modo implicito 0). Se Flag è diverso da zero, i bit rimanenti formano una struttura di dati di rimozione compressi.

  • L'RVA delle informazioni sulle eccezioni è l'indirizzo della struttura delle informazioni sulle eccezioni a lunghezza variabile archiviata nella .xdata sezione . Questi dati devono essere allineati a 4 byte.

  • I dati di rimozione compressi sono una descrizione compressa delle operazioni necessarie per la rimozione da una funzione, presupponendo una forma canonica. In questo caso, non è necessario alcun .xdata record.

.xdata Record

Quando il formato di rimozione compresso non è sufficiente per descrivere la rimozione di una funzione, è necessario creare un record a lunghezza .xdata variabile. L'indirizzo di questo record viene archiviato nella seconda parola del .pdata record. Il formato di .xdata è un set di parole a lunghezza variabile compresso:

.xdata record layout.

Questi dati sono suddivisi in quattro sezioni:

  1. Un'intestazione di 1 parola o 2 parole che descrive le dimensioni complessive della struttura e fornisce i dati della funzione chiave. La seconda parola è presente solo se i campi Epilog Count e Code Words sono impostati su 0. L'intestazione include questi campi di bit:

    a. La lunghezza della funzione è un campo a 18 bit. Indica la lunghezza totale della funzione in byte, divisa per 4. Se una funzione è maggiore di 1M, è necessario usare più .pdata record e .xdata per descrivere la funzione. Per altre informazioni, vedere la sezione Funzioni Large.

    b. Vers è un campo a 2 bit. Descrive la versione dell'oggetto rimanente .xdata. Attualmente è definita solo la versione 0, quindi i valori di 1-3 non sono consentiti.

    c. X è un campo a 1 bit. Indica la presenza (1) o l'assenza (0) dei dati delle eccezioni.

    d. E è un campo a 1 bit. Indica che le informazioni che descrivono un singolo epilogo vengono compresse nell'intestazione (1) anziché richiedere più parole di ambito più avanti (0).

    e. Epilog Count è un campo a 5 bit con due significati, a seconda dello stato di E bit:

    1. Se E è 0, specifica il conteggio del numero totale di ambiti epilogi descritti nella sezione 2. Se nella funzione esistono più di 31 ambiti, il campo Parole codice deve essere impostato su 0 per indicare che è necessaria una parola di estensione.

    2. Se E è 1, questo campo specifica l'indice del primo codice di rimozione che descrive quello e solo l'epilogo.

    f. Parole di codice è un campo a 5 bit che specifica il numero di parole a 32 bit necessarie per contenere tutti i codici di rimozione nella sezione 3. Se sono necessarie più di 31 parole (ovvero 124 codici di rimozione), questo campo deve essere 0 per indicare che è necessaria una parola di estensione.

    g. Il conteggio degli epilogi estesi e le parole di codice esteso sono rispettivamente campi a 16 bit e a 8 bit. Forniscono più spazio per la codifica di un numero insolitamente elevato di epilogi o un numero insolitamente elevato di parole di codice di rimozione. La parola di estensione che contiene questi campi è presente solo se i campi Epilog Count e Code Words nella prima parola di intestazione sono 0.

  2. Se il numero di epilogi non è zero, un elenco di informazioni sugli ambiti dell'epilogo, compresso uno a una parola, viene dopo l'intestazione e l'intestazione estesa facoltativa. Vengono archiviati in ordine di offset iniziale crescente. Ogni ambito contiene i bit seguenti:

    a. Epilog Start Offset è un campo a 18 bit con offset in byte, diviso per 4, dell'epilogo rispetto all'inizio della funzione.

    b. Res è un campo a 4 bit riservato per l'espansione futura. Il suo valore deve essere 0.

    c. Epilog Start Index è un campo a 10 bit (2 bit in più rispetto alle parole di codice estese). Indica l'indice byte del primo codice di rimozione che descrive questo epilogo.

  3. Dopo che l'elenco degli ambiti dell'epilogo viene fornita una matrice di byte che contengono codici di rimozione, descritti in dettaglio in una sezione successiva. Questa matrice viene riempita alla fine fino al più vicino confine di parola completa. I codici di rimozione vengono scritti in questa matrice. Iniziano con quello più vicino al corpo della funzione e si spostano verso i bordi della funzione. I byte per ogni codice di rimozione vengono archiviati in ordine big-endian in modo che il byte più significativo venga recuperato per primo, che identifica l'operazione e la lunghezza del resto del codice.

  4. Infine, dopo i byte del codice di rimozione, se il bit X nell'intestazione è stato impostato su 1, vengono fornite le informazioni sul gestore eccezioni. È costituito da un'unica RVA del gestore eccezioni che fornisce l'indirizzo del gestore eccezioni stesso. Viene seguito immediatamente da una quantità di dati a lunghezza variabile richiesta dal gestore eccezioni.

Il .xdata record è progettato in modo da poter recuperare i primi 8 byte e usarli per calcolare le dimensioni complete del record, meno la lunghezza dei dati delle eccezioni di dimensioni variabili che seguono. Il frammento di codice seguente calcola le dimensioni del record:

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;
}

Anche se il prologo e ogni epilogo ha un proprio indice nei codici di rimozione, la tabella viene condivisa tra di esse. È del tutto possibile (e non del tutto insolito) che possano condividere tutti gli stessi codici. Per un esempio, vedere l'esempio 2 nelSezione Esempi . I writer del compilatore devono essere ottimizzati per questo caso in particolare. Il motivo è che l'indice più grande che può essere specificato è 255, che limita il numero totale di codici di rimozione per una determinata funzione.

Codici di rimozione

La matrice di codici di rimozione è un pool di sequenze che descrivono esattamente come annullare gli effetti del prologo. Vengono archiviati nello stesso ordine in cui le operazioni devono essere annullate. I codici di rimozione possono essere considerati come un piccolo set di istruzioni, codificato come stringa di byte. Al termine dell'esecuzione, l'indirizzo restituito alla funzione chiamante si trova nel lr registro. Inoltre, tutti i registri non volatili vengono ripristinati ai relativi valori al momento della chiamata della funzione.

Se le eccezioni venivano garantite solo all'interno di un corpo della funzione e non all'interno di un prologo o di un epilogo, sarebbe necessaria solo una singola sequenza. Tuttavia, il modello di rimozione di Windows richiede che il codice possa rimuoversi da un prologo o un epilogo parzialmente eseguito. Per soddisfare questo requisito, i codici di rimozione sono stati progettati attentamente in modo da mappare in modo univoco 1:1 a ogni codice operativo pertinente nel prologo ed epilogo. Questa progettazione ha diverse implicazioni:

  • Conteggiando il numero di codici di rimozione, è possibile calcolare la lunghezza del prologo ed epilogo.

  • Conteggiando il numero di istruzioni oltre l'inizio di un ambito epilogo, è possibile ignorare il numero equivalente di codici di rimozione. È possibile eseguire il resto di una sequenza per completare la rimozione parzialmente eseguita dall'epilogo.

  • Conteggiando il numero di istruzioni prima della fine del prologo, è possibile ignorare il numero equivalente di codici di rimozione. È possibile eseguire il resto della sequenza per annullare solo le parti del prologo che hanno completato l'esecuzione.

I codici di rimozione vengono codificati in base alla tabella seguente. Tutti i codici di rimozione sono un byte singolo/doppio, ad eccezione di quello che alloca uno stack enorme (alloc_l). Ci sono 22 codici di rimozione in totale. Ogni codice di rimozione esegue il mapping esattamente di un'istruzione nel prologo/epilogo, per consentire la rimozione di prologi e epilogi parzialmente eseguiti.

Codice di rimozione Bit e interpretazione
alloc_s 000xxxxx: allocare uno stack di piccole dimensioni con dimensioni < 512 (2^5 * 16).
save_r19r20_x 001zzzzz: save <x19,x20> pair at [sp-#Z*8]!, pre-indexed offset >= -248
save_fplr 01zzzzzzzz: coppia di salvataggio <x29,lr> in corrispondenza di [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: allocare stack di grandi dimensioni con dimensioni < 32K (2^11 * 16).
save_regp 110010xx'xxzzzzzz: coppia di salvataggio x(19+#X) in [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: coppia <x(19+2*#X),lr> di salvataggio in [sp+#Z*8], offset <= 504
save_fregp 1101100x'xxzzzzzz: coppia d(8+#X) di salvataggio in [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: allocare stack di grandi dimensioni con dimensioni < 256M (2^24 * 16)
set_fp 11100001: configurare x29 con mov x29,sp
add_fp 11100010'xxxxxxxx: configurato x29 con add x29,sp,#x*8
nop 11100011: non è necessaria alcuna operazione di rimozione.
end 11100100: fine del codice di rimozione. Implica ret nell'epilogo.
end_c 11100101: fine del codice di rimozione nell'ambito concatenato corrente.
save_next 11100110: salvare la coppia di registrazione int o FP successiva non volatile.
11100111: riservato
11101xxx: riservato per i casi di stack personalizzati sotto generati solo per le routine asm
11101000: stack personalizzato per MSFT_OP_TRAP_FRAME
11101001: stack personalizzato per MSFT_OP_MACHINE_FRAME
11101010: stack personalizzato per MSFT_OP_CONTEXT
11101011: stack personalizzato per MSFT_OP_EC_CONTEXT
11101100: stack personalizzato per MSFT_OP_CLEAR_UNWOUND_TO_CALL
11101101: riservato
11101110: riservato
11101111: riservato
11110xxx: riservato
11111000'yyy : riservato
11111001'yy'yyy : riservato
11111010'yy'y'yyyy: riservato
11111011'yy'yy'yy'y: riservato
pac_sign_lr 11111100: firmare l'indirizzo restituito con lrpacibsp
11111101: riservato
11111110: riservato
11111111: riservato

Nelle istruzioni con valori di grandi dimensioni che coprono più byte, i bit più significativi vengono archiviati per primi. Questa progettazione consente di trovare le dimensioni totali in byte del codice di rimozione cercando solo il primo byte del codice. Poiché ogni codice di rimozione viene mappato esattamente a un'istruzione in un prologo o un epilogo, è possibile calcolare le dimensioni del prologo o dell'epilogo. Passare dall'inizio della sequenza alla fine e usare una tabella di ricerca o un dispositivo simile per determinare la lunghezza del codice operativo corrispondente.

L'indirizzamento dell'offset post-indicizzato non è consentito in un prologo. Tutti gli intervalli di offset (#Z) corrispondono alla codifica dell'indirizzamento ad eccezione save_r19r20_xdi stp/str , in cui 248 è sufficiente per tutte le aree di salvataggio (10 registri Int + 8 registri FP + 8 registri di input).

save_nextdeve seguire un salvataggio per la coppia di registri int o FP volatile: save_regp, save_regp_x, save_fregp_xsave_fregp, save_r19r20_x, o un altro save_next. Salva la coppia di registri successiva nello slot di 16 byte successivo in ordine di crescita. Un save_next fa riferimento alla prima coppia di registri FP quando segue che save-next indica l'ultima coppia di registri Int.

Poiché le dimensioni delle istruzioni di ritorno e salto regolari sono le stesse, non è necessario un codice di rimozione separato end in scenari di chiamata finale.

end_c è progettato per gestire frammenti di funzione non contigui a scopo di ottimizzazione. Oggetto end_c che indica la fine dei codici di rimozione nell'ambito corrente deve essere seguito da un'altra serie di codici di rimozione che terminano con un reale end. I codici di rimozione tra end_c e end rappresentano le operazioni di prologo nell'area padre (un prologo "fantasma"). Altri dettagli ed esempi sono descritti nella sezione seguente.

Dati di rimozione compressi

Per le funzioni i cui prologi ed epilogi seguono la forma canonica descritta di seguito, è possibile usare i dati di rimozione compressi. Elimina completamente la necessità di un .xdata record e riduce significativamente il costo di fornire dati di rimozione. I prologi e gli epilogi canonici sono progettati per soddisfare i requisiti comuni di una funzione semplice: uno che non richiede un gestore eccezioni e che esegue le operazioni di configurazione e di disinstallazione in un ordine standard.

Il formato di un .pdata record con dati di rimozione compressi è simile al seguente:

.pdata record with packed unwind data.

I campi sono i seguenti:

  • RVA start della funzione è l'RVA a 32 bit dell'inizio della funzione.
  • Flag è un campo a 2 bit come descritto in precedenza, con i significati seguenti:
    • 00 = dati di rimozione compressi non utilizzati; i bit rimanenti puntano a un .xdata record
    • 01 = dati di rimozione compressi usati con un singolo prologo ed epilogo all'inizio e alla fine dell'ambito
    • 10 = dati di rimozione compressi usati per il codice senza prologo ed epilogo. Utile per descrivere segmenti di funzione separati
    • 11 = riservato.
  • La lunghezza della funzione è un campo a 11 bit che fornisce la lunghezza dell'intera funzione in byte, divisa per 4. Se la funzione è maggiore di 8.000, è necessario usare invece un record completo .xdata .
  • Dimensioni frame è un campo a 9 bit che indica il numero di byte dello stack allocato per questa funzione, diviso per 16. Le funzioni che allocano più di (8k-16) byte dello stack devono usare un record completo .xdata . Include l'area della variabile locale, l'area dei parametri in uscita, l'area int salvata dal chiamato Int e FP e l'area dei parametri home. Esclude l'area di allocazione dinamica.
  • CR è un flag a 2 bit che indica se la funzione include istruzioni aggiuntive per configurare una catena di frame e restituire un collegamento:
    • 00 = funzione non associata, <x29,lr> la coppia non viene salvata nello stack
    • 01 = funzione non portabile, <lr> viene salvata nello stack
    • 10 = funzione concatenato con un pacibsp indirizzo restituito firmato
    • 11 = funzione concatenato, viene usata un'istruzione di coppia di archiviazione/caricamento nel prologo/epilogo <x29,lr>
  • H è un flag a 1 bit che indica se la funzione ospita i registri dei parametri integer (x0-x7) archiviandoli all'inizio della funzione. (0 = non si registra a casa, 1 = registri case).
  • RegI è un campo a 4 bit che indica il numero di registri INT non volatili (x19-x28) salvati nella posizione dello stack canonico.
  • RegF è un campo a 3 bit che indica il numero di registri FP non volatili (d8-d15) salvati nella posizione dello stack canonico. (RegF=0: non viene salvato alcun registro FP; RegF>0: i registri FP RegF+1 vengono salvati). I dati di rimozione compressi non possono essere usati per la funzione che salvano un solo registro FP.

I prolog canonici che rientrano nelle categorie 1, 2 (senza area dei parametri in uscita), 3 e 4 nella sezione precedente possono essere rappresentati dal formato di rimozione compresso. Gli epilogi per le funzioni canoniche seguono una forma simile, ad eccezione di H non ha alcun effetto, l'istruzione set_fp viene omessa e l'ordine dei passaggi e le istruzioni in ogni passaggio vengono invertiti nell'epilogo. L'algoritmo per il pacchetto .xdata segue questi passaggi, descritti in dettaglio nella tabella seguente:

Passaggio 0: Pre-calcolo delle dimensioni di ogni area.

Passaggio 1: Firmare l'indirizzo restituito.

Passaggio 2: Salvare i registri salvati dal chiamato Int.

Passaggio 3: Questo passaggio è specifico per il tipo 4 nelle sezioni iniziali. lr viene salvato alla fine dell'area Int.

Passaggio 4: Salvare i registri salvati dal chiamato FP.

Passaggio 5: Salvare gli argomenti di input nell'area dei parametri home.

Passaggio 6: Allocare lo stack rimanente, inclusa l'area locale, <x29,lr> la coppia e l'area dei parametri in uscita. 6a corrisponde al tipo canonico 1. 6b e 6c sono per il tipo canonico 2. 6d e 6e sono sia per il tipo 3 che per il tipo 4.

N.ro passaggio Valori dei flag # di istruzioni Opcode Codice di rimozione
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

* Se CR == 01 e RegI è un numero dispari , il passaggio 2 e l'ultimo save_rep nel passaggio 1 vengono uniti in un unico save_regp.

** Se RegI == CR == 0 e RegF != 0, il primo stp per il virgola mobile esegue la dichiarazione preliminare.

Nessuna istruzione corrispondente a mov x29,sp è presente nell'epilogo. I dati di rimozione compressi non possono essere usati se una funzione richiede il ripristino da spx29.

Rimozione di prologi parziali ed epilogi

Nelle situazioni di rimozione più comuni, l'eccezione o la chiamata si verifica nel corpo della funzione, lontano dal prologo e da tutti gli epilogi. In queste situazioni, la rimozione è semplice: lo rimozione esegue semplicemente i codici nella matrice di rimozione. Inizia con l'indice 0 e continua fino a quando non viene rilevato un end codice operativo.

È più difficile rimuovere correttamente nel caso in cui si verifichi un'eccezione o un interrupt durante l'esecuzione di un prologo o un epilogo. In queste situazioni, lo stack frame è costruito solo parzialmente. Il problema consiste nel determinare esattamente cosa è stato fatto, per annullarlo correttamente.

Ad esempio, prendere questa sequenza di prologo ed epilogo:

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

Accanto a ogni codice operativo è il codice di rimozione appropriato che descrive questa operazione. Si può vedere come la serie di codici di rimozione per il prologo è un'immagine speculare esatta dei codici di rimozione per l'epilogo (senza contare l'istruzione finale dell'epilogo). Si tratta di una situazione comune: è per questo che si presuppongono sempre che i codici di rimozione per il prologo vengano archiviati in ordine inverso rispetto all'ordine di esecuzione del prologo.

Quindi, sia per il prologo che per l'epilogo, siamo rimasti con un set comune di codici di rimozione:

set_fp, save_regp 0,240, save_fregp,0,224, save_fplr_x_256end

Il caso dell'epilogo è semplice, perché è in ordine normale. A partire dall'offset 0 all'interno dell'epilogo (che inizia all'offset 0x100 nella funzione), si prevede che la sequenza di rimozione completa venga eseguita, perché non è stata ancora eseguita alcuna pulizia. Se si trova un'istruzione in (in corrispondenza dell'offset 2 nell'epilogo), è possibile rimuovere correttamente ignorando il primo codice di rimozione. Possiamo generalizzare questa situazione e presupporre un mapping 1:1 tra opcodes e codici di rimozione. Quindi, per iniziare la rimozione dall'istruzione n nell'epilogo, è consigliabile ignorare i primi n codici di rimozione e iniziare l'esecuzione da lì.

Si scopre che una logica simile funziona per il prologo, tranne inverso. Se si inizia la rimozione dall'offset 0 nel prologo, non si vuole eseguire alcuna operazione. Se si rimuove l'offset 2, ovvero un'istruzione in , si vuole iniziare a eseguire la sequenza di rimozione un codice di rimozione dalla fine. Tenere presente che i codici vengono archiviati in ordine inverso. E anche qui, possiamo generalizzare: se iniziamo a rimuovere dall'istruzione n nel prologo, dovremmo iniziare a eseguire n codici di rimozione dalla fine dell'elenco di codici.

I codici prologo ed epilogo non corrispondono sempre esattamente, motivo per cui la matrice di rimozione potrebbe dover contenere diverse sequenze di codici. Per determinare l'offset di dove iniziare l'elaborazione dei codici, usare la logica seguente:

  1. Se si esegue la rimozione dall'interno del corpo della funzione, iniziare a eseguire codici di rimozione in corrispondenza dell'indice 0 e continuare fino a raggiungere un end codice operativo.

  2. Se si esegue la rimozione da un epilogo, usare l'indice iniziale specifico dell'epilogo fornito con l'ambito dell'epilogo come punto iniziale. Calcolare il numero di byte che il PC in questione proviene dall'inizio dell'epilogo. Avanti quindi attraverso i codici di rimozione, ignorando i codici di rimozione fino a quando non vengono riportate tutte le istruzioni già eseguite. Eseguire quindi a partire da quel punto.

  3. Se si esegue la rimozione dal prologo, usare l'indice 0 come punto iniziale. Calcolare la lunghezza del codice di prologo dalla sequenza e quindi calcolare il numero di byte in questione dal termine del prologo. Avanti quindi attraverso i codici di rimozione, ignorando i codici di rimozione fino a quando non vengono riportate tutte le istruzioni non ancora eseguite. Eseguire quindi a partire da quel punto.

Queste regole indicano che i codici di rimozione per il prologo devono essere sempre il primo nella matrice. E sono anche i codici usati per rilassarsi nel caso generale di rimozione dall'interno del corpo. Tutte le sequenze di codice specifiche dell'epilogo devono essere seguite immediatamente dopo.

Frammenti di funzione

Ai fini dell'ottimizzazione del codice e altri motivi, può essere preferibile suddividere una funzione in frammenti separati (dette anche aree). In caso di divisione, ogni frammento di funzione risultante richiede un record separato .pdata (ed eventualmente .xdata).

Per ogni frammento secondario separato con un proprio prologo, è previsto che non venga eseguita alcuna regolazione dello stack nel prologo. Tutto lo spazio dello stack richiesto da un'area secondaria deve essere preallocato dall'area padre o dall'area host. Questa preallocazione mantiene la manipolazione del puntatore dello stack rigorosamente nel prologo originale della funzione.

Un caso tipico di frammenti di funzione è "separazione del codice", in cui il compilatore può spostare un'area di codice fuori dalla funzione host. Esistono tre casi insoliti che potrebbero derivare dalla separazione del codice.

Esempio

  • (area 1: inizio)

        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
        ...
    
  • (area 1: fine)

  • (area 3: inizio)

        ...
    
  • (area 3: fine)

  • (area 2: inizio)

        ...
        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
    
  • (area 2: fine)

  1. Solo prologo (area 1: tutti gli epilogi si trovano in aree separate):

    È necessario descrivere solo il prologo. Questo prologo non può essere rappresentato nel formato compatto .pdata . Nel caso completo .xdata , può essere rappresentato impostando Epilog Count = 0. Vedere area 1 nell'esempio precedente.

    Codici di rimozione: set_fp, save_regp 0,240, save_fplr_x_256, end.

  2. Solo epilogi (area 2: prologo si trova nell'area host)

    Si presuppone che dal controllo dell'ora si inserisca in questa area, sono stati eseguiti tutti i codici di prologo. La rimozione parziale può verificarsi negli epilogi allo stesso modo di una funzione normale. Questo tipo di area non può essere rappresentato da compatta .pdata. In un record completo .xdata , può essere codificato con un prologo "fantasma", racchiuso tra parentesi da una end_c coppia di codice di rimozione e end rimozione. Il carattere iniziale end_c indica che la dimensione del prologo è zero. L'indice iniziale dell'epilogo del singolo epilogo punta a set_fp.

    Codice di rimozione per l'area 2: end_c, set_fp, save_regp 0,240, save_fplr_x_256, end.

  3. Nessun prologo o epilogo (area 3: prologi e tutti gli epilogi si trovano in altri frammenti):

    Il formato compatto .pdata può essere applicato tramite l'impostazione Flag = 10. Con record completo .xdata , Epilog Count = 1. Il codice di rimozione è uguale al codice per l'area 2 precedente, ma Epilog Start Index punta anche a end_c. La rimozione parziale non verrà mai eseguita in questa area di codice.

Un altro caso più complesso di frammenti di funzione è "compattare il wrapping". Il compilatore può scegliere di ritardare il salvataggio di alcuni registri salvati dal chiamato fino all'esterno del prologo della voce della funzione.

  • (area 1: inizio)

        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
        ...
    
  • (area 2: inizio)

        stp     x21,x22,[sp,#224]       // save_regp 2, 224
        ...
        ldp     x21,x22,[sp,#224]       // save_regp 2, 224
    
  • (area 2: fine)

        ...
        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
    
  • (area 1: fine)

Nel prologo dell'area 1, lo spazio dello stack viene preallocato. È possibile notare che l'area 2 avrà lo stesso codice di rimozione anche se viene spostato dalla funzione host.

Area 1: set_fp, save_regp 0,240, save_fplr_x_256, end. L'indice iniziale dell'epilogo punta come set_fp di consueto.

Area 2: save_regp 2, 224, end_c, set_fp, save_regp 0,240, save_fplr_x_256, . end Epilog Start Index punta al primo codice save_regp 2, 224di rimozione.

Funzioni di grandi dimensioni

I frammenti possono essere usati per descrivere le funzioni superiori al limite di 1M imposto dai campi di bit nell'intestazione .xdata . Per descrivere una funzione insolitamente grande come questa, deve essere suddivisa in frammenti più piccoli di 1M. Ogni frammento deve essere regolato in modo da non suddividere un epilogo in più parti.

Solo il primo frammento della funzione conterrà un prologo; tutti gli altri frammenti sono contrassegnati come senza prologo. A seconda del numero di epilogi presenti, ogni frammento può contenere zero o più epilogi. Tenere presente che ogni ambito dell'epilogo in un frammento specifica l'offset iniziale rispetto all'inizio del frammento, non all'inizio della funzione.

Se un frammento non ha prologo e nessun epilogo, richiede comunque il proprio .pdata record (ed eventualmente .xdata) per descrivere come eseguire la rimozione dall'interno del corpo della funzione.

Esempi

Esempio 1: Concatenamento con frame, formato compatto

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

Esempio 2: Concatenamento con frame, formato completo con prologo e epilogo mirror

|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] punta alla stessa sequenza di codice di rimozione prologo.

Esempio 3: Funzione variadic unchained

|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

L'indice iniziale dell'epilogo [4] punta al centro del codice di rimozione prologo (matrice di rimozione parzialmente riutilizzata).

Vedi anche

Panoramica delle convenzioni ABI arm64
Gestione delle eccezioni arm