Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
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_EHANDLERA função tem um manipulador de exceção que deve ser chamado ao procurar funções que precisam examinar exceções. UNW_FLAG_UHANDLERA função tem um tratador de terminação que deve ser chamado ao processar uma exceção. UNW_FLAG_CHAININFOEssa 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_NONVOLdevem 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', excetoUWOP_PUSH_MACHFRAME.UWOP_ALLOC_LARGE(1) 2 ou 3 nósAlocar 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_SMALL136 a 512 K - 8 bytes UWOP_ALLOC_LARGE, informações da operação = 0512K a 4 G - 8 bytes UWOP_ALLOC_LARGE, informações da operação = 1UWOP_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ósSalvar 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ósSalvar 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ósSalvar 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ósSalvar 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_MACHFRAMEregistra essa simulação, que indica que o computador fez essa operação conceitualmente:Extraia o endereço de retorno RIP do topo da pilha para o Temp
Realizar push de SS
Push do RSP antigo
Push EFLAGS
Executou push de CS
Push Temp
Código de Erro de Push (se op info for igual a 1)
A operação simulada
UWOP_PUSH_MACHFRAMEdecrementa 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:
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).
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.
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.
Se o manipulador específico da linguagem retornar um status de "manipulado", a execução será continuada usando o registro de contexto original.
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))