Compartilhar via


Tratamento de exceções x64

Uma visão geral do tratamento de exceções estruturado e das convenções e comportamentos de código no tratamento de exceções do C++ no x64. Para obter informações gerais sobre o tratamento de exceções, consulte Tratamento de exceções no Microsoft C++.

Desmontar dados para tratamento de exceção, suporte de ferramentas de depuração

Várias estruturas de dados são necessárias para tratamento de exceções e suporte à depuração.

struct RUNTIME_FUNCTION

O tratamento de exceções baseado em tabela requer uma entrada na tabela para cada função que aloca espaço de pilha ou chama outra função, como as funções não-folha, por exemplo. As entradas da tabela de funções têm o formato:

Tamanho Valor
ULONG Endereço de início da função
ULONG Endereço final da função
ULONG Endereço das informações de desenrolamento

A estrutura RUNTIME_FUNCTION deve estar alinhada ao DWORD na memória. Todos os endereços são relativos à imagem, ou seja, são deslocamentos de 32 bits do endereço inicial da imagem que contém a entrada da tabela de funções. Essas entradas são classificadas e colocadas na seção .pdata de uma imagem PE32+. Para funções geradas dinamicamente [compiladores JIT], para dar suporte a essas funções o runtime deve usar RtlInstallFunctionTableCallback ou RtlAddFunctionTable para fornecer essas informações ao sistema operacional. O não cumprimento dessa ação resultará em tratamento de exceções e depuração de processos não confiáveis.

struct UNWIND_INFO

A estrutura de dados de desenrolamento é usada para registrar os efeitos de uma função sobre o ponteiro da pilha e o local em que os registros não voláteis são salvos na pilha.

Tamanho Valor
UBYTE: 3 Versão
UBYTE: 5 Sinalizadores
UBYTE Tamanho do prólogo
UBYTE Contagem de códigos de desenrolamento
UBYTE: 4 Registro de Frames
UBYTE: 4 Deslocamento do Registrador de Frame (escalonado)
USHORT * n Matriz de códigos de desempacotamento
variável Pode ser na forma (1) ou (2) abaixo

(1) Manipulador de Exceção

Tamanho Valor
ULONG Endereço do manipulador de exceção
variável Dados específicos do manipulador de linguagem (opcional)

(2) Informações de desenrolamento encadeadas

Tamanho Valor
ULONG Endereço de início da função
ULONG Endereço final da função
ULONG Endereço das informações de desenrolamento

