Partilhar via


Prólogo e epílogo x64

Cada função que aloca espaço de pilha, chama outras funções, salva registros não voláteis ou usa tratamento de exceção deve ter um prólogo cujos limites de endereço são descritos nos dados de desenrolamento associados à respectiva entrada da tabela de funções. Para obter mais informações, consulte Tratamento de exceções x64. A caixa de diálogo salva os registros de argumento em seus endereços residenciais, se necessário, envia por push registros não voláteis na pilha, aloca a parte fixa da pilha para locais e temporários e, opcionalmente, estabelece um ponteiro de quadro. Os dados de desenrolamento associados devem descrever a ação do diálogo e fornecer as informações necessárias para desfazer o efeito do código do prólogo.

Se a alocação fixa na pilha for superior a uma página (ou seja, maior que 4096 bytes), é possível que a alocação de pilha possa abranger mais de uma página de memória virtual e, portanto, a alocação deverá ser verificada antes de ser alocada. Uma rotina especial que pode ser chamada do prólogo e que não destrói nenhum dos registros de argumento é fornecida para essa finalidade.

O método preferencial para salvar registros não voláteis é movê-los para a pilha antes da alocação de pilha fixa. Se a alocação de pilha fixa for executada antes que os registros não voláteis sejam salvos, provavelmente um deslocamento de 32 bits será necessário para tratar a área de registro salva. (Supostamente, pushes de registros são tão rápidos quanto movimentos e devem permanecer assim no futuro previsível, apesar da dependência implícita entre pushes). Registros não voláteis podem ser salvos em qualquer ordem. No entanto, o primeiro uso de um registro não volátil no prólogo deve ser para salvá-lo.

Código de prólogo

O código de uma caixa de diálogo típica pode ser:

    mov    [RSP + 8], RCX
    push   R15
    push   R14
    push   R13
    sub    RSP, fixed-allocation-size
    lea    R13, 128[RSP]
    ...

Esse prólogo armazena o RCX de registro de argumento em sua localização inicial, salva registros não voláteis R13-R15, aloca a parte fixa do quadro de pilha e estabelece um ponteiro de quadro que aponta 128 bytes para a área de alocação fixa. Usar um deslocamento permite que maior área de alocação fixa seja endereçada com deslocamentos de um byte.

Se o tamanho da alocação fixa for maior ou igual a uma página de memória, uma função auxiliar deverá ser chamada antes de modificar o RSP. Este auxiliar, __chkstk, sonda o intervalo de pilha a ser alocado para garantir que a pilha seja estendida corretamente. Nesse caso, o exemplo anterior do prólogo seria:

    mov    [RSP + 8], RCX
    push   R15
    push   R14
    push   R13
    mov    RAX,  fixed-allocation-size
    call   __chkstk
    sub    RSP, RAX
    lea    R13, 128[RSP]
    ...

O auxiliar __chkstk não modificará nenhum registro diferente de R10, R11 e os códigos de condição. Em particular, ele retornará o RAX inalterado e deixará todos os registradores não voláteis e registradores de passagem de argumentos inalterados.

Código epilog

O código de epílogo existe em cada saída para uma função. Enquanto normalmente há apenas um prólogo, pode haver muitos epílogos. O código de epílogo corta a pilha em seu tamanho de alocação fixa (se necessário), desaloca a alocação de pilha fixa, restaura registros não voláteis, colocando seus valores salvos da pilha e retorna.

O código de epílogo deve seguir um conjunto estrito de regras para que o código de desenrolamento ocorra de forma confiável por meio de exceções e interrupções. Essas regras reduzem a quantidade exigida de dados de desenrolamento, pois nenhum dado extra será necessário para descrever cada epílogo. Em vez disso, o código de desenrolamento pode determinar que um epílogo em execução está examinando por meio de um fluxo de código para identificar um epílogo.

Se nenhum ponteiro de quadro for usado na função, o epílogo deverá primeiro desalocar a parte fixa da pilha, os registros não voláteis serão exibidos e o controle será retornado à função de chamada. Por exemplo,

    add      RSP, fixed-allocation-size
    pop      R13
    pop      R14
    pop      R15
    ret

Se um ponteiro de quadro for usado na função, a pilha deverá ser cortada para sua alocação fixa antes da execução do epílogo. Essa ação tecnicamente não faz parte do epílogo. Por exemplo, o seguinte epílogo pode ser usado para desfazer o prólogo usado anteriormente:

    lea      RSP, -128[R13]
    ; epilogue proper starts here
    add      RSP, fixed-allocation-size
    pop      R13
    pop      R14
    pop      R15
    ret

Na prática, quando um ponteiro de quadro é usado, não há boas razões para ajustar a RSP em duas etapas, portanto, o epílogo a seguir deve ser usado como alternativa:

    lea      RSP, fixed-allocation-size - 128[R13]
    pop      R13
    pop      R14
    pop      R15
    ret

Essas são as únicas formas legais para um epílogo. Ele deve consistir em um add RSP,constant ou lea RSP,constant[FPReg], seguido por uma série de remoções mais recentes da pilha de registros de zero ou mais 8 bytes e um return ou um jmp. (Somente um subconjunto de instruções jmp é permitido no epílogo. O subconjunto é exclusivamente a classe de instruções jmp com referências de memória ModRM em que o valor do campo ModRM é 00. É proibido o uso de instruções jmp no epílogo com o valor de campo ModRM 01 ou 10. Consulte a Tabela A-15 no Volume 3 do Manual do Programador de Arquitetura AMD x86-64: Uso Geral e instruções do sistema, para obter mais informações sobre as referências de ModRM permitidos). Não pode aparecer nenhum outro código. Em particular, nada pode ser agendado em um epílogo, incluindo o carregamento de um valor retornado.

Quando um ponteiro de quadro não é usado, o epílogo deve usar add RSP,constant para desalocar a parte fixa da pilha. Em vez disso, ele pode não usar lea RSP,constant[RSP]. Essa restrição existe para que o código de desenrolamento tenha menos padrões a serem reconhecidos ao pesquisar epílogos.

Seguir essas regras permite que o código de desenrolamento determine que um epílogo está sendo executado no momento e simule a execução do restante do epílogo para permitir a recriação do contexto da função de chamada.

Confira também

Convenções de software x64