Informazioni di unwinding per la gestione delle eccezioni V3

Ambito

Unwind Information V3 aggiunge il supporto a Intel APX (Advanced Performance Extensions). Offre inoltre una maggiore flessibilità per la generazione di codice consentita sia nei prologhi di funzione che negli epiloghi, consentendo ai compilatori di ottimizzare meglio le funzioni nel suo complesso.

Unwind V3 è necessario per il codice che supporta APX. Il codice non abilitato per APX deve comunque usare le informazioni di rimozione convenzionali (pre-V3).

Avvertimento

Unwind Information V3 è una specifica in anteprima. Esiste ancora un rischio di modifiche o omissioni che causano un'interruzione. Il codice prodotto in questa guida deve essere considerato codice di anteprima.

Terminologia

Termine Definizione
Frammento Area contigua del codice del computer descritto da una singola RUNTIME_FUNCTION / UNWIND_INFO_V3 coppia. Una funzione può essere costituita da un frammento principale e da zero o più deframmentamenti concatenati. Per altre informazioni, vedere Strutture di informazioni di unwind concatenate.
WOD Il descrittore dell'operazione di unwinding è una codifica compatta a lunghezza variabile di una singola operazione di unwinding (push, alloc, save e così via).
Pool WOD Matrice di byte all'interno del payload che archivia tutti i WOD per il prologo e l'epilogo di un frammento.
IP Offset Offset di byte senza segno di un'istruzione rispetto all'inizio del prologo o dell'epilogo.
Payload La regione a lunghezza variabile immediatamente successiva all'intestazione UNWIND_INFO_V3 di 4 byte, la cui dimensione è espressa in PayloadWords parole di 16 bit. Contiene l'estensione del prologo di grandi dimensioni, gli offset IP del prologo, i descrittori dell'epilogo (con i relativi offset IP) e il pool WOD.
RVA L'indirizzo virtuale relativo è l'offset dall'indirizzo di base dell'immagine in fase di caricamento.

Layout generale del file oggetto

Unwind V3 riutilizza senza modificarle le convenzioni di sezione PE/COFF .pdata e .xdata esistenti:

.pdata   - sorted array of IMAGE_AMD64_RUNTIME_FUNCTION_ENTRY (12 bytes each)
.xdata   - UNWIND_INFO_V3 structures referenced by .pdata entries

Ogni RUNTIME_FUNCTION voce è:

Scarto Dimensione Campo
0 4 BeginAddress - RVA dell'inizio del frammento
4 4 EndAddress - RVA del primo byte successivo alla fine del frammento
8 4 UnwindInfoAddress - RVA di UNWIND_INFO_V3 in .xdata

Nessuna modifica da V1 o V2.

UNWIND_INFO_V3 Intestazione

Tutti i campi multi byte sono little-endian. Bit 0 è l'LSB di ogni byte.

Byte Bit Campo Larghezza
0 [2:0] Version (= 3) 3 bit
0 [7:3] Flags 5 bit
1 [7:0] SizeOfProlog 8 bit
2 [7:0] PayloadWords 8 bit
3 [4:0] NumberOfOps 5 bit
3 [7:5] NumberOfEpilogs 3 bit

Semantica dei campi

Campo Descrizione
Version Deve essere 3.
Flags Stesse definizioni di flag di V1 o V2: UNW_FLAG_EHANDLER (0x01), UNW_FLAG_UHANDLER (0x02), UNW_FLAG_CHAININFO (0x04). Novità nella versione 3: UNW_FLAG_LARGE (0x08). Bit 4 riservato (zero).
SizeOfProlog Offset di byte all'inizio della prima istruzione che non fa parte del prologo. Quando UNW_FLAG_LARGE è impostato, questo campo di 8 bit corrisponde al byte meno significativo di una dimensione del prologo di 16 bit. Per altre informazioni sul modulo a 16 bit, vedere UNW_FLAG_LARGE e UNWIND_INFO_LARGE_V3.
PayloadWords Numero di parole a 16 bit del payload successive a questo header. Il payload contiene gli offset IP del prologo, tutti i descrittori di epilogo (compresi i relativi offset IP) e il pool WOD. Non include l'RVA del gestore di eccezioni né l'elemento concatenato RUNTIME_FUNCTION che potrebbe seguire. L'algoritmo per individuare i dati del gestore o della catena è uguale a V1 o V2: header + 4 + PayloadWords * 2, allineato a DWORD.
NumberOfOps Numero di WOD nel prologo (0-31). Zero indica che non è previsto alcun prologo. Se una funzione richiede più di 31 operazioni del prologo, usare un sottoframmento.
NumberOfEpilogs Conteggio dei descrittori EPILOG_INFO_V3 che seguono gli offset IP del prologo (0–7). Zero indica che in questo frammento non sono presenti epilogi. Se sono necessari più di 7, usare un sottoframmento.