A estrutura UNWIND_INFO deve estar alinhada a DWORD na memória. Veja o que cada campo significa:

  • Versão

    Número de versão dos dados de desenrolamento; atualmente 1.

  • Sinalizadores

    Três sinalizadores estão definidos atualmente:

    Sinalizador Descrição
    UNW_FLAG_EHANDLER A função tem um manipulador de exceção que deve ser chamado ao procurar funções que precisam examinar exceções.
    UNW_FLAG_UHANDLER A função tem um tratador de terminação que deve ser chamado ao processar uma exceção.
    UNW_FLAG_CHAININFO Essa estrutura de informações de desenrolamento não é a principal do procedimento. Em vez disso, a entrada encadeada de informações de desenrolamento corresponde ao conteúdo de uma entrada anterior de RUNTIME_FUNCTION. Para obter informação, consulte Estruturas de informação de desenrolamento encadeadas. Se esse sinalizador estiver definido, os sinalizadores UNW_FLAG_EHANDLER e UNW_FLAG_UHANDLER deverão ser limpos. Além disso, o registrador de moldura e os campos de alocação de pilha fixa devem ter os mesmos valores que nas informações primárias de desenrolamento.
  • Tamanho do prólogo

    Comprimento do prólogo da função em bytes.

  • Contagem de códigos de desmontagem

    O número de slots na matriz de códigos de desenrolamento. Alguns códigos de unwind, como UWOP_SAVE_NONVOL, exigem mais de um slot na matriz.

  • Registro de quadros

    Se for diferente de zero, a função usará um ponteiro de quadro (FP), e esse campo será o número do registrador não volátil usado como ponteiro de quadro, utilizando a mesma codificação para o campo de informações de operação dos nós do UNWIND_CODE.

  • Deslocamento do registro de quadros (escalonado)

    Se o campo de registro de quadros for diferente de zero, esse campo será o deslocamento escalonado do RSP que é aplicado ao registro do FP quando ele é estabelecido. O registro do FP real é definido como RSP + 16 * esse número, permitindo deslocamentos de 0 a 240. Esse deslocamento permite apontar o registro FP para o meio da alocação de pilha local em quadros de pilha dinâmicos, permitindo uma melhor densidade de código por meio de instruções mais curtas. (Ou seja, mais instruções podem usar o formato de deslocamento de 8 bits com sinal).

  • Matriz de códigos de desenrolamento

    Uma matriz de itens que explica o efeito do prólogo sobre os registros não voláteis e o RSP. Confira a seção sobre UNWIND_CODE para obter os significados de cada item. Para fins de alinhamento, essa matriz sempre tem um número par de entradas e a entrada final é potencialmente não utilizada. Nesse caso, a matriz fica mais longa do que o indicado pela contagem de campos de códigos de desenrolamento.

  • Endereço do manipulador de exceção

    Um ponteiro relativo à imagem para a exceção ou o manipulador de terminação específico da linguagem da função, caso o sinalizador UNW_FLAG_CHAININFO esteja limpo e um dos sinalizadores UNW_FLAG_EHANDLER ou UNW_FLAG_UHANDLER esteja definido.

  • Dados do manipulador específico da linguagem

    Os dados do manipulador de exceção específicos da linguagem da função. O formato desses dados não é especificado e é completamente determinado pelo manipulador de exceção específico em uso.

  • Informações de Desempacotamento Encadeado

    Se o sinalizador UNW_FLAG_CHAININFO estiver definido, a estrutura UNWIND_INFO terminará com três UWORDs. Esses UWORDs representam as informações da RUNTIME_FUNCTION relacionadas à função do desenrolamento encadeado.

struct UNWIND_CODE

A matriz de código de desenrolamento é utilizada para registrar a sequência de operações no prólogo que impactam os registros não voláteis e o RSP. Cada item de código tem esse formato:

Tamanho Valor
UBYTE Deslocamento no prólogo
UBYTE: 4 Código de operação de Unwind
UBYTE: 4 Informações da operação

A matriz é classificada por ordem decrescente de deslocamento no prólogo.

Deslocamento no prólogo

Deslocamento (do início do prólogo) do final da instrução que executa essa operação, mais 1 (ou seja, o deslocamento do início da próxima instrução).

Código de operação de Unwind

Observação: determinados códigos de operação exigem um deslocamento sem sinal para um valor no quadro de pilha local. Esse deslocamento é do início, ou seja, o endereço mais baixo da alocação de pilha fixa. Se o campo Registrador de Quadro no UNWIND_INFO for zero, o deslocamento será em relação ao RSP. Se o campo do Registro de Quadro for diferente de zero, esse deslocamento se dá a partir de onde o RSP estava localizado quando o registro do FP foi definido. Ele será igual ao registrador FP menos o deslocamento do registrador FP (16 * o deslocamento escalonado do registrador de frame no UNWIND_INFO). Se um registro FP for usado, qualquer código de desenrolamento que envolva um deslocamento só deverá ser usado depois que o registro FP for estabelecido no prólogo.

Para todos os opcodes, exceto UWOP_SAVE_XMM128 e UWOP_SAVE_XMM128_FAR, o deslocamento é sempre um múltiplo de 8, porque todos os valores de interesse da pilha são armazenados em limites de 8 bytes (a pilha em si é sempre alinhada a 16 bytes). Para códigos de operação que recebem um deslocamento curto (menos de 512 K), o USHORT final nos nós desse código mantém o deslocamento dividido por 8. Em códigos de operação que exigem um deslocamento longo (512K <= deslocamento < 4GB), os dois últimos nós USHORT desse código armazenam o deslocamento (em formato little-endian).

Para os opcodes UWOP_SAVE_XMM128 e UWOP_SAVE_XMM128_FAR, o deslocamento é sempre um múltiplo de 16, já que todas as operações XMM de 128 bits devem ocorrer na memória alinhada de 16 bytes. Portanto, um fator de escala de 16 é usado para UWOP_SAVE_XMM128, permitindo deslocamentos inferiores a 1 M.

