Partilhar 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, confira Tratamento de Exceções no Visual C++.

Desenrolar dados para tratamento de exceção, suporte do depurador

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ção baseado em tabela requer uma entrada de tabela para todas as funções que alocam espaço de pilha ou chamam outra função (por exemplo, funções não-folha). 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 da 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. A falha ao fazer isso resultará em depuração de processos e tratamento de exceção não confiáveis.

struct UNWIND_INFO

A estrutura de informações de desenrolamento de dados é usada para registrar os efeitos que uma função tem no 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 Quadros
UBYTE: 4 Deslocamento do Registro de Quadros (escalonado)
USHORT * n Matriz de códigos de desenrolamento
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 do manipulador específicos da 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 de UNWIND_INFO deve estar alinhada ao 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 manipulador de terminação que deve ser chamado ao desenrolar uma exceção.
    UNW_FLAG_CHAININFO Essa estrutura de informações de desenrolamento não é a principal do procedimento. Em vez disso, a entrada de informações de desenrolamento encadeada é o conteúdo de uma entrada da RUNTIME_FUNCTION anterior. Para obter informações, confira Estruturas de informações 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 registro de quadros e os campos de alocação de pilha fixa devem ter os mesmos valores que nas informações de desenrolamento primárias.
  • Tamanho do prólogo

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

  • Contagem de códigos de desenrolamento

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

  • Registro de quadros

    Se for diferente de zero, a função usará um FP (ponteiro de quadro) e esse campo será o número do registro não volátil usado como ponteiro de quadro, usando a mesma codificação para o campo de informações de operação de 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 do FP para o meio da alocação de pilha local em registros de ativação 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 desenrolamento encadeadas

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

struct UNWIND_CODE

A matriz de código de desenrolamento é usada para registrar a sequência de operações no prólogo que afetam 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 da operação de desenrolamento
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 da operação de desenrolamento

Observação: determinados códigos de operação exigem um deslocamento sem sinal para um valor no registro de ativação local. Esse deslocamento é do início, ou seja, o endereço mais baixo da alocação de pilha fixa. Se o campo do Registro de Quadros no UNWIND_INFO for zero, esse deslocamento será do RSP. Se o campo do Registro de Quadros for diferente de zero, esse deslocamento será do local em que o RSP estava quando o registro do FP foi estabelecido. Ele será igual ao registro do FP menos o deslocamento do registro do FP (16 * o deslocamento do registro de quadro escalonado no UNWIND_INFO). Se um registro de FP for usado, qualquer código de desenrolamento que faça um deslocamento só deverá ser usado depois que o registro do 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 (512 K <= deslocamento < 4 GB), os dois últimos nós USHORT desse código vão conter 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 será 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 desenrolamento, exceto UWOP_PUSH_MACHFRAME.

  • UWOP_ALLOC_LARGE (1) 2 ou 3 nós

    Alocar uma área de grande tamanho 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 área de tamanho pequeno 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 Desenrolamento
    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 será 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 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 será 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 será o número do registro. O deslocamento de pilha escalonado 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 será 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 as informações de operação forem iguais a 0, um desses quadros foi enviado por push na pilha:

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

    Se as informações de operação forem iguais a 1, um desses quadros foi enviado por push:

    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. Removeu o retorno RIP mais recente da pilha para o Temp

    2. Efetuou push de SS

    3. Efetuou push do RSP antigo

    4. Efetuou push de EFLAGS

    5. Efetuou push de CS

    6. Efetuou push de Temp

    7. Efetuou push do Código de Erro (se as informações de operação forem iguais 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ções desenroladas 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 desenrolamento são todos instâncias de 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 encapsulamento reduzido múltiplo. Os ponteiros de informações de desenrolamento encadeados podem chegar a um item UNWIND_INFO que tenha UNW_FLAG_CHAININFO limpo. 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 pesquisar uma entrada de tabela RUNTIME_FUNCTION que descreva a função atual (ou parte da função, para entradas de UNWIND_INFO encadeadas).

  2. Se nenhuma entrada de tabela de funções for encontrada, ela estará em uma função folha e o RSP abordará 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) no código que pode ter o suporte de 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 poderá haver nenhum manipulador de exceção associado a essa exceção para essa função; os efeitos do epílogo deverão continuar a calcular o contexto da função de chamador. Para determinar se o RIP está dentro de um epílogo, o fluxo de código do RIP em diante será examinado. Se houver correspondência desse fluxo de código com a parte posterior de um epílogo legítimo, ele será um epílogo e a parte restante do epílogo será simulada, com o registro de contexto atualizado à medida que cada instrução é processada. Após esse processamento, a etapa 1 será repetida.

    • Caso b) Se o RIP estiver 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 de chamador. 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 desenrolados pela verificação posterior à matriz de códigos de desenrolamento da primeira entrada com um deslocamento menor ou igual ao deslocamento do RIP desde o início da função, desfazendo assim o efeito de todos os itens restantes na matriz de código 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 poderá 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 pelo 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 pesquisa por um manipulador de exceção ou como parte de um desenrolamento. 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 da 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 no qual o controle deixou a função de estabelecimento. 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 deslocamentos 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 da 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 do manipulador específico da linguagem que está sendo chamado.

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

Desenrolar auxiliares 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
PROC FRAME [:ehandler] Faz com que o MASM gere uma entrada de tabela de funções no .pdata e desenrole informações no .xdata para a exceção estruturada de uma função que manipula o comportamento de desenrolamento. 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 register Gera um desenrolamento de entrada de código UWOP_PUSH_NONVOL para o número de registro especificado usando o deslocamento atual no prólogo.

Use-a apenas com registros inteiros não voláteis. Para pushes de registros voláteis, use um .ALLOCSTACK 8
.SETFRAME register, offset 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 size 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 register, offset 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 register, offset 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 do quadro do procedimento, que geralmente está no RSP ou, se estiver usando um ponteiro de quadro, no ponteiro de quadro não escalonado.
.PUSHFRAME [code] 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 registro de ativação de n bytes (usando sub rsp, n) e emite as informações de desenrolamento apropriadas (.allocstack n)
save_reg reg, loc Salva um reg de registro não volátil na pilha no loc de deslocamento do RSP e emite as informações de desenrolamento apropriadas. (.savereg reg, loc)
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 reg de registro XMM não volátil na pilha no loc de deslocamento do RSP e emite as informações de desenrolamento apropriadas (.savexmm128 reg, loc)
set_frame reg, offset Define o reg de registro de quadro como o RSP + offset (usando um mov ou um lea) e emite as informações de desenrolamento 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

Desenrolar definições de dados em C

Aqui está uma descrição do 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