Registradores

64-bit (Qword)32-bit (Dword)16-bit (Word)8 high bits of lower 16 bits8 low bits (Byte)
RAXEAXAXAHAL
RBXEBXBXBHBL
RCXECXCXCHCL
RDXEDXDXDHDL
RSIESISIN/ASIL
RDIEDIDIN/ADIL
RBPEBPBPN/ABPL
RSPESPSPN/ASPL
R8 - R15R8D - R15DR8W - R15WN/AR8B - R15B


Control Flow

A lógica condicional é mecânica e dividida em duas etapas independentes e sequenciais:

  1. Avaliação: Uma instrução matemática ou lógica é executada e o processador atualiza o registrador de status com base nela.
  2. Ação: Uma instrução condicional lê o estado desse registrador para decidir se altera o fluxo do programa ou se move um dado.

O Registrador de Status (rFLAGS)

É um registrador de 64 bits onde bits específicos atuam como flags, descrevendo o resultado da última operação de avaliação. As quatro flags principais são:

  • ZF (Zero Flag): 1 se o resultado da operação foi exatamente zero (ou se dois operandos comparados eram iguais).
  • SF (Sign Flag): 1 se o resultado foi negativo.
  • CF (Carry Flag): 1 se o bit 65 era um.
  • OF (Overflow Flag): 1 em caso de overflow (inverteu o sinal incorretamente) em operações com sinal.

Instruções de Avaliação

  • cmp (Compare): Realiza um sub (Destino - Origem).
    • Exemplo: cmp rax, rbx.
    • Se forem iguais, a subtração dá zero e a ZF vira 1. Se rax for menor, a subtração gera um valor negativo, setando SF ou CF.
  • test (Logical Compare): Realiza um and (Destino & Origem).
    • Exemplo: test rax, rax.
    • É a maneira mais rápida e comum de verificar se um registrador contém o valor 0 (0 & 0)

Instruções como add e sub também alteram as flags.

Conditional Jumps

Após setar as flags, o programa usa um salto condicional para decidir se deve ou não executar esse jump.

InstruçãoSignificadoCategoriaCondição nas Flags
je / jzSalta se Igual / Zero ( == )IgualdadeZF=1
jne / jnzSalta se Não Igual / Não Zero ( != )IgualdadeZF=0
jg / jnleSalta se Maior ( > )SignedZF=0 e SF=OF
jl / jngeSalta se Menor ( < )SignedSF!=OF
jge / jnlSalta se Maior ou Igual ( >= )SignedSF=OF
jle / jngSalta se Menor ou Igual ( <= )SignedZF=1 ou SF!=OF
ja / jnbeSalta se Acima ( > )UnsignedCF=0 e ZF=0
jb / jnaeSalta se Abaixo ( < )UnsignedCF=1
jae / jnbSalta se Acima ou Igual ( >= )UnsignedCF=0
jbe / jnaSalta se Abaixo ou Igual ( <= )UnsignedCF=1 ou ZF=1

(Instruções na mesma linha são sinônimos e tem exatamente o mesmo opcode.)

Alternativas Branchless

Essas instruções movem dados condicionalmente sem saltos (não influenciam Branch Prediction):

  • cmovcc (Conditional Move): Move os dados da origem para o destino apenas se a condição for verdadeira.
    • Exemplo: cmove rax, rbx (Move o valor de rbx para rax se a flag Zero estiver ativa).
  • setcc (Set Byte on Condition): Escreve o resultado de uma comparação em um valor booleano (1 ou 0) no registrador de destino (altera todo o byte inferior). Usados para avaliar expressões lógicas diretas (ex: x = a == b). (cc no nome é uma das condições)
    • Exemplo: sete al (Grava 1 em al se a flag Zero indicar igualdade, ou 0 caso contrário).

Funções

Caller & Callee

A gestão do estado do processador, stack, passagem de argumentos, etc, é regida por uma calling convention que divide a responsabilidade de preservação dos registradores, alinhamento do stack e outras coisas.

  • Caller (Chamador): A função atual que executa o call.
  • Callee (Chamada): A função que recebe o controle.