O código de operação de desenrolamento é um destes valores:

  • UWOP_PUSH_NONVOL (0) 1 nó

    Efetuar push de um registro inteiro não volátil, decrementando o RSP em 8. A informação da operação é o número do registro. Devido às restrições em epílogos, os códigos de desenrolamento UWOP_PUSH_NONVOL devem aparecer primeiro no prólogo e, correspondentemente, por último na matriz de código de desenrolamento. Essa ordenação relativa se aplica a todos os outros códigos de 'unwind', exceto UWOP_PUSH_MACHFRAME.

  • UWOP_ALLOC_LARGE (1) 2 ou 3 nós

    Alocar uma área grande na pilha. Existem duas formas. Se as informações de operação forem iguais a 0, o tamanho da alocação dividida por 8 será registrado no slot seguinte, permitindo uma alocação de até 512 K - 8. Se as informações da operação forem iguais a 1, o tamanho não escalonado da alocação será registrado nos dois slots seguintes no formato little-endian, permitindo alocações de até 4 GB - 8.

  • UWOP_ALLOC_SMALL (2) 1 nó

    Alocar uma pequena área na pilha. O tamanho da alocação é o campo de informações da operação * 8 + 8, permitindo alocações de 8 a 128 bytes.

    O código de desenrolamento de uma alocação de pilha deve sempre usar a codificação mais curta possível:

    Tamanho da Alocação Código de Desenlace
    8 a 128 bytes UWOP_ALLOC_SMALL
    136 a 512 K - 8 bytes UWOP_ALLOC_LARGE, informações da operação = 0
    512K a 4 G - 8 bytes UWOP_ALLOC_LARGE, informações da operação = 1
  • UWOP_SET_FPREG (3) 1 nó

    Estabelecer o registro do ponteiro de quadro definindo o registro com algum deslocamento do RSP atual. O deslocamento é igual ao campo deslocamento do Registro de Quadros (escalonado) no UNWIND_INFO * 16, permitindo deslocamentos de 0 a 240. O uso de um deslocamento permite estabelecer um ponteiro de quadro que aponta para o meio da alocação de pilha fixa, ajudando a densidade de código, permitindo que mais acessos usem formatos de instrução curtos. O campo de informações de operação é reservado e não deve ser usado.

  • UWOP_SAVE_NONVOL (4) 2 nós

    Salvar um registro inteiro não volátil na pilha usando um MOV em vez de um PUSH. Esse código é usado primariamente para encapsulamento reduzido, em que um registro não volátil é salvo na pilha em uma posição que foi alocada anteriormente. A informação da operação é o número do registro. O deslocamento de pilha escalonado por 8 é registrado no próximo slot do código de operação de desenrolamento, conforme descrito na nota acima.

  • UWOP_SAVE_NONVOL_FAR (5) 3 nós

    Salvar um registro inteiro não volátil na pilha com um deslocamento longo usando um MOV em vez de um PUSH. Esse código é usado principalmente para shrink-wrapping, onde um registro não volátil é salvo na pilha em uma posição previamente alocada. A informação da operação é o número do registro. O deslocamento de pilha não escalonado é registrado nos próximos dois slots do código de operação de desenrolamento, conforme descrito na nota acima.

  • UWOP_SAVE_XMM128 (8) 2 nós

    Salvar todos os 128 bits de um registro XMM não volátil na pilha. A informação da operação é o número do registro. O deslocamento de pilha multiplicado por 16 é registrado no próximo slot.

  • UWOP_SAVE_XMM128_FAR (9) 3 nós

    Salvar todos os 128 bits de um registro XMM não volátil na pilha com um deslocamento longo. A informação da operação é o número do registro. O deslocamento de pilha não escalonado é registrado nos próximos dois slots.

  • UWOP_PUSH_MACHFRAME (10) 1 nó

    Efetuar push de um quadro do computador. Esse código de desenrolamento é usado para registrar o efeito de uma interrupção ou exceção de hardware. Existem duas formas. Se a informação da operação for igual a 0, um desses frames foi empurrado para a pilha:

    Localidade Valor
    RSP + 32 SS
    RSP + 24 RSP antigo
    RSP + 16 EFLAGS
    RSP + 8 CS
    RSP RIP

    Se a informação de operação for igual a 1, então um desses quadros foi empurrado:

    Localidade Valor
    RSP + 40 SS
    RSP + 32 RSP antigo
    RSP + 24 EFLAGS
    RSP + 16 CS
    RSP + 8 RIP
    RSP Código do erro

    Esse código de desenrolamento sempre aparece em um prólogo fictício, que nunca é realmente executado, mas aparece antes do ponto de entrada real de uma rotina de interrupção e existe apenas para fornecer um local para a simulação do push de um quadro de computador. UWOP_PUSH_MACHFRAME registra essa simulação, que indica que o computador fez essa operação conceitualmente:

    1. Extraia o endereço de retorno RIP do topo da pilha para o Temp

    2. Realizar push de SS

    3. Push do RSP antigo

    4. Push EFLAGS

    5. Executou push de CS

    6. Push Temp

    7. Código de Erro de Push (se op info for igual a 1)

    A operação simulada UWOP_PUSH_MACHFRAME decrementa o RSP em 40 (informações de operação iguais a 0) ou 48 (informações de operação iguais a 1).

