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.