Registradores Preservados (Callee-Saved / Não-Voláteis): São registradores cujo valor original deve permanecer inalterado da perspectiva do Caller. Se o Callee precisar usar esses registradores durante sua execução, ele é responsável por salvar o valor original no stack (no prólogo) e restaurá-lo (no epílogo) antes do ret .

Registradores Voláteis (Caller-Saved): Podem ser sobrescritos pelo Callee. Se o Caller precisar manter essa informação, ele mesmo deve salvá-la no stack antes do call .

Linux (System V AMD64 ABI)

Ordem de passagem dos argumentos: Os primeiros seis argumentos inteiros ou ponteiros1 são passados obrigatoriamente na seguinte ordem (argumentos extras ou pertencentes a classe de memória2 são empilhados no stack):

  1. rdi
  2. rsi
  3. rdx
  4. rcx
  5. r8
  6. r9

Stack Frame e Red Zone

O stack cresce para baixo ( [rsp - 8] ) e deve estar sempre alinhado para otimizar o barramento de dados da CPU e permitir o uso de instruções vetoriais (SSE/AVX).

  1. Antes do call (No Caller): Imediatamente antes de chamar uma função, o registrador rsp deve obrigatoriamente apontar para um endereço que seja múltiplo de 16 (32 ou 64).
  2. A Execução do call: O call altera a pilha, subtraindo 8 do rsp e empilhando o endereço de retorno de 64 bits (rip).
  3. Na Entrada (No Callee): Agora com o Callee no controle é preciso restaurar o alinhamento, assim começa o prólogo da função e a preparação de um stack frame pra ela.

Para que seja possivel usar o stack com segurança ou chamar outras funções o alinhamento de 16 bytes deve ser restaurado. Isso pode ser feito empilhando o Base Pointer, o que subtrai mais 8 bytes do rsp (8 + 8 = 16). Ou alocando espaço de forma explícita (ex: sub rsp, N ).

A Red Zone é uma otimização de arquitetura exclusiva da System V ABI, é uma área de 128 bytes localizada em endereços de memória imediatamente inferiores ao ponteiro atual da pilha (de rsp - 1 até rsp - 128 ).

A memória localizada nessa área é reservada para uso por “leaf functions” (funções que não chamam outras funções), como espaço adicional para o frame delas, dessa maneira elas não precisam modificar o Stack Pointer ( rsp ).

Uso dos Registradores de Propósito Geral

RegistradorUsos PrincipaisCallee-Saved
rax1º registrador de retorno; contém o número da syscall antes da interrupção.Não (Volátil)
rbxUso geral. Atua como armazenamento seguro de longo prazo.Sim (Não-volátil)
rcx4º argumento inteiro. É destruído pelo kernel em syscall s (usado para salvar o rip temporariamente).Não (Volátil)
rdx3º argumento inteiro; 2º registrador de retorno (para retornos de 128 bits).Não (Volátil)
rdi1º argumento inteiro. Em C++ é o registrador que carrega o ponteiro this .Não (Volátil)
rsi2º argumento inteiro.Não (Volátil)
rbpBase Pointer (stack frames). Compiladores omitem o frame pointer e o utilizam como um registrador de uso geral.Sim (Não-volátil)
rspStack Pointer. Aponta para o topo da pilha.Sim (Não-volátil)
r85º argumento inteiro.Não (Volátil)
r96º argumento inteiro.Não (Volátil)
r10Temporário. O kernel usa para passar o 4º argumento.Não (Volátil)
r11Temporário. É destruído pelo kernel em syscall s (usado para salvar o rflags ).Não (Volátil)
r12 - r15Uso geral para salvar variáveis locais.Sim (Não-volátil)

Windows (Microsoft x64 ABI)

A calling convention baseia-se no modelo __fastcall e em um modelo de tratamento de exceções baseado em metadados (unwind data).

Ordem de passagem dos argumentos: Há uma correspondência estrita de um para um entre os argumentos da função e os registradores, um único argumento nunca é dividido em múltiplos registradores. Os quatro primeiros argumentos são passados em registradores, o quinto e os seguintes são empilhados da direita para a esquerda (cima para baixo). Argumentos maiores que 8 bytes ou com tamanhos diferentes de 1, 2, 4 ou 8 bytes são passados por referência.

  • Inteiros / Ponteiros:
    1. rcx
    2. rdx
    3. r8
    4. r9
  • Ponto Flutuante (XMM):
    1. xmm0
    2. xmm1
    3. xmm2
    4. xmm3