Informações da operação

O significado dos bits de informações da operação depende do código da operação. Para codificar um registro de uso geral (inteiro), este mapeamento é usado:

bit Registrar-se
0 RAX
1 RCX
2 RDX
3 RBX
4 RSP
5 RBP
6 RSI
7 RDI
8 a 15 R8 a R15

Estruturas de informação de desenrolamento encadeadas

Se o sinalizador UNW_FLAG_CHAININFO estiver definido, a estrutura de informações de desenrolamento será secundária e o campo de endereço de informações encadeadas/manipulador de exceções compartilhado conterá as informações de desenrolamento primárias. Este código de exemplo recupera as informações de desenrolamento primárias, supondo que unwindInfo seja a estrutura que tem o sinalizador UNW_FLAG_CHAININFO definido.

PRUNTIME_FUNCTION primaryUwindInfo = (PRUNTIME_FUNCTION)&(unwindInfo->UnwindCode[( unwindInfo->CountOfCodes + 1 ) & ~1]);

Informações encadeadas são úteis em duas situações. Primeiro, elas podem ser usadas para segmentos de código não contíguos. Ao usar informações encadeadas, você poderá reduzir o tamanho das informações de desenrolamento necessárias, pois não precisará duplicar a matriz de códigos de desenrolamento das informações de desenrolamento primárias.

Você também poderá usar informações encadeadas para agrupar salvamentos de registro volátil. O compilador poderá atrasar o salvamento de alguns registros voláteis até que ele esteja fora do prólogo de entrada da função. Você poderá registrá-las com informações de desenrolamento primárias para a parte da função anterior ao código agrupado e, em seguida, configurar informações encadeadas com um tamanho de prólogo diferente de zero, em que os códigos de desenrolamento das informações encadeadas refletirão salvamentos dos registros não voláteis. Nesse caso, os códigos de unwind são todos instâncias do UWOP_SAVE_NONVOL. Não há suporte para um agrupamento que salve registros não voláteis usando um PUSH ou que modifique o registro RSP usando uma alocação de pilha fixa adicional.

Um item UNWIND_INFO que tem UNW_FLAG_CHAININFO definido pode conter uma entrada de RUNTIME_FUNCTION cujo item UNWIND_INFO também tem UNW_FLAG_CHAININFO definido, às vezes chamado de multiple shrink-wrapping. Os ponteiros de informações de desenrolamento encadeados eventualmente chegam a um item UNWIND_INFO que tem o UNW_FLAG_CHAININFO desmarcado. Este é o item UNWIND_INFO primário, que aponta para o ponto de entrada real do procedimento.

Procedimento de desenrolar