UNW_FLAG_LARGE e UNWIND_INFO_LARGE_V3

Quando UNW_FLAG_LARGE è impostato nell'intestazione Flags, il primo byte del payload è un'estensione a 1 byte UNWIND_INFO_LARGE_V3 :

Byte Bit Campo Larghezza
0 [7:0] SizeOfPrologHighByte 8 bit

SizeOfPrologHighByte viene combinato con UNWIND_INFO_V3.SizeOfProlog per formare una dimensione di prologo a 16 bit: SizeOfProlog16 = (SizeOfPrologHighByte << 8) | SizeOfProlog.

Inoltre, quando UNW_FLAG_LARGE è impostato:

  • Le voci di offset IP prolog sono senza segno a 16 bit (2 byte ciascuno) anziché a 8 bit.

Il UNWIND_INFO_LARGE_V3 byte fa parte del payload ed è incluso in PayloadWords. La formula di offset dell'handler rimane invariata.

Questo flag è necessario solo per i prolog che superano i 255 byte.

Individuazione del gestore delle eccezioni / informazioni della catena

Identico a V1 o V2:

handler_offset = ALIGN_UP(sizeof(UNWIND_INFO_V3) + PayloadWords * 2, 4)

Se UNW_FLAG_EHANDLER o UNW_FLAG_UHANDLER è impostato, un'RVA a 4 byte del gestore si trova in handler_offset, seguita dai dati del gestore specifici del linguaggio.

Se UNW_FLAG_CHAININFO è impostato, un byte a 12 byte RUNTIME_FUNCTION si trova in handler_offset.

Layout del payload

Immediatamente dopo l'intestazione di 4 byte, il payload è organizzato nel seguente ordine (il tutto in formato packed, semantica #pragma pack(1)):

  1. UNWIND_INFO_LARGE_V3 - 1 byte, presente solo se UNW_FLAG_LARGE è impostato.
  2. Offset IP del prologo - NumberOfOps byte (o parole di 16 bit se UNW_FLAG_LARGE).
  3. Per ogni epilogo (ripetuto NumberOfEpilogs volte):
    • EPILOG_INFO_V3 Descrittore.
    • EPILOG_INFO_EX_V3 o EPILOG_INFO_LARGE_EX_V3 descrittore esteso (presente solo quando NumberOfOps > 0).
    • Matrice di offset IP : NumberOfOps byte (o parole a 16 bit se EPILOG_INFO_LARGE).
  4. Pool WOD: byte rimanenti.

Dimensione totale = PayloadWords × 2 byte (può includere riempimento per riempire le parole).

Offset IP di Prolog

Un array di elementi NumberOfOps. Ogni voce è un byte senza segno che indica l'offset IP dall'inizio del frammento di istruzione che esegue la corrispondente operazione di unwinding. Quando UNW_FLAG_LARGE è impostata, ogni voce è invece una word a 16 bit senza segno.

Ordine: Il primo elemento corrisponde all'operazione più vicina al corpo della funzione (l'ultima istruzione del prologo con un effetto di unwinding). L'ultima voce corrisponde all'operazione più vicina al punto di ingresso della funzione (la prima istruzione di prologo). Corrisponde alla convenzione V1 o V2 di elencare i codici di rimozione nell'ordine di esecuzione inversa. I WOD prologo iniziano sempre in corrispondenza dell'offset di byte zero del pool WOD e si applica lo stesso ordinamento.

Descrittori di epilogo

Zero o più strutture EPILOG_INFO_V3 seguono gli offset IP del prologo. Ognuno può avere un valore facoltativo EPILOG_INFO_EX_V3 (o EPILOG_INFO_LARGE_EX_V3 quando il EPILOG_INFO_LARGE flag è impostato) più la propria matrice di offset IP a dimensione variabile, di dimensioni NumberOfOps:

Quando NumberOfOps > 0 (descrittore completo)

Modulo standard (EPILOG_INFO_LARGE non impostato):

EPILOG_INFO_V3:

Byte Bit Campo Larghezza
0 [2:0] Flags 3 bit
0 [7:3] NumberOfOps 5 bit
1–2 [15:0] EpilogOffset Segno a 16 bit

EPILOG_INFO_EX_V3:

Byte Bit Campo Larghezza
0–1 [15:0] FirstOp Senza segno a 16 bit
2 [7:0] IpOffsetOfLastInstruction 8 bit senza segno
3 .. - Matrice di offset IP NumberOfOps byte, senza segno

