Pular para o conteúdo principal

Convenção de chamada de funções

A convenção abordada aqui é a convenção de chamada padrão do System V AMD64 ABI, usado pelo Linux, macOS, BSD, etc. para código em modo usuário.

Imagine que temos uma função funcao(arg1, arg2, arg3,...). Como a função obtém seus parâmetros?

Troca de stack frame

Lmbre-se que no início de toda função temos a criação de um novo stack frame para a função com push rbp (salva rbp/stack frame antigo) e mov RBP, RSP (novo stack frame). Antes do push rbp, a instrução call faz o push rip (salva return address) e jmp.

Assim, antes de começar a puxar os parâmetros na função, temos a troca de stack frame.

Convenção de chamada para 32-bit e 64-bit

x86 (32-bit)

  • Argumentos são todos passados na stack (push arg1; push arg2; ...)
  • Chama-se função (call funcao = push rip; jmp funcao)
  • Função cria novo stack frame (push rbp; mov rbp, rsp)
  • funcao vai buscar o valor para o primeiro parâmetro após o return address (depois de criar novo frame). Isso seria aproximadamente esp+4 (para o stack frame antigo, considerando o endereço de retorno na stack)

Antes entrar em funcao, mas após call:

ESP   → endereço de retorno
ESP+4 → primeiro argumento
ESP+8 → segundo argumento, etc.

Quando entramos em funcao:

  • Novo ESP e EBP (novo stack frame) (push ebp; mov ebp, esp)
  • funcao espera encontrar o valor para arg1 em ebp+8, e os outros em seguida
EBP+0   → RBP antigo
EBP+4 → endereço de retorno
ESP+8 → primeiro argumento
ESP+12 → segundo argumento, etc.

x86-64 (64-bit)

  • Os primeiros argumentos são passados em registradores
  • Argumentos excedentes são passados na stack (push arg7; push arg8; ...)
  • Chama-se função (call funcao = push rip; jmp funcao)
  • Função cria novo stack frame (push rbp; mov rbp, rsp)
  • funcao vai primeiro olhar nos registradores que armazenam parâmetros (RDI, RSI, RDX, RCX, R8, R9)
  • Só depois busca na stack

Lembrando a ordem de passagem de argumentos para registradores:

  1. RDI (Endereço da Format String)
  2. RSI (Parâmetro 1)
  3. RDX (Parâmetro 2)
  4. RCX (...)
  5. R8
  6. R9
  7. Stack (RSP+8, RSP+16, ...)

Alinhamento de stack

A stack deve estar alinhada em 16 bytes no momento da chamada call (rsp % 16 == 0). Para funções nativas do código isso não é problema, mas para ataques precisamos ter isso em mente.