A matriz de código de desenrolamento é classificada em ordem decrescente. Quando ocorre uma exceção, o contexto completo é armazenado pelo sistema operacional em um registro de contexto. Em seguida, a lógica de expedição de exceção é invocada, que executa repetidamente estas etapas para localizar um manipulador de exceção:

  1. Usar o RIP atual armazenado no registro de contexto para localizar uma entrada na tabela RUNTIME_FUNCTION que descreva a função atual (ou parte da função, no caso de entradas de UNWIND_INFO encadeadas).

  2. Se nenhuma entrada de tabela de funções for encontrada, a função estará em uma folha de função e o RSP endereçará diretamente o ponteiro de retorno. O ponteiro de retorno no [RSP] é armazenado no contexto atualizado, o RSP simulado é incrementado em 8 e a etapa 1 é repetida.

  3. Se uma entrada de tabela de funções for encontrada, o RIP poderá estar em três regiões: a) em um epílogo, b) no prólogo ou c) em código que pode estar coberto por um manipulador de exceção.

    • Caso a) Se o RIP estiver em um epílogo, o controle estará saindo da função, e não há possibilidade de existir um manipulador de exceção associado a essa exceção para essa função; os efeitos do epílogo devem continuar para calcular o contexto da função chamadora. Para determinar se o RIP está dentro de um epílogo, o fluxo de código do RIP em diante é examinado. Se houver correspondência desse fluxo de código com a parte posterior de um epílogo legítimo, então está em um epílogo, e a parte restante do epílogo é simulada, com o registro de contexto atualizado à medida que cada instrução é processada. Após esse processamento, a etapa 1 será repetida.

    • Caso b) Caso o RIP esteja no prólogo, então o controle não entrou na função e não poderá haver nenhum manipulador de exceção associado a essa exceção para essa função; os efeitos do prólogo deverão ser desfeitos para calcular o contexto da função chamadora. O RIP estará dentro do prólogo se a distância do início da função até o RIP for menor ou igual ao tamanho do prólogo codificado nas informações de desenrolamento. Os efeitos do prólogo são revertidos realizando uma varredura antecipada na matriz de códigos de desenrolamento para encontrar a primeira entrada com um deslocamento menor ou igual ao deslocamento do RIP a partir do início da função, e então desfazendo o efeito de todos os itens restantes na matriz de códigos de desenrolamento. Em seguida, a etapa 1 é repetida.

    • Caso c) Se o RIP não estiver em um prólogo nem em um epílogo e a função tiver um manipulador de exceção (UNW_FLAG_EHANDLER definido), o manipulador específico da linguagem será chamado. O manipulador examina seus dados e chama funções de filtro conforme apropriado. O manipulador específico da linguagem pode retornar que a exceção foi tratada ou que a pesquisa deve continuar. Ele também pode iniciar um desenrolamento diretamente.

  4. Se o manipulador específico da linguagem retornar um status de "manipulado", a execução será continuada usando o registro de contexto original.

  5. Se não houver um manipulador específico da linguagem ou o manipulador retornar um status de "continuar pesquisa", o registro de contexto deverá ser desenrolado de acordo com o estado do chamador. Isso é feito através do processamento de todos os elementos da matriz de código de desenrolamento, desfazendo o efeito de cada um. Em seguida, a etapa 1 é repetida.

Quando informações de desenrolamento encadeadas estão envolvidas, essas etapas básicas ainda são seguidas. A única diferença é que, ao percorrer a matriz de código de desenrolamento para desenrolar os efeitos de um prólogo, quando o final da matriz for alcançado, ela será vinculada às informações de desenrolamento pai e toda a matriz de código de desenrolamento encontrada será percorrida. Essa vinculação continua até chegar a uma informação de desenrolamento sem o sinalizador UNW_CHAINED_INFO e, em seguida, ela termina de percorrer a matriz de código de desenrolamento.

O menor conjunto de dados de desenrolamento é de 8 bytes. Isso representaria uma função que alocou apenas 128 bytes de pilha ou menos e possivelmente salvou um registro não volátil. Esse também é o tamanho de uma estrutura de informações de desenrolamento encadeada para um prólogo de comprimento zero sem códigos de desenrolamento.

Manipulador específico da linguagem

O endereço relativo do manipulador específico da linguagem está presente no UNWIND_INFO sempre que os sinalizadores UNW_FLAG_EHANDLER ou UNW_FLAG_UHANDLER estiverem definidos. Conforme descrito na seção anterior, o manipulador específico da linguagem é chamado como parte da busca por um manipulador de exceção ou como parte de um desempacotamento. Ele tem este protótipo:

