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é ilC
flag implica r11.
Se il
Ret
campo è impostato su 0, ilL
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 cheStack Adjust
è 0x3F4 o superiore e bit 2 è impostato.EF
o "riduzione dell'epilogo" indica cheStack 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 C
campi , L
R
, 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:
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:
- SeE
è 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.
- SeE
è 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 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. 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.
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:
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.
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.
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 .pdata
e possibilmente .xdata
diverso.
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 .pdata
e possibilmente .xdata
registrazione 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
, B
e 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 epilogoFunction Length
= 0x31 (= 0x62/2)Ret
= 1, che indica una restituzione di un ramo a 16 bitH
= 0, che indica che i parametri non sono stati homedR
= 0 eReg
= 1, che indica push/pop di r4-r5L
= 0, che indica nessun salvataggio/ripristino LRC
= 0, che indica nessun concatenamento dei fotogrammiStack 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 epilogoFunction Length
= 0x35 (= 0x6A/2)Ret
= 0, che indica un ritorno pop {pc}H
= 0, che indica che i parametri non sono stati homedR
= 0 eReg
= 3, che indica push/pop di r4-r7L
= 1, che indica che LR è stato salvato/ripristinatoC
= 0, che indica nessun concatenamento dei fotogrammiStack 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 epilogoFunction Length
= 0x2A (= 0x54/2)Ret
= 0, che indica un ritorno pop {pc}-style (in questo caso unldr pc,[sp],#0x14
ritorno)H
= 1, che indica che i parametri sono stati homedR
= 0 eReg
= 2, che indica push/pop di r4-r6L
= 1, che indica che LR è stato salvato/ripristinatoC
= 0, che indica nessun concatenamento dei fotogrammiStack 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 eccezioneE
= 0, che indica un elenco di ambiti epilogoF
= 0, che indica una descrizione completa della funzione, incluso il prologoEpilogue Count
= 0x04, che indica i 4 ambiti dell'epilogo totaleCode 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 eccezioneE
= 0, che indica un elenco di ambiti epilogoF
= 0, che indica una descrizione completa della funzione, incluso il prologoEpilogue Count
= 0x001, che indica l'ambito dell'epilogo totaleCode 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 presentiE
= 1, che indica un singolo epilogoF
= 0, che indica una descrizione completa della funzione, incluso il prologoEpilogue Count
= 0x00, che indica i codici di rimozione dell'epilogo iniziano all'offset 0x00Code 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 epilogoFunction Length
= 0x0B (= 0x16/2)Ret
= 0, che indica un ritorno pop {pc}H
= 0, che indica che i parametri non sono stati homedR
= 0 eReg
= 7, a indicare che non sono stati salvati/ripristinati registriL
= 1, che indica che LR è stato salvato/ripristinatoC
= 0, che indica nessun concatenamento dei fotogrammiStack 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