Convenções de chamada no Windows
Aprendendo sobre as convenções de chamada usadas no Windows (x64, cdecl e stdcall).
O Windows tem suas próprias convenções de chamadas e o objetivo desse tópico é aprender sobre as três principais que dizem respeito à linguagem C.
Convenção de chamada x64
Essa é a convenção de chamada padrão usada em x86-64 e portanto é essencial aprendê-la caso vá programar no Windows diretamente em Assembly.
Registradores
Os registradores RBX, RBP, RDI, RSI, RSP, R12 até R15 e XMM6 até XMM15 devem ser preservados pela função chamada (callee). Caso a função chamada precise alterar o valor de algum desses registradores ela tem a obrigação de preservar o valor anterior e restaurá-lo antes de retornar.
Os demais registradores são considerados voláteis, isto é, podem ter seu valor alterado quando uma chamada de função é efetuada. A função chamada pode modificar o valor dos registradores voláteis livremente.
Passagem de parâmetros
Os primeiros quatro argumentos inteiros ou ponteiros são passados nos seguintes registradores e na mesma ordem: RCX, RDX, R8 e R9. Os demais argumentos devem ser empilhados na ordem inversa.
Os primeiros quatro argumentos float ou double são passados nos registradores XMM0 até XMM3 como valores escalares. Os demais também são empilhados na ordem inversa.
Structs e unions de 8, 16, 32 ou 64 bits são passados como se fossem inteiros do respectivo tamanho. Se forem de outro tamanho a função chamadora deve então passar um ponteiro para a struct/union que será armazenada em uma memória alocada pela própria função chamadora. Essa memória deve estar em um endereço alinhado por 16 bytes.
A função chamadora (caller) é responsável por alocar um espaço de 32 bytes na pilha chamado de shadow space. Ele é alocado com o intuito de ser usado pela função chamada (callee) para armazenar os parâmetros passados em registradores caso seja necessário, por exemplo caso a função chamada precise usar esses registradores com outro intuito. Esse espaço vem antes mesmo do primeiro parâmetro empilhado.
Exemplo de protótipo de função:
Assim que a função fosse chamada ECX, EDX, R8D e R9D armazenariam os parâmetros a
, b
, c
e d
respectivamente. O parâmetro f
seria empilhado seguido do parâmetro e
.
O 0(%rsp)
seria o endereço de retorno. O espaço entre 8(%rsp)
e 40(%rsp)
é o shadow space. 40(%rsp)
apontaria para o parâmetro e
, enquanto 48(%rsp)
apontaria para o parâmetro f
. Como na demonstração abaixo:
Retorno de valores
Valores inteiros e ponteiros são retornados em RAX.
Valores float ou double são retornados no registrador XMM0.
O retorno de structs é feito com a função chamadora alocando o espaço de memória necessário para a struct, ela então passa o ponteiro para esse espaço como primeiro argumento para a função em RCX. A função chamada (callee) deve retornar o mesmo ponteiro em RAX.
Convenção de chamada cdecl
A convenção de chamada __cdecl
é a convenção padrão usada em código escrito em C na arquitetura IA-32 (x86).
Registradores
Apenas os registradores EAX, ECX e EDX são considerados voláteis, ou seja, registradores que podem ser modificados livremente pela função chamada. Todos os demais registradores precisam ser preservados e restaurados antes do retorno da função.
Passagem de parâmetros
Todos os parâmetros são passados na pilha e devem ser empilhados na ordem inversa. A função chamadora (caller) é a responsável por remover os argumentos da pilha após a função retornar.
Exemplo:
Retorno de valores
Valores inteiros ou ponteiros são retornados em EAX.
Valores float ou double são retornados em ST0.
O retorno de structs ocorre da mesma maneira que na convenção de chamada x64. Com a diferença que o primeiro argumento é, obviamente, passado na pilha.
Convenção de chamada stdcall
A convenção de chamada __stdcall
é a utilizada para chamar funções da WinAPI.
Registradores
Assim como na __cdecl
os registradores EAX, ECX e EDX são voláteis e os demais devem ser preservados pela função chamada.
Passagem de parâmetros
Todos os argumentos são passados na pilha na ordem inversa. A função chamada (callee) é a responsável por remover os argumentos da pilha. Exemplo:
Retorno de valores
O retorno de valores funciona da mesma maneira que o retorno de valores da __cdecl
.
Last updated