typedef EXCEPTION_DISPOSITION (*PEXCEPTION_ROUTINE) (
    IN PEXCEPTION_RECORD ExceptionRecord,
    IN ULONG64 EstablisherFrame,
    IN OUT PCONTEXT ContextRecord,
    IN OUT PDISPATCHER_CONTEXT DispatcherContext
);

ExceptionRecord fornece um ponteiro para um registro de exceção, que tem a definição padrão do Win64.

EstablisherFrame é o endereço base da alocação de pilha fixa para essa função.

ContextRecord aponta para o contexto da exceção no momento em que a exceção foi gerada (no caso do manipulador de exceção) ou o contexto atual de "desenrolamento" (no caso do manipulador de encerramento).

DispatcherContext aponta para o contexto do dispatcher dessa função. Ele tem essa definição:

typedef struct _DISPATCHER_CONTEXT {
    ULONG64 ControlPc;
    ULONG64 ImageBase;
    PRUNTIME_FUNCTION FunctionEntry;
    ULONG64 EstablisherFrame;
    ULONG64 TargetIp;
    PCONTEXT ContextRecord;
    PEXCEPTION_ROUTINE LanguageHandler;
    PVOID HandlerData;
} DISPATCHER_CONTEXT, *PDISPATCHER_CONTEXT;

ControlPc é o valor de RIP nessa função. Esse valor é um endereço de exceção ou o endereço onde o controle deixou a função de configuração. O RIP é usado para determinar se o controle está dentro de algum constructo protegido dentro dessa função, por exemplo, um bloco __try para __try/__except ou __try/__finally.

ImageBase é a base da imagem (endereço de carga) do módulo que contém essa função, a ser adicionada aos offsets de 32 bits usados na entrada da função e nas informações de desenrolamento para registrar endereços relativos.

FunctionEntry fornece um ponteiro para a entrada da função RUNTIME_FUNCTION que contém a função e desenrola os endereços relativos da imagem base das informações dessa função.

EstablisherFrame é o endereço base da alocação de pilha fixa para essa função.

TargetIp fornece um endereço de instrução opcional que especifica o endereço de continuação do desenrolamento. Esse endereço será ignorado se EstablisherFrame não for especificado.

ContextRecord aponta para o contexto de exceção, para ser usado pelo código de expedição/desenrolamento da exceção do sistema.

LanguageHandler aponta para a rotina de manipulação de linguagem específica que está sendo chamada.

HandlerData aponta para os dados do manipulador específico da linguagem para essa função.

Auxiliares de desempacotamento para MASM

Para escrever rotinas de assembly adequadas, há um conjunto de pseudo-operações que podem ser usadas em paralelo com as instruções de assembly reais para criar o .pdata e o .xdata apropriados. Além disso, há um conjunto de macros que fornecem o uso simplificado das pseudo-operações para os usos mais comuns.

Pseudo-operações brutas

Pseudo-operação Descrição
QUADRO PROC [:ehandler] Faz com que o MASM gere uma entrada de tabela de funções em .pdata e informações de desenrolamento em .xdata para o comportamento de desenrolamento da manipulação de exceções estruturadas de uma função. Se o ehandler estiver presente, esse proc será inserido no .xdata como o manipulador específico da linguagem.

Quando o atributo FRAME é usado, ele deve ser seguido por uma diretiva .ENDPROLOG. Se a função for uma função folha (conforme definido em Tipos de função), o atributo FRAME será desnecessário, assim como o restante dessas pseudo-operações.
.PUSHREG registro Gera uma entrada de código UWOP_PUSH_NONVOL de desempacotamento para o número de registrador especificado, usando o deslocamento atual na fase inicial.

Use-a apenas com registros inteiros não voláteis. Para realizar pushes de registros voláteis, use .ALLOCSTACK 8, em vez disso
.SETFRAME register, deslocamento Preenche o campo do registro de quadro e o deslocamento nas informações de desenrolamento usando o registro e o deslocamento especificados. O deslocamento deve ser um múltiplo de 16 e menor ou igual a 240. Essa diretiva também gera uma entrada de código de desenrolamento UWOP_SET_FPREG para o registro especificado usando o deslocamento do prólogo atual.
.ALLOCSTACK Tamanho Gera um UWOP_ALLOC_SMALL ou um UWOP_ALLOC_LARGE com o tamanho especificado do deslocamento atual no prólogo.

