Partilhar via


x64 prólogo e epílogo

Cada função que aloca espaço de pilha, chama outras funções, salva registros não voláteis ou usa tratamento de exceções deve ter um prolog cujos limites de endereço são descritos nos dados de desenrolar associados à respetiva entrada da tabela de funções. Para obter mais informações, consulte Tratamento de exceções x64. O prólogo salva registos de argumentos nos seus endereços de origem, se necessário, envia registos não voláteis para a pilha, aloca a parte fixa da pilha para variáveis locais e temporárias e, opcionalmente, estabelece um ponteiro de base. Os dados de desenrolar associados devem descrever a ação do prólogo e devem fornecer as informações necessárias para desfazer o efeito do código de 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 cubra mais de uma página de memória virtual e, portanto, deve ser verificada antes de ser efetuada. Uma rotina especial que pode ser chamada a partir do prolog e que não destrói nenhum dos registos de argumentos é fornecida para este fim.

O método preferido para salvar os registradores não voláteis é movê-los para a pilha antes da alocação fixa da pilha. Se a alocação de pilha fixa for executada antes que os registradores não voláteis sejam salvos, então muito provavelmente um deslocamento de 32 bits será necessário para acessar a área dos registradores salvos. (Alegadamente, as operações de transferência de registos são tão rápidas como os movimentos e devem assim permanecer no futuro previsível, apesar da dependência implícita entre as operações de transferência.) Os registos persistentes podem ser guardados em qualquer ordem. No entanto, o primeiro uso de um registro não volátil no prolog deve ser para salvá-lo.

Código Prolog

O código para um prólogo típico pode ser:

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

Este prolog armazena o argumento registo RCX no seu local de origem, salva registos 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 o interior da área de alocação fixa. O uso de um deslocamento permite que mais da área de alocação fixa seja endereçada com deslocamentos de um byte.

Se o tamanho de alocação fixa for maior ou igual a uma página de memória, uma função auxiliar deve ser chamada antes de modificar o RSP. Este auxiliar, __chkstk, verifica a faixa da pilha alocada em to-bepara garantir que a pilha seja estendida corretamente. Nesse caso, o exemplo anterior de Prolog 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 __chkstk auxiliar não modificará nenhum registro além de R10, R11 e os códigos de condição. Em particular, ele retornará o RAX inalterado e deixará todos os registros não voláteis e registros de passagem de argumentos inalterados.

Código Epilog

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

O código epilog deve seguir um conjunto estrito de regras para que o código de desenrolamento seja desenrolado de forma confiável por meio de exceções e interrupções. Essas regras reduzem a quantidade de dados de desenrolamento necessários, porque não são necessários dados adicionais para descrever cada epílogo. Em vez disso, o código de desenrolar pode determinar que um epílog está sendo executado varrendo para frente através de um fluxo de código para identificar um epílogo.

Se não for usado um ponteiro de frame na função, o epílogo deve primeiro desalocar a parte fixa da pilha, repor os registos não voláteis e devolver o controlo à função de chamada. Por exemplo

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

Se um ponteiro de frame for usado na função, então a pilha deve ser ajustada para a sua alocação fixa antes da execução do epílogo. Esta 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á uma boa razão para ajustar o RSP em duas etapas, então o seguinte epílogo seria usado:

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

Estas formas são as únicas legais para uma epíloga. Deve consistir em um add RSP,constant ou lea RSP,constant[FPReg], seguido por uma série de zero ou mais pops de registro de 8 bytes e um return ou um jmp. (Apenas um subconjunto de jmp afirmações é permitido no epílogo. O subconjunto é exclusivamente a classe de instruções com referências de memória ModRM onde o valor do jmp campo mod ModRM é 00. É proibido o uso de instruções no epílogo com o valor de jmp campo ModRM mod 01 ou 10. Consulte a Tabela A-15 no Manual do programador de arquitetura AMD x86-64 Volume 3: Instruções gerais e do sistema, para obter mais informações sobre as referências permitidas do ModRM.) Nenhum outro código pode aparecer. Em particular, nada pode ser programado dentro de um epílogo, incluindo o carregamento de um valor de retorno.

Quando um ponteiro de quadro não é usado, o epílogo deve utilizar add RSP,constant para desalocar a parte fixa da pilha. Pode não usar lea RSP,constant[RSP] em vez disso. Essa restrição existe para que o código de desenrolamento reconheça menos padrões ao procurar epílogos.

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

Ver também

Convenções de Software x64