Formato grande (EPILOG_INFO_LARGE impostato):

EPILOG_INFO_V3:

Byte Bit Campo Larghezza
0 [2:0] Flags 3 bit
0 [7:3] NumberOfOps 5 bit
1–2 [15:0] EpilogOffset Segno a 16 bit

EPILOG_INFO_LARGE_EX_V3:

Byte Bit Campo Larghezza
0–1 [15:0] FirstOp Senza segno a 16 bit
2–3 [15:0] IpOffsetOfLastInstruction 16 bit senza segno
4 .. - Matrice di offset IP NumberOfOps × 2 byte, senza segno a 16 bit

Quando NumberOfOps == 0 (descrittore ereditato)

EPILOG_INFO_V3:

Byte Bit Campo Larghezza
0 [2:0] Flags 3 bit
0 [7:3] NumberOfOps (= 0) 5 bit
1–2 [15:0] EpilogOffset Segno a 16 bit

Un EPILOG_INFO_V3 record con NumberOfOps == 0 eredita i valori effettivi per NumberOfOps, FirstOp, IpOffsetOfLastInstruction campi e matrice di offset IP dal primo oggetto precedente EPILOG_INFO_V3 con NumberOfOps != 0. Il compilatore deve garantire che Flags i bit 0 e 1 in un record di epilogo abbiano NumberOfOps == 0 lo stesso valore di quelli del primo EPILOG_INFO_V3 precedente con NumberOfOps != 0.

Semantica del campo Epilog

Campo Descrizione
Flags Bit 0: EPILOG_INFO_PARENT_FRAGMENT_TRANSFER - impostato se questo epilogo trasferisce il controllo al frammento padre (ad esempio, tramite JMP) anziché restituirlo al chiamante. Bit 1: EPILOG_INFO_LARGE - se impostato, il descrittore esteso usa EPILOG_INFO_LARGE_EX_V3 (16 bit IpOffsetOfLastInstruction) e ogni voce dell'offset IP è a 16 bit, consentendo di gestire epiloghi che superano i 255 byte. Bit 2: riservato (zero).
NumberOfOps Numero di WOD per questo epilogo (0-31). Zero è un valore speciale che significa "eredita dal descrittore dell'epilogo precedente".
EpilogOffset Spostamento con segno a 16 bit alla prima istruzione di questo epilogo. Per il primo descrittore di epilogo, i valori positivi sono offset in byte dall'inizio del frammento e i valori negativi sono offset in byte rispetto alla coda del frammento, ovvero il primo byte oltre la fine. Per i descrittori di epilogo successivi: delta a partire dall'inizio dell'epilogo precedente. Tutti gli epilogi devono usare lo stesso segno, ovvero tutti in ordine crescente dall'inizio o tutti ordinati in ordine decrescente dalla coda.
FirstOp Indice di byte nel pool WOD in cui risiede il primo WOD per questo epilogo. Si tratta di un offset di byte, non di un indice WOD. Gli epiloghi possono condividere i WOD con il prologo o fra loro puntando alla stessa area del pool.
IpOffsetOfLastInstruction Offset in byte senza segno calcolato dall'inizio dell'epilogo fino all'ultima istruzione dell'epilogo, in genere RET o JMP. 8 bit in EPILOG_INFO_EX_V3; 16 bit in EPILOG_INFO_LARGE_EX_V3. L'unwinder utilizza questo valore per determinare dove finisce l'epilogo e riprende il corpo della funzione.

Offset IP di Epilog

Subito dopo ogni descrittore epilogo completo (quando NumberOfOps > 0), una matrice di NumberOfOps voci restituisce l'offset IP di ogni istruzione epiloga con un WOD corrispondente. Ogni voce è un byte senza segno o una parola senza segno a 16 bit quando EPILOG_INFO_LARGE viene impostata. Ordinamento: prima voce = operazione più vicina al corpo (prima istruzione dell'epilogo con un effetto di unwinding), ultima voce = operazione più vicina all'istruzione di trasferimento del controllo.

WOD Pool

I byte rimanenti nel payload formano il pool WOD. I WOD del prologo iniziano in modo implicito in corrispondenza dell'offset di byte 0 di questo pool. L'elenco WOD di ogni epilogo inizia in corrispondenza dell'offset di byte specificato dal relativo FirstOp campo. I WOD sono compressi senza allineamento o spaziatura interna tra di essi.

Il numero di WOD consumati per il prologo è UNWIND_INFO_V3.NumberOfOps. Il numero consumato per ciascun epilogo è quello del rispettivo EPILOG_INFO_V3.NumberOfOps (o quello ereditato).