O operando size deve ser um múltiplo de 8.
.SAVEREG registrador, deslocamento Gera uma entrada de código de desenrolamento UWOP_SAVE_NONVOL ou UWOP_SAVE_NONVOL_FAR para o registro e o deslocamento especificados usando o deslocamento do prólogo atual. O MASM escolhe a codificação mais eficiente.

O offset deve ser positivo e um múltiplo de 8. O offset é relativo à base do quadro do procedimento, que geralmente está no RSP ou, se estiver usando um ponteiro de quadro, no ponteiro de quadro não escalonado.
. SAVEXMM128 registro, deslocamento Gera uma entrada de código de desenrolamento UWOP_SAVE_XMM128 ou UWOP_SAVE_XMM128_FAR para o registro XMM e o deslocamento especificados usando o deslocamento do prólogo atual. O MASM escolhe a codificação mais eficiente.

O offset deve ser positivo e um múltiplo de 16. O offset é relativo à base da moldura do procedimento, que geralmente está no RSP ou, se estiver usando um ponteiro de moldura, no ponteiro de moldura não escalonado.
.PUSHFRAME [código] Gera uma entrada de código de desenrolamento UWOP_PUSH_MACHFRAME. Se o code opcional for especificado, a entrada de código de desenrolamento receberá um modificador igual a 1. Caso contrário, o modificador será 0.
.ENDPROLOG Sinaliza o término das declarações do prólogo. Deve ocorrer nos primeiros 255 bytes da função.

Veja um prólogo de função de exemplo com o uso adequado da maioria dos opcodes:

sample PROC FRAME
    db      048h; emit a REX prefix, to enable hot-patching
    push rbp
    .pushreg rbp
    sub rsp, 040h
    .allocstack 040h
    lea rbp, [rsp+020h]
    .setframe rbp, 020h
    movdqa [rbp], xmm7
    .savexmm128 xmm7, 020h ;the offset is from the base of the frame
                           ;not the scaled offset of the frame
    mov [rbp+018h], rsi
    .savereg rsi, 038h
    mov [rsp+010h], rdi
    .savereg rdi, 010h ; you can still use RSP as the base of the frame
                       ; or any other register you choose
    .endprolog

; you can modify the stack pointer outside of the prologue (similar to alloca)
; because we have a frame pointer.
; if we didn't have a frame pointer, this would be illegal
; if we didn't make this modification,
; there would be no need for a frame pointer

    sub rsp, 060h

; we can unwind from the next AV because of the frame pointer

    mov rax, 0
    mov rax, [rax] ; AV!

; restore the registers that weren't saved with a push
; this isn't part of the official epilog, as described in section 2.5

    movdqa xmm7, [rbp]
    mov rsi, [rbp+018h]
    mov rdi, [rbp-010h]

; Here's the official epilog

    lea rsp, [rbp+020h] ; deallocate both fixed and dynamic portions of the frame
    pop rbp
    ret
sample ENDP

Para obter mais informações sobre o exemplo de epílogo, veja o Código de epílogo em Prólogo e epílogo do x64.

Macros MASM

Para simplificar o uso das Pseudo-operações brutas, há um conjunto de macros, definido no ksamd64.inc, que pode ser usado para criar prólogos e epílogos de procedimento típicos.

Macro Descrição
alloc_stack(n) Aloca um quadro de pilha de n bytes (usando sub rsp, n) e emite as informações de desenrolamento apropriadas (.allocstack n)
save_reg reg, loc Salva um registrador não volátil reg na pilha no deslocamento loc do RSP e emite as informações de desenrolamento apropriadas. (.savereg reg, loc)
reg push_reg reg Efetua push de um reg de registro não volátil na pilha e emite as informações de desenrolamento apropriadas. (.pushreg reg)
rex_push_reg reg Salva um registro não volátil na pilha usando um push de 2 bytes e emite as informações de desenrolamento apropriadas (.pushreg reg). Use essa macro se o push for a primeira instrução na função, para garantir que a função seja passível de aplicação de patch instantâneo.
save_xmm128 reg, loc Salva um registrador XMM não volátil na pilha no deslocamento de RSP loc e emite as informações de desenrolamento apropriadas (.savexmm128 reg, loc)
set_frame reg, deslocamento Define o registro de quadro reg como RSP + offset (usando um mov ou um lea) e emite as informações de descompactação apropriadas (.set_frame reg, offset)
push_eflags Efetua push das eflags com uma instrução pushfq e emite as informações de desenrolamento apropriadas (.alloc_stack 8)