Stack Frame e Shadow Store

O stack exige alinhamento de 16 bytes para o registrador rsp na maior parte do código (exceto em leaf functions ou durante a execução de prólogos/epílogos).

O Caller é responsável por alocar o espaço na base da pilha para os parâmetros do Callee. O Caller deve sempre alocar espaço contíguo suficiente para abrigar os quatro parâmetros do registrador (32 bytes), mesmo que o Callee não receba quatro parâmetros. Esses 4 slots pertencem ao Callee, que pode utilizá-los para salvar os argumentos passados via registrador na memória, caso precise do endereço deles ou para depuração.

Ao contrário do linux, toda memória além do endereço atual do rsp (abaixo do topo da pilha) é considerada volátil. O sistema operacional, depuradores ou tratadores de interrupção podem sobrescrever essa área a qualquer momento. Portanto, o rsp deve ser obrigatoriamente subtraído para alocar espaço físico antes de qualquer tentativa de leitura ou escrita de variáveis locais.

Tipos de Funções e Unwindability

  • Frame Functions: Funções que alocam espaço na pilha, chamam outras funções ou salvam registradores não-voláteis. Requerem um prólogo e epílogo com estruturas e instruções altamente restritas. Isso é obrigatório para que os dados de exceção (pdata e xdata) consigam realizar o “unwind” (desenrolamento) da pilha em caso de erro, restaurando o contexto do chamador.
  • Leaf Functions (Funções Folha): Funções que não alteram registradores não-voláteis (incluindo o rsp). Não podem chamar funções nem alocar espaço na pilha. Não exigem prólogo, epílogo ou metadados de exceção, e têm a permissão de deixar a pilha desalinhada durante a sua execução.

Retorno de Valores

  • Valores escalares e tipos definidos pelo usuário que caibam em 64 bits (1, 2, 4, 8, 16, 32 ou 64 bits), incluindo structs são retornados em rax.
  • Tipos não-escalares (floats, doubles e vetores como __m128) são retornados em xmm0.
  • Para tipos definidos pelo usuário que excedem 64 bits ou não cumprem os requisitos de retorno por valor, o Caller deve alocar a memória e passar um ponteiro oculto para ela como o primeiro argumento (em rcx). Os argumentos reais são deslocados uma posição para a direita. O Callee retorna esse mesmo ponteiro em rax.

Uso dos Registradores de Propósito Geral

RegistradorUsos PrincipaisCallee-Saved
raxValor de retorno.Não (Volátil)
rbxUso geral.Sim (Não-volátil)
rcx1º argumento inteiro. Em __thiscall (métodos não estáticos C++) é o registrador usado para passar o ponteiro this .Não (Volátil)
rdx2º argumento inteiro.Não (Volátil)
rdiUso geral. Diferente do Linux deve ser preservado. O Callee deve salvá-lo se for utilizá-lo para instruções de string ( rep stosq , etc).Sim (Não-volátil)
rsiUso geral. Mesma regra do rdiSim (Não-volátil)
rbpFrame pointer. Também de uso geral em funções normais, mas é obrigatoriamente forçado a atuar como Base Pointer se a função utilizar alloca() .Sim (Não-volátil)
rspStack Pointer.Sim (Não-volátil)
r83º argumento inteiro.Não (Volátil)
r94º argumento inteiro.Não (Volátil)
r10 - r11Usados pela ntdll.dll nos stubs de syscall / sysret . O compilador utiliza como descartáveis.Não (Volátil)
r12 - r15Uso geral.Sim (Não-volátil)
xmm0 - xmm3Primeiros 4 argumentos de floating point/vector. xmm0 é o valor de retorno de float/double.Não (Volátil)
xmm4 - xmm5Usados como 5º e 6º argumentos se a função usar __vectorcall .Não (Volátil)
xmm6 - xmm15Operações floating point. Diferente do Linux devem ser preservados.Sim (Não-volátil)

Footnotes

  1. “This class consists of integral types that fit into one of the general purpose registers.”

  2. “If the size of an object is larger than eight eightbytes (64 bytes), or it contains unaligned fields, it has class MEMORY”