Informazioni di riferimento sulla codifica WOD

I WOD sono a lunghezza variabile (1-5 byte), compressi con #pragma pack(1). Il codice operativo viene codificato nei bit di ordine minimo del primo byte. La decodifica richiede un'ispezione a più livelli.

Tabella di smistamento dei codici operativi

Per decodificare un WOD, leggere il primo byte e testarne i bit bassi nell'ordine seguente:

Test Valore del codice operativo Tipo di WOD Dimensione
byte[0] == 0x00 0 WOD_SET_FPREG 2 byte
byte[0] == 0x01 1 WOD_ALLOC_HUGE 5 byte
byte[0] == 0x02 2 WOD_ALLOC_LARGE 3 byte
byte[0] == 0x03 3 WOD_PUSH_CANONICAL_FRAME 2 byte
(byte[0] & 0x07) == 0x04 4 WOD_PUSH 1 byte
(byte[0] & 0x07) == 0x05 5 WOD_SAVE_NONVOL_FAR 5 byte
(byte[0] & 0x07) == 0x06 6 WOD_SAVE_NONVOL 3 byte
(byte[0] & 0x07) == 0x07 7 WOD_PUSH_CONSECUTIVE_2 1 byte
(byte[0] & 0x0F) == 0x08 8 WOD_ALLOC_SMALL 1 byte
(byte[0] & 0x0F) == 0x09 9 WOD_SAVE_XMM128_FAR 5 byte
(byte[0] & 0x0F) == 0x0A 10 WOD_SAVE_XMM128 3 byte
(byte[0] & 0x3F) == 0x20 32 WOD_PUSH2 2 byte

** Algoritmo di decodifica WOD (pseudocodice):**

uint8_t b0 = pool[offset];
uint8_t op3 = b0 & 0x07;

switch (op3)
{
case 4: return WOD_PUSH;           // 3-bit opcode = 100b
case 5: return WOD_SAVE_NONVOL_FAR;// 3-bit opcode = 101b
case 6: return WOD_SAVE_NONVOL;    // 3-bit opcode = 110b
case 7: return WOD_PUSH_CONSECUTIVE_2; // 3-bit opcode = 111b
default: break; // bits[2:0] are 0b000, 0b001, 0b010, or 0b011
}

uint8_t op4 = b0 & 0x0F;
switch (op4)
{
case 0x08: return WOD_ALLOC_SMALL;
case 0x09: return WOD_SAVE_XMM128_FAR;
case 0x0A: return WOD_SAVE_XMM128;
default: break;
}

uint8_t op6 = b0 & 0x3F;
if (op6 == 0x20) return WOD_PUSH2;

// 8-bit opcode (full byte match)
switch (b0)
{
case 0x00: return WOD_SET_FPREG;
case 0x01: return WOD_ALLOC_HUGE;
case 0x02: return WOD_ALLOC_LARGE;
case 0x03: return WOD_PUSH_CANONICAL_FRAME;
default:   return INVALID;
}

layout WOD (a livello di bit)

Tutte le posizioni di bit sono numerate LSB-first all'interno di ogni byte. I campi integer a più byte sono little-endian.

WOD_PUSH - 1 byte

Byte 0: [2:0] = 100b (opcode 4)
        [7:3] = Register (5 bits, AMD64 integer register number)

Effetto:PUSH <reg> regola RSP di 8 e archivia la registrazione.

WOD_PUSH2 - 2 byte

Byte 0: [5:0] = 100000b (opcode 32)
        [7:6] = Register1[1:0] (low 2 bits)
Byte 1: [2:0] = Register1[4:2] (high 3 bits)
        [7:3] = Register2 (5 bits)

Effetto:PUSH2 <reg1>, <reg2> esegue il push di due registri con una singola istruzione (APX). Regola l'RSP di 16.

WOD_PUSH_CONSECUTIVE_2 - 1 byte

Byte 0: [2:0] = 111b (opcode 7)
        [7:3] = Register (5 bits)

Effetto: Spinge Register e Register+1 consecutivamente. Regola RSP di 16 in totale. Register il valore deve essere limitato a [0, 30], in quanto il valore di 31 verrebbe posizionato Register+1 fuori dai limiti.

WOD_ALLOC_SMALL - 1 byte

Byte 0: [3:0] = 1000b (opcode 8)
        [7:4] = Size (4 bits)

Allocazione effettiva:(Size + 1) * 8 byte. Intervallo: 8–128 byte con incrementi di 8.

WOD_ALLOC_LARGE - 3 byte