Veja um prólogo de função de exemplo com o uso adequado das macros:

sampleFrame struct
    Fill     dq ?; fill to 8 mod 16
    SavedRdi dq ?; Saved Register RDI
    SavedRsi dq ?; Saved Register RSI
sampleFrame ends

sample2 PROC FRAME
    alloc_stack(sizeof sampleFrame)
    save_reg rdi, sampleFrame.SavedRdi
    save_reg rsi, sampleFrame.SavedRsi
    .end_prolog

; function body

    mov rsi, sampleFrame.SavedRsi[rsp]
    mov rdi, sampleFrame.SavedRdi[rsp]

; Here's the official epilog

    add rsp, (sizeof sampleFrame)
    ret
sample2 ENDP

Expandir definições de dados em C

Aqui está uma descrição em C dos dados de desenrolamento:

typedef enum _UNWIND_OP_CODES {
    UWOP_PUSH_NONVOL = 0, /* info == register number */
    UWOP_ALLOC_LARGE,     /* no info, alloc size in next 2 slots */
    UWOP_ALLOC_SMALL,     /* info == size of allocation / 8 - 1 */
    UWOP_SET_FPREG,       /* no info, FP = RSP + UNWIND_INFO.FPRegOffset*16 */
    UWOP_SAVE_NONVOL,     /* info == register number, offset in next slot */
    UWOP_SAVE_NONVOL_FAR, /* info == register number, offset in next 2 slots */
    UWOP_SAVE_XMM128 = 8, /* info == XMM reg number, offset in next slot */
    UWOP_SAVE_XMM128_FAR, /* info == XMM reg number, offset in next 2 slots */
    UWOP_PUSH_MACHFRAME   /* info == 0: no error-code, 1: error-code */
} UNWIND_CODE_OPS;

typedef unsigned char UBYTE;

typedef union _UNWIND_CODE {
    struct {
        UBYTE CodeOffset;
        UBYTE UnwindOp : 4;
        UBYTE OpInfo   : 4;
    };
    USHORT FrameOffset;
} UNWIND_CODE, *PUNWIND_CODE;

#define UNW_FLAG_EHANDLER  0x01
#define UNW_FLAG_UHANDLER  0x02
#define UNW_FLAG_CHAININFO 0x04

typedef struct _UNWIND_INFO {
    UBYTE Version       : 3;
    UBYTE Flags         : 5;
    UBYTE SizeOfProlog;
    UBYTE CountOfCodes;
    UBYTE FrameRegister : 4;
    UBYTE FrameOffset   : 4;
    UNWIND_CODE UnwindCode[1];
/*  UNWIND_CODE MoreUnwindCode[((CountOfCodes + 1) & ~1) - 1];
*   union {
*       OPTIONAL ULONG ExceptionHandler;
*       OPTIONAL ULONG FunctionEntry;
*   };
*   OPTIONAL ULONG ExceptionData[]; */
} UNWIND_INFO, *PUNWIND_INFO;

typedef struct _RUNTIME_FUNCTION {
    ULONG BeginAddress;
    ULONG EndAddress;
    ULONG UnwindData;
} RUNTIME_FUNCTION, *PRUNTIME_FUNCTION;

#define GetUnwindCodeEntry(info, index) \
    ((info)->UnwindCode[index])

#define GetLanguageSpecificDataPtr(info) \
    ((PVOID)&GetUnwindCodeEntry((info),((info)->CountOfCodes + 1) & ~1))

#define GetExceptionHandler(base, info) \
    ((PEXCEPTION_HANDLER)((base) + *(PULONG)GetLanguageSpecificDataPtr(info)))

#define GetChainedFunctionEntry(base, info) \
    ((PRUNTIME_FUNCTION)((base) + *(PULONG)GetLanguageSpecificDataPtr(info)))

#define GetExceptionDataPtr(info) \
    ((PVOID)((PULONG)GetLanguageSpecificData(info) + 1))

Confira também

Convenções de software x64