Gestão de Exceções Informação de Desmantelamento V3

Âmbito

O Unwind Information V3 adiciona suporte para o Intel APX (Advanced Performance Extensions). Também traz flexibilidade adicional à geração de código permitida tanto nos prólogos como nos epílogos de funções, permitindo aos compiladores otimizar melhor as funções no seu conjunto.

O Unwind V3 é necessário para o código que suporta APX. O código não habilitado para APX deve continuar a usar a Informação Unwind convencional (pré-V3).

Warning

Unwind Information V3 é uma especificação preliminar. Ainda existe o risco de quebrar alterações ou omissões. O código produzido sob esta orientação deve, por si só, ser considerado código de pré-visualização.

Terminologia

Prazo Definition
Fragmento Uma região contígua de código máquina descrita por um único RUNTIME_FUNCTION / UNWIND_INFO_V3 par. Uma função pode consistir num fragmento principal e zero ou mais subfragmentos encadeados. Para mais informações, consulte Estruturas de informações de desenrolamento encadeadas.
WOD O Descritor de Operação de Desempilhamento é uma codificação compacta de comprimento variável de uma única operação de desempilhamento (push, alloc, save, e assim por diante).
Pool WOD O array de bytes dentro da carga útil que armazena todos os WODs relativos ao prólogo e ao(s) epílogo(s) de um fragmento.
Deslocamento de IP O deslocamento em bytes sem sinal de uma instrução em relação ao início do prólogo ou epílogo.
Payload A região de comprimento variável imediatamente após o cabeçalho UNWIND_INFO_V3 de 4 bytes, cujo tamanho é definido por PayloadWords palavras de 16 bits. Contém a extensão prolog grande, os deslocamentos de IP do prolog, os descritores do epilog (com os seus deslocamentos de IP) e o pool WOD.
RVA O Endereço Virtual Relativo é o deslocamento em relação ao endereço base da imagem no momento do carregamento.

Estrutura geral do ficheiro-objeto

O Unwind V3 reutiliza sem alterações as convenções de secção existentes de PE/COFF .pdata e .xdata:

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

Cada RUNTIME_FUNCTION entrada é:

Deslocamento Size Campo
0 4 BeginAddress - RVA do início do fragmento
4 4 EndAddress - RVA do primeiro byte após o fim do fragmento
8 4 UnwindInfoAddress - RVA de UNWIND_INFO_V3 em .xdata

Sem alterações em relação à V1 ou V2.

UNWIND_INFO_V3 Cabeçalho

Todos os campos de múltiplos bytes são little-endian. O bit 0 é o LSB de cada byte.

byte Bits Campo Largura
0 [2:0] Version (= 3) 3 bits
0 [7:3] Flags 5 bits
1 [7:0] SizeOfProlog 8 bits
2 [7:0] PayloadWords 8 bits
3 [4:0] NumberOfOps 5 bits
3 [7:5] NumberOfEpilogs 3 bits

Semântica de Campo

Campo Description
Version Deve ser 3.
Flags Mesmas definições de bandeira que V1 ou V2: UNW_FLAG_EHANDLER (0x01), UNW_FLAG_UHANDLER (0x02), UNW_FLAG_CHAININFO (0x04). Novidade na V3: UNW_FLAG_LARGE (0x08). Bit 4 reservado (zero).
SizeOfProlog Deslocamento de bytes para o início da primeira instrução que não faz parte do prólogo. Quando UNW_FLAG_LARGE está definido, este campo de 8 bits é o byte mais baixo de um tamanho de prólog de 16 bits. Para mais informações sobre o formato de 16 bits, consulte UNW_FLAG_LARGE e UNWIND_INFO_LARGE_V3.
PayloadWords Número de palavras de carga útil de 16 bits após este cabeçalho. A carga útil contém deslocamentos IP prolog, todos os descritores epilog (incluindo os seus deslocamentos IP) e o pool WOD. Não inclui o gestor de exceções RVA ou chained RUNTIME_FUNCTION que possa seguir. O algoritmo para localizar os dados do handler/chain é o mesmo do V1 ou V2: header + 4 + PayloadWords * 2, alinhado com DWORD.
NumberOfOps Número de WODs no prólogo (0–31). Zero significa sem prólogo. Se uma função precisar de mais de 31 operações de prólogo, use um subfragmento.
NumberOfEpilogs Número de descritores EPILOG_INFO_V3 que seguem os deslocamentos de IP do prólogo (0–7). Zero significa que não há epílogos neste fragmento. Se forem necessários mais de 7, use um subfragmento.