Byte 0: [7:0] = 0x02 (opcode 2)
Bytes 1–2: Size (16-bit unsigned, little-endian)

Allocazione effettiva:Size * 8 byte. Intervallo: fino a 524.280 byte.

WOD_ALLOC_HUGE - 5 byte

Byte 0: [7:0] = 0x01 (opcode 1)
Bytes 1–4: Size (32-bit unsigned, little-endian)

Allocazione effettiva: Numero di byte non elaborato (nessuna scalabilità). Intervallo: fino a 4 GiB.

WOD_SET_FPREG - 2 byte

Byte 0: [7:0] = 0x00 (opcode 0)
Byte 1: [3:0] = Register (4 bits, 0–15)
        [7:4] = Offset (4 bits)

Effetto: Stabilisce un puntatore al frame. <reg> = RSP + Offset * 16.

WOD_SAVE_NONVOL - 3 byte

Byte 0: [2:0] = 110b (opcode 6)
        [7:3] = Register (5 bits)
Bytes 1–2: Displacement (16-bit unsigned, little-endian)

Effetto:MOV [RSP + Displacement * 8], <reg> salva un registro intero non volatile nello stack.

WOD_SAVE_NONVOL_FAR - 5 byte

Byte 0: [2:0] = 101b (opcode 5)
        [7:3] = Register (5 bits)
Bytes 1–4: Displacement (32-bit unsigned, little-endian)

Effetto: Uguale a WOD_SAVE_NONVOL ma con uno spostamento di byte non ridimensionato a 32 bit. Usato quando l'offset non rientra in 16 bit moltiplicati per 8.

WOD_SAVE_XMM128 - 3 byte

Byte 0: [3:0] = 1010b (opcode 10)
        [7:4] = Register (4 bits, XMM0–XMM15)
Bytes 1–2: Displacement (16-bit unsigned, little-endian)

Effetto:MOVAPS [RSP + Displacement * 16], <xmm> salva un registro XMM a 128 bit.

WOD_SAVE_XMM128_FAR - 5 byte

Byte 0: [3:0] = 1001b (opcode 9)
        [7:4] = Register (4 bits, XMM0–XMM15)
Bytes 1–4: Displacement (32-bit unsigned, little-endian)

Effetto: Uguale a WOD_SAVE_XMM128 ma con uno spostamento di byte non ridimensionato a 32 bit.

WOD_PUSH_CANONICAL_FRAME - 2 byte

Byte 0: [7:0] = 0x03 (opcode 3)
Byte 1: [7:0] = Type (8 bits)

Effetto: Indica che l'hardware/sistema operativo ha eseguito il push di un frame canonico nello stack. I valori di tipo distinguono:

  • Telaio della macchina senza codice di errore
  • Telaio della macchina con codice di errore
  • Telaio della macchina con spinta della pila ombra
  • Record di contesto

I valori di tipo esatti sono definiti dal sistema operativo. Consultare le intestazioni Windows SDK.

Riepilogo delle costanti del codice operativo

#define WOD_OP_SET_FPREG              0   // 8-bit opcode, 2 bytes
#define WOD_OP_ALLOC_HUGE             1   // 8-bit opcode, 5 bytes
#define WOD_OP_ALLOC_LARGE            2   // 8-bit opcode, 3 bytes
#define WOD_OP_PUSH_CANONICAL_FRAME   3   // 8-bit opcode, 2 bytes
#define WOD_OP_PUSH                   4   // 3-bit opcode, 1 byte
#define WOD_OP_SAVE_NONVOL_FAR        5   // 3-bit opcode, 5 bytes
#define WOD_OP_SAVE_NONVOL            6   // 3-bit opcode, 3 bytes
#define WOD_OP_PUSH_CONSECUTIVE_2     7   // 3-bit opcode, 1 byte
#define WOD_OP_ALLOC_SMALL            8   // 4-bit opcode, 1 byte
#define WOD_OP_SAVE_XMM128_FAR        9   // 4-bit opcode, 5 bytes
#define WOD_OP_SAVE_XMM128           10   // 4-bit opcode, 3 bytes
#define WOD_OP_PUSH2                 32   // 6-bit opcode, 2 bytes

Codifica del registro

I registri interi usano la numerazione AMD64 standard (5 bit, 0-31):

Value Registrazione
0 RAX
1 RCX
2 RDX
3 RBX
4 RSP
5 RBP
6 RSI
7 RDI
8 - 15 R8 - R15
16 - 31 R16 - R31 (APX)

I campi del registro XMM sono di 4 bit (0–15) e corrispondono direttamente a XMM0–XMM15.