Condividi tramite


Gestione delle eccezioni ARM

Windows in ARM usa lo stesso meccanismo strutturato di gestione delle eccezioni 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 ARM e gli helper del linguaggio sia l'assembler Microsoft ARM che il compilatore MSVC generano.

Gestione delle eccezioni ARM

Windows in ARM usa i codici di rimozione per controllare la rimozione dello stack durante la gestione delle eccezioni strutturata (SEH). I codici di rimozione sono una sequenza di byte archiviati nella .xdata sezione dell'immagine eseguibile. Questi codici descrivono il funzionamento del prologo e dell'epilogo della funzione in modo astratto. Il gestore li usa per annullare gli effetti del prologo della funzione quando si rimuove allo stack frame del chiamante.

Arm EABI (interfaccia binaria dell'applicazione incorporata) specifica un modello per la rimozione delle eccezioni che usa i codici di rimozione. Il modello non è sufficiente per la rimozione di SEH in Windows. Deve gestire casi asincroni in cui il processore si trova al centro del prologo o dell'epilogo di una funzione. Windows suddivide inoltre il controllo della rimozione in rimozione a livello di funzione e rimozione con ambito specifico del linguaggio, unificate nell'interfaccia EABI ARM. Per queste ragioni Windows su ARM specifica maggiori dettagli per i dati e la procedura di rimozione.

Presupposti

Le immagini eseguibili per Windows su ARM usano il formato Portable Executable (PE). Per altre informazioni, vedere Formato PE. Le informazioni sulla gestione delle eccezioni vengono archiviate nelle .pdata sezioni e .xdata dell'immagine.

Il meccanismo di gestione delle eccezioni si basa su alcuni presupposti in relazione al codice che segue l'ABI per Windows su ARM:

  • Quando si verifica un'eccezione all'interno del corpo di una funzione, il gestore potrebbe annullare le operazioni del prologo o eseguire le operazioni dell'epilogo in modo forward. Entrambi devono produrre risultati identici.

  • Prologhi ed epiloghi tendono a riflettersi. Questa funzionalità può essere usata per ridurre le dimensioni dei metadati necessari per descrivere la rimozione.

  • Le funzioni tendono a essere relativamente piccole. Diverse ottimizzazioni si basano su questa osservazione per una compressione efficiente dei dati.

  • Se viene inserita una condizione in un epilogo, sarà valida anche per tutte le istruzioni contenute nell'epilogo.

  • Se il prologo salva il puntatore dello stack (SP) in un altro registro, tale registro deve rimanere invariato in tutta la funzione, in modo che il sp originale possa essere recuperato in qualsiasi momento.

  • A meno che l'SP non venga salvato in un altro registro, tutte le relative modifiche devono avvenire esclusivamente all'interno di prologo ed epilogo.

  • La rimozione di uno stack frame richiede le operazioni seguenti:

    • Regolare r13 (SP) a incrementi di 4 byte.

    • Prelevare uno o più registri Integer.

    • Prelevare uno o più registri Pop VFP (Virtual Floating-Point).

    • Copiare un valore di registro arbitrario in r13 (SP).

    • Caricare l'SP dallo stack mediante una piccola operazione post-decremento.

    • Analizzare uno dei pochi tipi di frame definiti correttamente.

.pdata Archivio

I .pdata record in un'immagine in formato PE sono una matrice ordinata di elementi a lunghezza fissa che descrivono ogni funzione di modifica dello stack. Le funzioni foglia (funzioni che non chiamano altre funzioni) non richiedono .pdata record quando non modificano lo stack. In altri termini, non richiedono archiviazione locale e non devono salvare o ripristinare registri non volatili. I record per queste funzioni possono essere omessi dalla .pdata sezione per risparmiare spazio. Un'operazione di rimozione da una di queste funzioni può semplicemente copiare l'indirizzo mittente dal registro collegamenti (LR) al contatore del programma (PC) per risalire al chiamante.

Ogni .pdata record per ARM è lungo 8 byte. Il formato generale di un record inserisce l'indirizzo virtuale relativo della funzione iniziale nella prima parola a 32 bit, seguito 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, come illustrato in questa tabella:

Offset parola BITS Scopo
0 0-31 Function Start RVA è l'RVA a 32 bit dell'inizio della funzione. Se la funzione contiene codice Thumb, è necessario impostare il bit inferiore di questo indirizzo.
1 0-1 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'appliance virtuale di controllo delle informazioni sulle eccezioni (con i due bit bassi in modo implicito 0). Se Flag è diverso da zero, i bit rimanenti formano una struttura di dati di rimozione compressi.
1 2-31 Informazioni sulle eccezioni RVA o 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.

Dati di rimozione compressi

Per le funzioni i cui prologhi ed epiloghi seguono la forma canonica descritta sotto, è possibile usare i dati di rimozione compressi. Elimina la necessità di un .xdata record e riduce significativamente lo spazio necessario per fornire i dati di rimozione. I prologhi e gli epiloghi canonici sono progettati per soddisfare i requisiti comuni di una funzione semplice che non richiede un gestore eccezioni ed esegue le operazioni di configurazione e di disinstallazione in un ordine standard.

Questa tabella mostra il formato di un .pdata record con dati di rimozione compressi:

Offset parola BITS Scopo
0 0-31 Function Start RVA è l'RVA a 32 bit dell'inizio della funzione. Se la funzione contiene codice Thumb, è necessario impostare il bit inferiore di questo indirizzo.
1 0-1 Flag è un campo a 2 bit con questi significati:

- 00 = dati di rimozione compressi non usati; i bit rimanenti puntano al .xdata record.
- 01 = dati di rimozione compressi.
- 10 = dati di rimozione compressi in cui si presuppone che la funzione non abbia alcun prologo. Si rivela utile per la descrizione dei frammenti di funzione non contigui all'inizio della funzione.
- 11 = riservato.
1 2-12 Function Length è un campo a 11 bit che fornisce la lunghezza dell'intera funzione in byte divisi per 2. Se la funzione è maggiore di 4 KB, è necessario usare invece un record completo .xdata .
1 13-14 Ret è un campo a 2 bit che indica come restituisce la funzione:

- 00 = restituito tramite pop {pc} (il L bit del flag deve essere impostato su 1 in questo caso).
- 01 = restituito usando un ramo a 16 bit.
- 10 = restituito usando un ramo a 32 bit.
- 11 = nessun epilogo. Si rivela utile per descrivere un frammento di funzione non contiguo che può solo contenere un prologo, ma il cui epilogo si trova altrove.
1 15 H è un flag a 1 bit che indica se la funzione "ospita" il parametro integer registra (r0-r3) eseguendo il push all'inizio della funzione e dealloca i 16 byte dello stack prima di restituire. (0 = non registra le abitazioni, 1 = registri case.
1 16-18 Reg è un campo a 3 bit che indica l'indice dell'ultimo registro non volatile salvato. Se il R bit è 0, vengono salvati solo i registri integer e si presuppone che si trovi nell'intervallo di r4-rN, dove N è uguale a 4 + Reg. Se il R bit è 1, vengono salvati solo i registri a virgola mobile e si presuppone che si trovi nell'intervallo di d8-dN, dove N è uguale a 8 + Reg. La combinazione speciale di R = 1 e Reg = 7 indica che non vengono salvati registri.
1 19 R è un flag a 1 bit che indica se i registri non volatili salvati sono registri interi (0) o registri a virgola mobile (1). Se R è impostato su 1 e il Reg campo è impostato su 7, non sono stati inseriti registri non volatili.
1 20 L è un flag a 1 bit che indica se la funzione salva/ripristina LR, insieme ad altri registri indicati dal Reg campo. (0 = non salva/ripristina, 1 = salva/ripristina.
1 21 C è un flag a 1 bit che indica se la funzione include istruzioni aggiuntive per configurare una catena di fotogrammi per il passeggio rapido dello stack (1) o meno (0). Se questo bit è impostato, r11 viene aggiunto implicitamente all'elenco di registri Integer non volatili salvati. Vedere le restrizioni seguenti se viene usato il C flag.
1 22-31 Stack Adjust è un campo a 10 bit che indica il numero di byte dello stack allocati per questa funzione, diviso per 4. Tuttavia, solo i valori compresi tra 0x000 e 0x3F3 possono essere codificati direttamente. Le funzioni che allocano più di 4044 byte dello stack devono usare un record completo .xdata . Se il Stack Adjust campo è 0x3F4 o superiore, i 4 bit bassi hanno un significato speciale:

- I bit 0-1 indicano il numero di parole di regolazione dello stack (1-4) meno 1.
- Bit 2 è impostato su 1 se il prologo ha combinato questa regolazione nell'operazione push.
- Bit 3 è impostato su 1 se l'epilogo combinato questa regolazione nell'operazione pop.

Date le possibili ridondanze nelle codifiche descritte sopra, si applicano le seguenti limitazioni:

  • Se il C flag è impostato su 1:

    • Il L flag deve anche essere impostato su 1, perché il concatenamento dei fotogrammi richiede sia r11 che LR.

    • R11 non deve essere incluso nel set di registri descritti da Reg. Ovvero, se viene eseguito il push di r4-r11, Reg deve descrivere solo r4-r10, perché il C flag implica r11.

  • Se il Ret campo è impostato su 0, il L flag deve essere impostato su 1.

La violazione di queste restrizioni determina una sequenza non supportata.

Ai fini della discussione seguente, due pseudo-flag sono derivati da Stack Adjust:

  • PF o "riduzione del prologo" indica che Stack Adjust è 0x3F4 o superiore e bit 2 è impostato.

  • EF o "riduzione dell'epilogo" indica che Stack Adjust è 0x3F4 o superiore e bit 3 è impostato.

I prologhi per le funzioni canoniche possono contenere fino a 5 istruzioni (tenere presente che 3a e 3b si escludono a vicenda):

Istruzione Il codice operativo è considerato presente se: Dimensione Opcode Codici di rimozione
1 H==1 16 push {r0-r3} 04
2 C==1 o L==1 o R==0 o PF==1 16/32 push {registers} 80-BF/D0-DF/EC-ED
3a C==1 e (R==1 e PF==0) 16 mov r11,sp FB
3b C==1 e (R==0 o PF==1) 32 add r11,sp,#xx FC
4 R==1 e Reg != 7 32 vpush {d8-dE} E0-E7
5 Stack Adjust != 0 e PF==0 16/32 sub sp,sp,#xx 00-7F/E8-EB

L'istruzione 1 è sempre presente se il H bit è impostato su 1.

Per configurare il concatenamento dei fotogrammi, l'istruzione 3a o 3b è presente se il C bit è impostato. È un mov a 16 bit se non viene eseguito il push di registri diversi da r11 e LR; in caso contrario, è un add a 32 bit.

Se viene specificata una regolazione non ridotta, l'istruzione 5 è la regolazione dello stack esplicita.

Le istruzioni 2 e 4 sono impostate in base alla necessità o meno di un'operazione push. Questa tabella riepiloga i registri salvati in base ai Ccampi , LR, e PF . In tutti i casi, N è uguale a Reg + 4, E è uguale a Reg + 8 ed S è uguale a (~Stack Adjust) & 3.

A L R PF Registri Integer sottoposti a push Registri VFP sottoposti a push
0 0 0 0 r4 - r*N* Nessuno
0 0 0 1 r*S* - r*N* Nessuno
0 0 1 0 Nessuno d8 - d*E*
0 0 1 1 r*S* - r3 d8 - d*E*
0 1 0 0 r4 - r*N*, LR Nessuno
0 1 0 1 r*S* - r*N*, LR Nessuno
0 1 1 0 LR d8 - d*E*
0 1 1 1 r*S* - r3, LR d8 - d*E*
1 0 0 0 (codifica non valida) (codifica non valida)
1 0 0 1 (codifica non valida) (codifica non valida)
1 0 1 0 (codifica non valida) (codifica non valida)
1 0 1 1 (codifica non valida) (codifica non valida)
1 1 0 0 r4 - r*N*, r11, LR Nessuno
1 1 0 1 r*S* - r*N*, r11, LR Nessuno
1 1 1 0 r11, LR d8 - d*E*
1 1 1 1 r*S* - r3, r11, LR d8 - d*E*

Gli epiloghi per le funzioni canoniche hanno un formato analogo, ma in ordine inverso e con alcune opzioni aggiuntive. L'epilogo può contenere fino a 5 istruzioni e la sua forma dipende strettamente dalla forma del prologo.

Istruzione Il codice operativo è considerato presente se: Dimensione Opcode
6 Stack Adjust!=0 e EF==0 16/32 add sp,sp,#xx
7 R==1 e Reg!=7 32 vpop {d8-dE}
8 C==1 o (L==1 e (H==0 o Ret !=0)) o R==0 o EF==1 16/32 pop {registers}
9a H==1 e (L==0 o Ret!=0) 16 add sp,sp,#0x10
9b H==1 e L==1 e Ret==0 32 ldr pc,[sp],#0x14
10a Ret==1 16 bx reg
10b Ret==2 32 b address

L'istruzione 6 è la regolazione dello stack esplicita se viene specificata una regolazione non ridotta. Poiché PF è indipendente da EF, è possibile avere l'istruzione 5 presente senza istruzione 6 o viceversa.

Le istruzioni 7 e 8 usano la stessa logica del prologo per determinare quali registri vengono ripristinati dallo stack, ma con queste tre modifiche: prima, EF viene usata al posto di PF; secondo, se Ret = 0 e H = 0, LR viene sostituito con PC nell'elenco dei registri e l'epilogo termina immediatamente; terzo, se Ret = 0 e H = 1, quindi LR viene omesso dall'elenco dei registri e estratto dall'istruzione 9b.

Se H è impostato, è presente l'istruzione 9a o 9b. L'istruzione 9a viene usata quando Ret è diverso da zero, che implica anche la presenza di 10a o 10b. Se L=1, allora LR è stato estratto come parte dell'istruzione 8. L'istruzione 9b viene usata quando L è 1 e Ret è zero, per indicare una fine anticipata all'epilogo e per restituire e regolare lo stack contemporaneamente.

Se l'epilogo non è già terminato, è presente l'istruzione 10a o 10b per indicare un ramo a 16 bit o a 32 bit, in base al valore di Ret.

.xdata Archivio

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 con quattro sezioni:

  1. Intestazione di 1 o 2 parole che descrive le dimensioni complessive della .xdata struttura e fornisce i dati della funzione chiave. La seconda parola è presente solo se i campi Epilogo Count e Code Words sono entrambi impostati su 0. I campi sono illustrati in dettaglio in questa tabella:

    Word BITS Scopo
    0 0-17 Function Length è un campo a 18 bit che indica la lunghezza totale della funzione in byte, divisa per 2. Se una funzione è maggiore di 512 KB, è necessario usare più .pdata record e .xdata per descrivere la funzione. Per maggiori dettagli, vedere la sezione Funzioni di grandi dimensioni più avanti in questo documento.
    0 18-19 Vers è un campo a 2 bit che descrive la versione rimanente.xdata. Attualmente è definita solo la versione 0; i valori 1-3 sono riservati.
    0 20 X è un campo a 1 bit che indica la presenza (1) o l'assenza (0) dei dati delle eccezioni.
    0 21 E è un campo a 1 bit che indica che le informazioni che descrivono un singolo epilogo vengono compresse nell'intestazione (1) anziché richiedere parole di ambito aggiuntive più avanti (0).
    0 22 F è un campo a 1 bit che indica che questo record descrive un frammento di funzione (1) o una funzione completa (0). Un frammento implica che non c'è alcun prologo e che tutte le elaborazioni del prologo devono essere ignorate.
    0 23-27 Il conteggio dell'epilogo è un campo a 5 bit con due significati, a seconda dello stato del E bit:

    - Se E è 0, questo campo è un conteggio del numero totale di ambiti epilogo descritti nella sezione 2. Se nella funzione esistono più di 31 ambiti, questo campo e il campo Parole di codice devono essere entrambi impostati su 0 per indicare che è necessaria una parola di estensione.
    - Se E è 1, questo campo specifica l'indice del primo codice di rimozione che descrive l'unico epilogo.
    0 28-31 Parole di codice è un campo a 4 bit che specifica il numero di parole a 32 bit necessarie per contenere tutti i codici di rimozione nella sezione 4. Se sono necessarie più di 15 parole per più di 63 byte di codice di rimozione, questo campo e il campo Conteggio epilogo devono essere entrambi impostati su 0 per indicare che è necessaria una parola di estensione.
    1 0-15 Extended Epilogue Count è un campo a 16 bit che fornisce più spazio per la codifica di un numero insolitamente elevato di epiloghi. La parola di estensione che contiene questo campo è presente solo se i campi Epilogo Count e Code Words nella prima parola di intestazione sono entrambi impostati su 0.
    1 16-23 Parole di codice estese è un campo a 8 bit che offre più spazio per la codifica di un numero insolitamente elevato di parole di codice di rimozione. La parola di estensione che contiene questo campo è presente solo se i campi Epilogo Count e Code Words nella prima parola di intestazione sono entrambi impostati su 0.
    1 24-31 Prenotato
  2. Dopo i dati dell'eccezione (se il E bit nell'intestazione è stato impostato su 0) è un elenco di informazioni sugli ambiti dell'epilogo, che vengono compressi uno in una parola e archiviati in ordine di offset iniziale crescente. Ogni ambito contiene i campi seguenti:

    BITS Scopo
    0-17 L'offset iniziale dell'epilogo è un campo a 18 bit che descrive l'offset dell'epilogo, in byte diviso per 2, rispetto all'inizio della funzione.
    18-19 Res è un campo a 2 bit riservato per l'espansione futura. Il suo valore deve essere 0.
    20-23 Condizione è un campo a 4 bit che fornisce la condizione in cui viene eseguito l'epilogo. Per gli epiloghi non condizionali, deve essere impostato su 0xE, che significa "sempre". Si noti che un epilogo deve essere interamente condizionale o interamente non condizionale e, in modalità Thumb-2, l'epilogo inizia con la prima istruzione dopo l'opcode IT.
    24-31 L'indice iniziale dell'epilogo è un campo a 8 bit che indica l'indice byte del primo codice di rimozione che descrive questo epilogo.
  3. L'elenco di ambiti di epilogo è seguito da una matrice di byte che contiene codici di rimozione, descritti in dettaglio nella sezione Codici di rimozione di questo articolo. Questa matrice viene riempita alla fine fino al più vicino confine di parola completa. I byte sono archiviati in ordine little-endian, in modo da essere direttamente recuperabili in modalità little-endian.

  4. Se il campo X nell'intestazione è 1, i byte del codice di rimozione sono seguiti dalle informazioni sul gestore eccezioni. Questo è costituito da un'VA del gestore eccezioni che contiene l'indirizzo del gestore eccezioni, seguita immediatamente dalla quantità di dati (a lunghezza variabile) richiesta dal gestore eccezioni.

Il .xdata record è progettato in modo che sia possibile recuperare i primi 8 byte e calcolare le dimensioni complete del record, senza includere la lunghezza dei dati delle eccezioni di dimensioni variabili che seguono. Questo frammento di codice calcola la dimensione del record:

ULONG ComputeXdataSize(PULONG Xdata)
{
    ULONG Size;
    ULONG EpilogueScopes;
    ULONG UnwindWords;

    if ((Xdata[0] >> 23) != 0) {
        Size = 4;
        EpilogueScopes = (Xdata[0] >> 23) & 0x1f;
        UnwindWords = (Xdata[0] >> 28) & 0x0f;
    } else {
        Size = 8;
        EpilogueScopes = Xdata[1] & 0xffff;
        UnwindWords = (Xdata[1] >> 16) & 0xff;
    }

    if (!(Xdata[0] & (1 << 21))) {
        Size += 4 * EpilogueScopes;
    }

    Size += 4 * UnwindWords;

    if (Xdata[0] & (1 << 20)) {
        Size += 4;  // Exception handler RVA
    }

    return Size;
}

Anche se il prologo e ogni epilogo ha un indice nei codici di rimozione, la tabella viene condivisa tra di esse. Non è raro che tutti possano condividere gli stessi codici di rimozione. È consigliabile che i writer dei compilatori siano ottimizzati per questo caso, perché l'indice più grande che si può specificare è 255 e questo limita il numero totale di codici di rimozione possibile per una determinata funzione.

Codici di rimozione

La matrice di codici di rimozione è un pool di sequenze di istruzione che descrive esattamente come annullare gli effetti del prologo, nell'ordine in cui le operazioni devono essere annullate. I codici di rimozione sono un mini set di istruzioni, codificato come stringa di byte. Al termine dell'esecuzione, l'indirizzo mittente della funzione di chiamata è nel registro LR e vengono ripristinati i valori di tutti i registri non volatili al momento della chiamata della funzione.

Se fosse garantito che le eccezioni possono verificarsi solo nel corpo di una funzione e mai all'interno di un prologo o epilogo, sarebbe necessaria una sola sequenza di rimozione. Il modello di rimozione di Windows, invece, richiede la possibilità di rimozione da un prologo o un epilogo parzialmente eseguito. Per tenere conto di questo requisito, i codici di rimozione sono stati progettati accuratamente in modo da includere un mapping uno a uno non ambiguo a ogni codice operativo pertinente nel prologo e nell'epilogo. Questo ha diverse implicazioni:

  • È possibile calcolare la lunghezza del prologo e dell'epilogo contando il numero di codici di rimozione. Questo è possibile anche con istruzioni Thumb-2 a lunghezza variabile perché ci sono mapping distinti per i codici operativi a 16 e 32 bit.

  • Contando il numero di istruzioni dopo l'inizio dell'ambito di un epilogo, è possibile saltare il numero equivalente di codici di rimozione ed eseguire il resto di una sequenza per completare la rimozione parziale che l'epilogo stava eseguendo.

  • Contando il numero di istruzioni prima della fine del prologo, è possibile saltare il numero equivalente di codici di rimozione ed eseguire il resto della sequenza per annullare solo le parti di prologo già eseguite completamente.

La tabella seguente illustra il mapping dai codici di rimozione ai codici operativi. I codici più comuni includono un solo byte, mentre quelli meno comuni richiedono due, tre o persino quattro byte. Ogni codice è archiviato dal byte più significativo a quello meno significativo. La struttura di codici di rimozione è diversa rispetto alla codifica descritta in nell'interfaccia EABI ARM perché questi codici di rimozione sono progettati per disporre di un mapping uno a uno agli opcode nel prologo e nell'epilogo per consentire la rimozione di prologhi ed epiloghi parzialmente eseguiti.

Byte 1 Byte 2 Byte 3 Byte 4 Opsize Spiegazione
00-7F 16 add sp,sp,#X

dove X è (Codice & 0x7F) * 4
80-BF 00-FF 32 pop {r0-r12, lr}

dove LR viene estratto se Code & 0x2000 e r0-r12 vengono visualizzati se il bit corrispondente è impostato in Code & 0x1FFF
C0-CF 16 mov sp,rX

dove X è Code & 0x0F
D0-D7 16 pop {r4-rX,lr}

dove X è (Codice & 0x03) + 4 e LR viene estratto se Code & 0x04
D8-DF 32 pop {r4-rX,lr}

dove X è (Codice & 0x03) + 8 e LR viene estratto se Code & 0x04
E0-E7 32 vpop {d8-dX}

dove X è (Codice & 0x07) + 8
E8-EB 00-FF 32 addw sp,sp,#X

dove X è (Codice & 0x03FF) * 4
EC-ED 00-FF 16 pop {r0-r7,lr}

dove LR viene estratto se Code & 0x0100 e r0-r7 vengono visualizzati se il bit corrispondente è impostato in Code & 0x00FF
EE 00-0F 16 Specifico di Microsoft
EE 10-FF 16 Disponibile
EF 00-0F 32 ldr lr,[sp],#X

dove X è (Codice & 0x000F) * 4
EF 10-FF 32 Disponibile
F0-F4 - Disponibile
F5 00-FF 32 vpop {dS-dE}

dove S è (Codice & 0x00F0) >> 4 ed E è Codice & 0x000F
F6 00-FF 32 vpop {dS-dE}

dove S è ((Codice & 0x00F0) 4) >> + 16 e E è (Codice & 0x000F) + 16
F7 00-FF 00-FF 16 add sp,sp,#X

dove X è (Codice e 0x00FFFF) * 4
F8 00-FF 00-FF 00-FF 16 add sp,sp,#X

dove X è (Codice & 0x00FFFFFF) * 4
F9 00-FF 00-FF 32 add sp,sp,#X

dove X è (Codice e 0x00FFFF) * 4
FA 00-FF 00-FF 00-FF 32 add sp,sp,#X

dove X è (Codice & 0x00FFFFFF) * 4
FB 16 nop (16 bit)
FC 32 nop (32 bit)
FD 16 end + nop a 16 bit nell'epilogo
FE 32 end + nop a 32 bit nell'epilogo
FF - end

Viene illustrato l'intervallo di valori esadecimali per ogni byte in un codice di rimozione Codice, insieme alle dimensioni opcode Opsize e all'interpretazione originale corrispondente delle istruzioni. Le celle vuote indicano codici di rimozione più brevi. Nelle istruzioni con valori ampi che coprono più byte, i bit più significativi sono archiviati per primi. Il campo Opsize mostra le dimensioni implicite del codice operativo associate a ogni operazione Thumb-2. Le voci apparentemente duplicate nella tabella con diverse codifiche consentono di distinguere le varie dimensioni del codice operativo.

I codici di rimozione sono progettati in modo tale che il primo byte del codice indica sia la dimensione totale in byte del codice, sia la dimensione dell'opcode corrispondente nel flusso di istruzioni. Per calcolare la dimensione del prologo o epilogo, scorrere i codici di rimozione dall'inizio della sequenza fino alla fine e usare una tabella di ricerca o un metodo analogo per determinare la lunghezza dell'opcode corrispondente.

I codici di rimozione 0xFD e 0xFE sono equivalenti al codice finale normale 0xFF, ma prevedono un codice operativo nop supplementare nel caso dell'epilogo, a 16 o 32 bit. Per i prologhi, i codici 0xFD, 0xFE e 0xFF sono perfettamente equivalenti. Questo indica le terminazioni bx lr dell'epilogo comune o b <tailcall-target>, che non hanno un'istruzione prologo equivalente. In questo modo aumentano le possibilità di condivisione delle sequenze di rimozione tra il prologo e gli epiloghi.

In molti casi dovrebbe essere possibile usare lo stesso set di codici di rimozione per il prologo e tutti gli epiloghi. Per gestire la rimozione di prologhi ed epiloghi parzialmente eseguiti, tuttavia, possono essere necessarie più sequenze di codici di rimozione che variano per ordine e comportamento. Per questo motivo ogni epilogo ha un indice proprio nella matrice di rimozione per specificare l'inizio dell'esecuzione.

Rimozione di prologhi ed epiloghi parziali

Il caso di rimozione più comune è quando l'eccezione si verifica nel corpo della funzione, lontano dal prologo e da tutti gli epiloghi. In questo caso, l'agente di rimozione esegue il codice nella matrice di rimozione iniziando dall'indice 0 e prosegue fino a quando non viene rilevato un codice operativo finale.

Quando si verifica un'eccezione durante l'esecuzione di un prologo o epilogo, lo stack frame viene costruito solo parzialmente e l'agente di rimozione deve determinare esattamente quali operazioni sono state eseguite per poterle annullare correttamente.

Si consideri ad esempio questo prologo ed epilogo:

0000:   push  {r0-r3}         ; 0x04
0002:   push  {r4-r9, lr}     ; 0xdd
0006:   mov   r7, sp          ; 0xc7
...
0140:   mov   sp, r7          ; 0xc7
0142:   pop   {r4-r9, lr}     ; 0xdd
0146:   add   sp, sp, #16     ; 0x04
0148:   bx    lr

Accanto a ogni codice operativo è presente il codice di rimozione appropriato per descrivere l'operazione. La sequenza dei codici di rimozione per il prologo è un'immagine speculare dei codici di rimozione per l'epilogo, senza contare l'istruzione finale. Questo caso è comune ed è il motivo per cui si presuppone che i codici di rimozione per il prologo vengano sempre archiviati in ordine inverso rispetto all'ordine di esecuzione del prologo. Ecco quindi un set comune di codici di rimozione:

0xc7, 0xdd, 0x04, 0xfd

Il codice 0xFD è un codice speciale per la fine della sequenza che indica che l'epilogo è più lungo di un'istruzione a 16 bit rispetto al prologo. Questo aumenta notevolmente le possibilità di condivisione dei codici di rimozione.

Nell'esempio, se si verifica un'eccezione durante l'esecuzione del corpo della funzione compreso tra prologo ed epilogo, la rimozione inizia con il caso dell'epilogo, all'offset 0 all'interno del codice dell'epilogo. Questo corrisponde all'offset 0x140 nell'esempio. L'agente di rimozione esegue la sequenza di rimozione completa poiché non è stata eseguita alcuna pulizia. Se invece l'eccezione si verifica un'istruzione dopo l'inizio del codice dell'epilogo, l'agente di rimozione può eseguire la rimozione saltando il primo codice di rimozione. Dato un mapping uno-a-uno tra opcodes e codici di rimozione, se si rimuove dall'istruzione n nell'epilogo, lo rimozione deve ignorare i primi n codici di rimozione.

Una logica simile è applicabile al contrario per il prologo. In caso di rimozione dall'offset 0 nel prologo, non deve essere eseguito nulla. Per la rimozione da un'istruzione in avanti, la sequenza di rimozione deve iniziare da un codice di rimozione dalla fine perché i codici di rimozione del prologo sono archiviati in ordine inverso. Nel caso generale, se si rimuove dall'istruzione n nel prologo, la rimozione dovrebbe iniziare a eseguire a n codici di rimozione dalla fine dell'elenco dei codici.

I codici di rimozione del prologo e dell'epilogo non corrispondono sempre esattamente. In questo caso, può essere necessario che la matrice di codici di rimozione contenga più sequenze di codice. Per determinare l'offset per l'inizio dell'elaborazione dei codici, usare la logica seguente:

  1. Per la rimozione dall'interno del corpo della funzione, iniziare l'esecuzione dei codici di rimozione all'indice 0 e proseguire fino a quando non viene raggiunto un codice operativo finale.

  2. Per la rimozione dall'interno di un epilogo, usare l'indice di inizio specifico dell'epilogo fornito dall'ambito dell'epilogo. Calcolare il numero di byte di distanza tra PC e l'inizio dell'epilogo. Procedere nei codici di rimozione fino a quando non sono state considerate tutte le istruzioni già eseguite. Eseguire la sequenza di rimozione iniziando da tale punto.

  3. Per la rimozione dal prologo, iniziare dall'indice 0 nei codici di rimozione. Calcolare la lunghezza dl codice di prologo dalla sequenza, quindi calcolare il numero di byte di distanza tra PC e la fine del prologo. Procedere nei codici di rimozione fino a quando non sono state considerate tutte le istruzioni non eseguite. Eseguire la sequenza di rimozione iniziando da tale punto.

I codici di rimozione per il prologo devono essere sempre i primi nella matrice. sono anche i codici usati per la rimozione nel caso generale di rimozione dall'interno del corpo. Le sequenze di codice specifiche dell'epilogo devono essere immediatamente successive alla sequenza di codice del prologo.

Frammenti di funzione

Per ottimizzare il codice può essere utile suddividere una funzione in parti discontinue. Al termine di questa operazione, ogni frammento di funzione richiede un record separato .pdatae possibilmente .xdatadiverso.

Supponendo che il prologo della funzione si trovi all'inizio della funzione e non possa essere suddiviso, si presentano quattro casi di frammentazione delle funzioni:

  • Solo prologo; tutti gli epiloghi in altri frammenti.

  • Prologo e uno o più epiloghi; più epiloghi in altri frammenti.

  • Nessun prologo o epilogo; prologo e uno o più epiloghi in altri frammenti.

  • Solo epiloghi; prologo e possibilmente più epiloghi in altri frammenti.

Nel primo caso, deve essere descritto solo il prologo. Questa operazione può essere eseguita in forma compatta .pdata descrivendo normalmente il prologo e specificando un Ret valore pari a 3 per indicare nessun epilogo. Nel formato completo .xdata , questa operazione può essere eseguita fornendo i codici di rimozione del prologo in corrispondenza dell'indice 0 come di consueto e specificando un numero di epilogo pari a 0.

Il secondo caso corrisponde esattamente a una funzione normale. Se è presente un solo epilogo nel frammento e si trova alla fine del frammento, è possibile usare un record compatto .pdata . In caso contrario, è necessario utilizzare un record completo .xdata . Tenere presente che gli offset specificati per l'inizio dell'epilogo sono relativi all'inizio del frammento, non all'inizio originario della funzione.

Il terzo e il quarto caso sono varianti del primo e del secondo caso, rispettivamente, tranne che non contengono un prologo. In queste situazioni, si presuppone che ci sia codice prima dell'inizio dell'epilogo ed è considerato parte del corpo della funzione, che normalmente sarebbe stato deposto annullando gli effetti del prologo. Questi casi devono quindi essere codificati con uno pseudo-prologo, che descrive le modalità di rimozione dal corpo, ma che viene considerato di lunghezza 0 quando viene stabilito se eseguire una rimozione parziale all'inizio del frammento. In alternativa, questo pseudo-prologo può essere descritto usando gli stessi codici di rimozione dell'epilogo perché presumibilmente eseguono operazioni equivalenti.

Nel terzo e nel quarto caso, la presenza di uno pseudo-prologo viene specificata impostando il Flag campo del record compatto .pdata su 2 oppure impostando il flag F nell'intestazione .xdata su 1. In entrambi i casi, il controllo della presenza di una rimozione epilogo parziale viene ignorato e tutte le rimozioni non epilogo vengono considerate complete.

Funzioni di grandi dimensioni

I frammenti possono essere usati per descrivere le funzioni superiori al limite di 512 KB imposto dai campi di bit nell'intestazione .xdata . Per descrivere una funzione più grande, è sufficiente suddividerla in frammenti inferiori a 512 KB. Ogni frammento deve essere regolato in modo da non suddividere un epilogo in più parti.

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

Se un frammento non ha prologo e nessun epilogo, richiede comunque la propria .pdatae possibilmente .xdataregistrazione per descrivere come eseguire la rimozione dall'interno del corpo della funzione.

Wrapping di riduzione

Un caso speciale più complesso di frammenti di funzione è denominato wrapping della compattazione. Si tratta di una tecnica per rinviare il registro salva dall'inizio della funzione a in un secondo momento nella funzione. Ottimizza per i casi semplici che non richiedono il salvataggio del registro. Questo caso include due parti: è presente un'area esterna che alloca lo spazio dello stack, ma salva un set minimo di registri e un'area interna che salva e ripristina altri registri.

ShrinkWrappedFunction
    push   {r4, lr}          ; A: save minimal non-volatiles
    sub    sp, sp, #0x100    ; A: allocate all stack space up front
    ...                      ; A:
    add    r0, sp, #0xE4     ; A: prepare to do the inner save
    stm    r0, {r5-r11}      ; A: save remaining non-volatiles
    ...                      ; B:
    add    r0, sp, #0xE4     ; B: prepare to do the inner restore
    ldm    r0, {r5-r11}      ; B: restore remaining non-volatiles
    ...                      ; C:
    pop    {r4, pc}          ; C:

Le funzioni con wrapping compattato in genere preallocano lo spazio per il registro aggiuntivo salva nel prologo normale e quindi salvano i registri usando str o stm invece di push. Questa azione mantiene tutta la manipolazione del puntatore dello stack nel prologo originale della funzione.

La funzione ridotta di esempio deve essere suddivisa in tre aree, contrassegnate come A, Be C nei commenti. La prima A area copre l'inizio della funzione fino alla fine dei salvataggi aggiuntivi non volatili. Un .pdata record o .xdata deve essere costruito per descrivere questo frammento come prologo e senza epiloghi.

L'area centrale B ottiene il proprio .pdata record o .xdata che descrive un frammento senza prologo e senza epilogo. I codici di rimozione per quest'area devono comunque essere presenti perché è considerata un corpo di funzione. I codici devono descrivere un prologo composito che rappresenta sia i registri originali salvati nel prologo dell'area A che i registri aggiuntivi salvati prima di entrare nell'area B, come se fossero stati prodotti da una sequenza di operazioni.

Il registro salva per l'area B non può essere considerato come "prologo interno" perché il prologo composito descritto per l'area B deve descrivere sia il A prologo dell'area che i registri aggiuntivi salvati. Se il frammento B avesse un prologo, i codici di rimozione implicavano anche le dimensioni di tale prologo e non c'è modo di descrivere il prologo composito in modo da mappare uno a uno con i codici opcode che salvano solo i registri aggiuntivi.

I salvataggi aggiuntivi del registro devono essere considerati parte dell'area A, perché fino a quando non vengono completati, il prologo composito non descrive in modo accurato lo stato dello stack.

L'ultima C area ottiene il proprio .pdata record o .xdata descrive un frammento senza prologo, ma ha un epilogo.

Un approccio alternativo può essere utile anche se la manipolazione dello stack eseguita prima di entrare nell'area B può essere ridotta a un'istruzione:

ShrinkWrappedFunction
    push   {r4, lr}          ; A: save minimal non-volatile registers
    sub    sp, sp, #0xE0     ; A: allocate minimal stack space up front
    ...                      ; A:
    push   {r4-r9}           ; A: save remaining non-volatiles
    ...                      ; B:
    pop    {r4-r9}           ; B: restore remaining non-volatiles
    ...                      ; C:
    pop    {r4, pc}          ; C: restore non-volatile registers

Le informazioni dettagliate principali sono che in ogni limite di istruzione lo stack è completamente coerente con i codici di rimozione per l'area. Se si verifica una rimozione prima del push interno in questo esempio, viene considerata parte dell'area A. Solo il prologo dell'area A è unwound. Se la rimozione si verifica dopo il push interno, viene considerata parte dell'area B, che non ha prologo. Tuttavia, dispone di codici di rimozione che descrivono sia il push interno che il prologo originale dall'area A. Logica simile per il pop interno.

Ottimizzazioni della codifica

La ricchezza dei codici di rimozione, e la capacità di utilizzare forme di dati compattate ed espanse, offrono molte opportunità per ottimizzare la codifica per ridurre ulteriormente lo spazio. Con l'uso aggressivo di queste tecniche, il sovraccarico netto della descrizione di funzioni e frammenti tramite codici di rimozione può essere ridotto al minimo.

L'idea di ottimizzazione più importante: non confondere i limiti del prologo e dell'epilogo per scopi di rimozione con prologo logico ed epilogo dal punto di vista del compilatore. I limiti di rimozione possono essere ridotti e ristretti per migliorare l'efficienza. Ad esempio, un prologo può contenere codice dopo l'installazione dello stack per eseguire i controlli di verifica. Tuttavia, una volta completate tutte le modifiche dello stack, non è necessario codificare altre operazioni e non è possibile rimuoverle dal prologo di rimozione.

Questa stessa regola vale per la lunghezza della funzione. Se sono presenti dati (ad esempio un pool letterale) che seguono un epilogo in una funzione, non deve essere incluso come parte della lunghezza della funzione. Riducendo la funzione solo con il codice che fa parte della funzione, è probabile che l'epilogo si trovi alla fine e che sia possibile usare un record compatto .pdata .

In un prologo, una volta salvato il puntatore dello stack in un altro registro, in genere non è necessario registrare altri codici operativo. Per rimuovere la funzione, la prima operazione eseguita consiste nel ripristinare SP dal registro salvato. Ulteriori operazioni non hanno alcun effetto sulla rimozione.

Gli epiloghi a istruzione singola non devono essere codificati affatto, come ambiti o come codici di rimozione. Se viene eseguita una rimozione prima dell'esecuzione di tale istruzione, è possibile presupporre che si tratti dall'interno del corpo della funzione. È sufficiente eseguire i codici di rimozione del prologo. Quando la rimozione viene eseguita dopo l'esecuzione della singola istruzione, viene eseguita per definizione in un'altra area.

Gli epiloghi a più istruzioni non devono codificare la prima istruzione dell'epilogo, per lo stesso motivo del punto precedente: se la rimozione avviene prima dell'esecuzione dell'istruzione, è sufficiente una rimozione completa del prologo. Se la rimozione avviene dopo tale istruzione, è necessario considerare solo le operazioni successive.

Il riutilizzo del codice di rimozione deve essere aggressivo. L'indice di ogni ambito dell'epilogo specifica i punti a un punto iniziale arbitrario nella matrice di codici di rimozione. Non deve puntare all'inizio di una sequenza precedente; può puntare al centro. L'approccio migliore consiste nel generare la sequenza di codice di rimozione. Cercare quindi una corrispondenza esatta dei byte nel pool di sequenze già codificato. Usare qualsiasi corrispondenza perfetta come punto di partenza per il riutilizzo.

Dopo che gli epiloghi a istruzione singola vengono ignorati, se non ci sono epiloghi rimanenti, prendere in considerazione l'uso di una forma compatta .pdata ; diventa molto più probabile in assenza di un epilogo.

Esempi

In questi esempi, la base dell'immagine è in 0x00400000.

Esempio 1: funzione foglia, nessuna variabile locale

Prologue:
  004535F8: B430      push        {r4-r5}
Epilogue:
  00453656: BC30      pop         {r4-r5}
  00453658: 4770      bx          lr

.pdata (fisso, 2 parole):

  • Parola 0

    • Function Start RVA = 0x000535F8 (= 0x004535F8-0x00400000)
  • Parola 1

    • Flag = 1, che indica i formati di prologo canonico ed epilogo

    • Function Length = 0x31 (= 0x62/2)

    • Ret = 1, che indica una restituzione di un ramo a 16 bit

    • H = 0, che indica che i parametri non sono stati homed

    • R = 0 e Reg = 1, che indica push/pop di r4-r5

    • L = 0, che indica nessun salvataggio/ripristino LR

    • C = 0, che indica nessun concatenamento dei fotogrammi

    • Stack Adjust = 0, che indica nessuna regolazione dello stack

Esempio 2: funzione annidata con allocazione locale

Prologue:
  004533AC: B5F0      push        {r4-r7, lr}
  004533AE: B083      sub         sp, sp, #0xC
Epilogue:
  00453412: B003      add         sp, sp, #0xC
  00453414: BDF0      pop         {r4-r7, pc}

.pdata (fisso, 2 parole):

  • Parola 0

    • Function Start RVA = 0x000533AC (= 0x004533AC -0x00400000)
  • Parola 1

    • Flag = 1, che indica i formati di prologo canonico ed epilogo

    • Function Length = 0x35 (= 0x6A/2)

    • Ret = 0, che indica un ritorno pop {pc}

    • H = 0, che indica che i parametri non sono stati homed

    • R = 0 e Reg = 3, che indica push/pop di r4-r7

    • L = 1, che indica che LR è stato salvato/ripristinato

    • C = 0, che indica nessun concatenamento dei fotogrammi

    • Stack Adjust = 3 (= 0x0C/4)

Esempio 3: funzione variadic annidata

Prologue:
  00453988: B40F      push        {r0-r3}
  0045398A: B570      push        {r4-r6, lr}
Epilogue:
  004539D4: E8BD 4070 pop         {r4-r6}
  004539D8: F85D FB14 ldr         pc, [sp], #0x14

.pdata (fisso, 2 parole):

  • Parola 0

    • Function Start RVA = 0x00053988 (= 0x00453988-0x00400000)
  • Parola 1

    • Flag = 1, che indica i formati di prologo canonico ed epilogo

    • Function Length = 0x2A (= 0x54/2)

    • Ret = 0, che indica un ritorno pop {pc}-style (in questo caso un ldr pc,[sp],#0x14 ritorno)

    • H = 1, che indica che i parametri sono stati homed

    • R = 0 e Reg = 2, che indica push/pop di r4-r6

    • L = 1, che indica che LR è stato salvato/ripristinato

    • C = 0, che indica nessun concatenamento dei fotogrammi

    • Stack Adjust = 0, che indica nessuna regolazione dello stack

Esempio 4: funzione con più epiloghi

Prologue:
  004592F4: E92D 47F0 stmdb       sp!, {r4-r10, lr}
  004592F8: B086      sub         sp, sp, #0x18
Epilogues:
  00459316: B006      add         sp, sp, #0x18
  00459318: E8BD 87F0 ldm         sp!, {r4-r10, pc}
  ...
  0045943E: B006      add         sp, sp, #0x18
  00459440: E8BD 87F0 ldm         sp!, {r4-r10, pc}
  ...
  004595D4: B006      add         sp, sp, #0x18
  004595D6: E8BD 87F0 ldm         sp!, {r4-r10, pc}
  ...
  00459606: B006      add         sp, sp, #0x18
  00459608: E8BD 87F0 ldm         sp!, {r4-r10, pc}
  ...
  00459636: F028 FF0F bl          KeBugCheckEx     ; end of function

.pdata (fisso, 2 parole):

  • Parola 0

    • Function Start RVA = 0x000592F4 (= 0x004592F4-0x00400000)
  • Parola 1

    • Flag = 0, che indica .xdata il record presente (obbligatorio per più epiloghi)

    • .xdata address - 0x00400000

.xdata (variabile, 6 parole):

  • Parola 0

    • Function Length = 0x0001A3 (= 0x000346/2)

    • Vers = 0, che indica la prima versione di.xdata

    • X = 0, che indica che non sono presenti dati di eccezione

    • E = 0, che indica un elenco di ambiti epilogo

    • F = 0, che indica una descrizione completa della funzione, incluso il prologo

    • Epilogue Count = 0x04, che indica i 4 ambiti dell'epilogo totale

    • Code Words = 0x01, che indica una parola a 32 bit di codici di rimozione

  • Parole 1-4, che descrivono 4 ambiti di epilogo in 4 posizioni. Ogni ambito ha un set comune di codici di rimozione, condiviso con il prologo, all'offset 0x00, e non è condizionale, specificando la condizione 0x0E (sempre).

  • Codici di rimozione, a partire dalla parola 5: (condivisi tra prologo/epilogo)

    • Codice di rimozione 0 = 0x06: sp += (6 << 2)

    • Codice di rimozione 1 = 0xDE: pop {r4-r10, lr}

    • Codice di rimozione 2 = 0xFF: end

Esempio 5: funzione con stack dinamico e prologo interno

Prologue:
  00485A20: B40F      push        {r0-r3}
  00485A22: E92D 41F0 stmdb       sp!, {r4-r8, lr}
  00485A26: 466E      mov         r6, sp
  00485A28: 0934      lsrs        r4, r6, #4
  00485A2A: 0124      lsls        r4, r4, #4
  00485A2C: 46A5      mov         sp, r4
  00485A2E: F2AD 2D90 subw        sp, sp, #0x290
Epilogue:
  00485BAC: 46B5      mov         sp, r6
  00485BAE: E8BD 41F0 ldm         sp!, {r4-r8, lr}
  00485BB2: B004      add         sp, sp, #0x10
  00485BB4: 4770      bx          lr
  ...
  00485E2A: F7FF BE7D b           #0x485B28    ; end of function

.pdata (fisso, 2 parole):

  • Parola 0

    • Function Start RVA = 0x00085A20 (= 0x00485A20-0x00400000)
  • Parola 1

    • Flag = 0, che indica il .xdata record presente (necessario per più epiloghi)

    • .xdata address - 0x00400000

.xdata (variabile, 3 parole):

  • Parola 0

    • Function Length = 0x0001A3 (= 0x000346/2)

    • Vers = 0, che indica la prima versione di.xdata

    • X = 0, che indica che non sono presenti dati di eccezione

    • E = 0, che indica un elenco di ambiti epilogo

    • F = 0, che indica una descrizione completa della funzione, incluso il prologo

    • Epilogue Count = 0x001, che indica l'ambito dell'epilogo totale

    • Code Words = 0x01, che indica una parola a 32 bit di codici di rimozione

  • Parola 1: ambito di epilogo all'offset 0xC6 (= 0x18C/2), con inizio indice dei codici di rimozione in 0x00 e con la condizione 0x0E (sempre)

  • Codici di rimozione, a partire dalla parola 2: (condivisi tra prologo/epilogo)

    • Codice di rimozione 0 = 0xC6: sp = r6

    • Codice di rimozione 1 = 0xDC: pop {r4-r8, lr}

    • Codice di rimozione 2 = 0x04: sp += (4 << 2)

    • Codice di rimozione 3 = 0xFD: end, conta come istruzione a 16 bit per l'epilogo

Esempio 6: funzione con gestore di eccezioni

Prologue:
  00488C1C: 0059 A7ED dc.w  0x0059A7ED
  00488C20: 005A 8ED0 dc.w  0x005A8ED0
FunctionStart:
  00488C24: B590      push        {r4, r7, lr}
  00488C26: B085      sub         sp, sp, #0x14
  00488C28: 466F      mov         r7, sp
Epilogue:
  00488C6C: 46BD      mov         sp, r7
  00488C6E: B005      add         sp, sp, #0x14
  00488C70: BD90      pop         {r4, r7, pc}

.pdata (fisso, 2 parole):

  • Parola 0

    • Function Start RVA = 0x00088C24 (= 0x00488C24-0x00400000)
  • Parola 1

    • Flag = 0, che indica il .xdata record presente (necessario per più epiloghi)

    • .xdata address - 0x00400000

.xdata (variabile, 5 parole):

  • Parola 0

    • Function Length =0x000027 (= 0x00004E/2)

    • Vers = 0, che indica la prima versione di.xdata

    • X = 1, che indica i dati delle eccezioni presenti

    • E = 1, che indica un singolo epilogo

    • F = 0, che indica una descrizione completa della funzione, incluso il prologo

    • Epilogue Count = 0x00, che indica i codici di rimozione dell'epilogo iniziano all'offset 0x00

    • Code Words = 0x02, che indica due parole a 32 bit di codici di rimozione

  • Codici di rimozione, a partire dalla parola 1:

    • Codice di rimozione 0 = 0xC7: sp = r7

    • Codice di rimozione 1 = 0x05: sp += (5 << 2)

    • Codice di rimozione 2 = 0xED/0x90: pop {r4, r7, lr}

    • Codice di rimozione 4 = 0xFF: end

  • Word 3 specifica un gestore eccezioni = 0x0019A7ED (= 0x0059A7ED - 0x00400000)

  • La parola 4 e successive sono dati di eccezione inline

Esempio 7: Funclet

Function:
  00488C72: B500      push        {lr}
  00488C74: B081      sub         sp, sp, #4
  00488C76: 3F20      subs        r7, #0x20
  00488C78: F117 0308 adds        r3, r7, #8
  00488C7C: 1D3A      adds        r2, r7, #4
  00488C7E: 1C39      adds        r1, r7, #0
  00488C80: F7FF FFAC bl          target
  00488C84: B001      add         sp, sp, #4
  00488C86: BD00      pop         {pc}

.pdata (fisso, 2 parole):

  • Parola 0

    • Function Start RVA = 0x00088C72 (= 0x00488C72-0x00400000)
  • Parola 1

    • Flag = 1, che indica i formati di prologo canonico ed epilogo

    • Function Length = 0x0B (= 0x16/2)

    • Ret = 0, che indica un ritorno pop {pc}

    • H = 0, che indica che i parametri non sono stati homed

    • R = 0 e Reg = 7, a indicare che non sono stati salvati/ripristinati registri

    • L = 1, che indica che LR è stato salvato/ripristinato

    • C = 0, che indica nessun concatenamento dei fotogrammi

    • Stack Adjust = 1, che indica una regolazione dello stack di 1 × 4 byte

Vedi anche

Panoramica delle convenzioni ABI ARM
Problemi comuni relativi alla migrazione di Visual C++ ARM