UNW_FLAG_LARGE e UNWIND_INFO_LARGE_V3

Quando UNW_FLAG_LARGE está definido no cabeçalho Flags, o primeiro byte da carga útil é uma extensão de 1 byte UNWIND_INFO_LARGE_V3 :

byte Bits Campo Largura
0 [7:0] SizeOfPrologHighByte 8 bits

SizeOfPrologHighByte é combinado com UNWIND_INFO_V3.SizeOfProlog para formar um tamanho de prólog de 16 bits: SizeOfProlog16 = (SizeOfPrologHighByte << 8) | SizeOfProlog.

Além disso, quando UNW_FLAG_LARGE está definido:

  • As entradas de deslocamento IP do Prolog são de 16 bits não assinados (2 bytes cada), em vez de terem 8 bits.

O UNWIND_INFO_LARGE_V3 byte faz parte da carga útil e está incluído em PayloadWords. A fórmula do manipulador-offset mantém-se inalterada.

Esta flag é necessária apenas para prólogos que excedam 255 bytes.

Localizar Processador de Exceções / Informações da Cadeia

Idêntico ao V1 ou V2:

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

Se UNW_FLAG_EHANDLER ou UNW_FLAG_UHANDLER estiver definido, um RVA de 4 bytes está em handler_offset, seguido de dados específicos de cada linguagem.

Se UNW_FLAG_CHAININFO estiver definido, um RUNTIME_FUNCTION de 12 bytes encontra-se em handler_offset.

Disposição da Carga Útil

Imediatamente após o cabeçalho de 4 bytes, a carga útil está organizada pela seguinte ordem (todos empacotados, semântica #pragma pack(1)):

  1. UNWIND_INFO_LARGE_V3 - 1 byte, presente apenas quando UNW_FLAG_LARGE for definido.
  2. Deslocamentos de IP Prolog - NumberOfOps bytes (ou palavras de 16 bits se UNW_FLAG_LARGE).
  3. Para cada epílogo (repetido NumberOfEpilogs vezes):
    • EPILOG_INFO_V3 descritor.
    • EPILOG_INFO_EX_V3 ou EPILOG_INFO_LARGE_EX_V3 descritor estendido (presente apenas quando NumberOfOps > 0).
    • Matriz de deslocamento IP - NumberOfOps bytes (ou palavras de 16 bits se EPILOG_INFO_LARGE).
  4. WOD Pool - bytes restantes.

Tamanho total = PayloadWords × 2 bytes (pode incluir enchimento para completar palavras).

Deslocamentos de IP do Prolog

Uma matriz de NumberOfOps entradas. Cada entrada é um byte sem sinal que indica o deslocamento de IP a partir do início do fragmento da instrução que executa a operação de desenrolamento correspondente. Quando UNW_FLAG_LARGE estiver definido, cada entrada passa a ser uma palavra não assinalada de 16 bits.

Ordenação: A primeira entrada corresponde à operação mais próxima do corpo da função (a última instrução do prólogo com efeito de recuperação). A última entrada corresponde à operação mais próxima do ponto de entrada da função (a primeira instrução prolog). Isto está em conformidade com a convenção V1 ou V2 de enumeração dos códigos de desenrolamento pela ordem inversa de execução. Os WODs prolog começam sempre no deslocamento de bytes zero do pool de WOD, e esta mesma ordem aplica-se.

Descritores do Epilog

Seguem-se zero ou mais estruturas EPILOG_INFO_V3 aos deslocamentos de IP do prolog. Cada um pode ter um EPILOG_INFO_EX_V3 opcional (ou EPILOG_INFO_LARGE_EX_V3 quando o sinalizador EPILOG_INFO_LARGE está definido), além da sua própria matriz de deslocamentos IP de tamanho variável, com tamanho NumberOfOps:

Quando NumberOfOps > 0 (descrição completa)

Forma padrão (EPILOG_INFO_LARGE não definida):

EPILOG_INFO_V3:

byte Bits Campo Largura
0 [2:0] Flags 3 bits
0 [7:3] NumberOfOps 5 bits
1–2 [15:0] EpilogOffset 16 bits com sinal

EPILOG_INFO_EX_V3:

byte Bits Campo Largura
0–1 [15:0] FirstOp 16 bits sem sinal
2 [7:0] IpOffsetOfLastInstruction 8 bits não assinado
3 .. - Matriz de deslocamento IP NumberOfOps Bytes, sem assinatura

Formato grande (conjunto EPILOG_INFO_LARGE):

EPILOG_INFO_V3:

byte Bits Campo Largura
0 [2:0] Flags 3 bits
0 [7:3] NumberOfOps 5 bits
1–2 [15:0] EpilogOffset 16 bits com sinal

EPILOG_INFO_LARGE_EX_V3:

byte Bits Campo Largura
0–1 [15:0] FirstOp 16 bits sem sinal
2–3 [15:0] IpOffsetOfLastInstruction 16 bits sem sinal
4 .. - Matriz de deslocamento IP NumberOfOps × 2 bytes, 16 bits sem sinal

Quando NumberOfOps == 0 (descritor herdado)

EPILOG_INFO_V3:

byte Bits Campo Largura
0 [2:0] Flags 3 bits
0 [7:3] NumberOfOps (= 0) 5 bits
1–2 [15:0] EpilogOffset 16 bits com sinal

Um registo EPILOG_INFO_V3 com NumberOfOps == 0 herda os valores efetivos dos campos NumberOfOps, FirstOp e IpOffsetOfLastInstruction, bem como a matriz de deslocamentos de IP, do primeiro EPILOG_INFO_V3 anterior com NumberOfOps != 0. O compilador deve garantir que Flags os bits 0 e 1 num registo de epílogo com NumberOfOps == 0 têm o mesmo valor que os do primeiro EPILOG_INFO_V3 precedente com NumberOfOps != 0.

Semântica dos Campos do Epilog

Campo Description
Flags Bit 0: EPILOG_INFO_PARENT_FRAGMENT_TRANSFER - definido caso este epílogo transfira o controlo de volta para o fragmento pai (por exemplo, através de JMP) em vez de regressar ao chamador. Bit 1: EPILOG_INFO_LARGE - quando definido, o descritor estendido usa EPILOG_INFO_LARGE_EX_V3 (16 bits IpOffsetOfLastInstruction) e cada entrada de deslocamento IP é de 16 bits, acomodando epílogos superiores a 255 bytes. Bit 2: reservado (0).
NumberOfOps Número de WODs neste epílogo (0–31). Zero é um valor especial que significa "herdar do descritor epilógico anterior."
EpilogOffset deslocamento de 16 bits com sinal até à primeira instrução deste epílogo. Para o primeiro descritor do epílogo: valores positivos são deslocamentos de bytes do início do fragmento, e valores negativos são deslocamentos de bytes da cauda do fragmento, ou seja, o primeiro byte após o fim. Para descritores do epílogo subsequente: delta do início do epílogo anterior. Todos os epílogos devem usar o mesmo sinal – ou todos ordenados ascendendo desde o início, ou todos ordenados descendo a partir da cauda.
FirstOp Índice de bytes para o pool WOD onde reside o primeiro WOD deste epílogo. Isto é um deslocamento de bytes, não um índice WOD. Os epílogos podem partilhar WODs com o prólogo ou entre si apontando para a mesma região do pool.
IpOffsetOfLastInstruction Deslocamento de byte não assinado desde o início do epílogo até à última instrução do epílogo, normalmente RET ou JMP. 8 bits em EPILOG_INFO_EX_V3; 16 bits em EPILOG_INFO_LARGE_EX_V3. O desenrolador usa este valor para determinar onde termina o epílogo e onde o corpo da função recomeça.

Deslocamentos IP da Epilog

Imediatamente após cada descritor completo de epílogo (quando NumberOfOps > 0), uma matriz de entradas NumberOfOps fornece o deslocamento do IP de cada instrução de epílogo que tenha um WOD correspondente. Cada entrada é um byte sem sinal, ou uma palavra sem sinal de 16 bits quando EPILOG_INFO_LARGE está definida. Ordenação: primeira entrada = operação mais próxima do corpo (primeira instrução do epílogo com efeito de desempilhamento), última entrada = operação mais próxima da instrução de transferência de controlo.

Piscina WOD

Os bytes restantes na carga útil formam o pool WOD. Os WODs do prólogo começam implicitamente no deslocamento do byte 0 deste pool. A lista de WOD de cada epilog começa no deslocamento em bytes indicado pelo campo FirstOp. Os WODs estão agrupados, sem alinhamento nem espaçamento interno entre si.

O número de WODs consumidos para o prólogo é UNWIND_INFO_V3.NumberOfOps. O número consumido por cada epílogo é o seu respetivo EPILOG_INFO_V3.NumberOfOps (ou herdado).

Referência de codificação do WOD

Os WODs são de comprimento variável (1–5 bytes), preenchidos com #pragma pack(1). O opcode é codificado nos bits de ordem baixa do primeiro byte. A decodificação requer inspeção de bits multinível.

Tabela de Despacho de Opcode

Para decodificar um WOD, leia o primeiro byte e teste os seus bits mais baixos na seguinte ordem:

Test Valor do Opcode Tipo de WOD Size
byte[0] == 0x00 0 WOD_SET_FPREG 2 bytes
byte[0] == 0x01 1 WOD_ALLOC_HUGE 5 bytes
byte[0] == 0x02 2 WOD_ALLOC_LARGE 3 bytes
byte[0] == 0x03 3 WOD_PUSH_CANONICAL_FRAME 2 bytes
(byte[0] & 0x07) == 0x04 4 WOD_PUSH 1 byte
(byte[0] & 0x07) == 0x05 5 WOD_SAVE_NONVOL_FAR 5 bytes
(byte[0] & 0x07) == 0x06 6 WOD_SAVE_NONVOL 3 bytes
(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 bytes
(byte[0] & 0x0F) == 0x0A 10 WOD_SAVE_XMM128 3 bytes
(byte[0] & 0x3F) == 0x20 32 WOD_PUSH2 2 bytes

** Algoritmo de decodificação WOD (pseudocódigo):**

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

Esquemas WOD (ao nível dos bits)

Todas as posições dos bits são numeradas LSB-first dentro de cada byte. Os campos de inteiros com vários bytes estão em formato little-endian.

WOD_PUSH - 1 byte

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

Efeito:PUSH <reg> ajusta o RSP em 8 e armazena o registo.

WOD_PUSH2 - 2 bytes

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)

Efeito:PUSH2 <reg1>, <reg2> empurra dois registos com uma única instrução (APX). Ajusta o RSP em 16.

WOD_PUSH_CONSECUTIVE_2 - 1 byte

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

Efeito: Empurra Register e Register+1 consecutivamente. Aumenta RSP em 16 no total. Register O valor deve ser limitado a [0, 30], pois o valor de 31 colocaria Register+1 fora dos limites.

WOD_ALLOC_SMALL - 1 byte

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

Alocação efetiva:(Size + 1) * 8 bytes. Alcance: 8–128 bytes em passos de 8.

WOD_ALLOC_LARGE - 3 bytes

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

Alocação efetiva:Size * 8 bytes. Alcance: até 524.280 bytes.

WOD_ALLOC_HUGE - 5 bytes

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

Alocação efetiva: Contagem bruta de bytes (sem escalonamento). Alcance: até 4 GiB.

WOD_SET_FPREG - 2 bytes

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

Efeito: Estabelece um apontador de frame. <reg> = RSP + Offset * 16.

WOD_SAVE_NONVOL - 3 bytes

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

Efeito:MOV [RSP + Displacement * 8], <reg> guarda um registo de números inteiros não volátil na pilha.

WOD_SAVE_NONVOL_FAR - 5 bytes

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

Efeito: O mesmo que WOD_SAVE_NONVOL mas com um deslocamento de byte sem escala de 32 bits. Usado quando o offset não cabe em 16 bits escalados por 8.

WOD_SAVE_XMM128 - 3 bytes

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

Efeito:MOVAPS [RSP + Displacement * 16], <xmm> poupa um registo XMM de 128 bits.

WOD_SAVE_XMM128_FAR - 5 bytes

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

Efeito: O mesmo que WOD_SAVE_XMM128 mas com um deslocamento de byte sem escala de 32 bits.

WOD_PUSH_CANONICAL_FRAME - 2 bytes

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

Efeito: Indica que o hardware/sistema operativo colocou um quadro canónico na pilha. Os valores de tipo distinguem:

  • Estrutura da máquina sem código de erro
  • Estrutura da máquina com código de erro
  • Estrutura da máquina com inserção na pilha sombra
  • Registo de contexto

(Os valores exatos dos tipos são definidos pelo sistema operativo; consulte os cabeçalhos do SDK do Windows.)

Resumo da Constante do Opcode

#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ção de registos

Os registos inteiros utilizam a numeração padrão AMD64 (5 bits, 0–31):

Value Registar
0 RAX
1 RCX
2 RDX
3 RBX
4 DERP
5 RBP
6 LER
7 IDI
8 - 15 R8 - R15
16 - 31 R16 - R31 (APX)

Os campos de registo XMM têm 4 bits (0–15), correspondendo diretamente a XMM0–XMM15.