Registradores e Modelo de Memória sBPF
Quando seu programa sBPF executa, ele opera com dois mecanismos de armazenamento primários: 11 registradores de alta velocidade integrados ao processador e regiões de memória organizadas que armazenam todo o resto.
Registradores
Registradores são como 11 slots de armazenamento numerados (r0 até r10) integrados diretamente ao processador. Cada um armazena um único valor de 64 bits e fornece acesso instantâneo sem atrasos. Quando você executa add64 r1, r2, o processador recupera imediatamente os valores de ambos os registradores e realiza o cálculo.
O tradeoff é simples: você tem apenas 11 slots no total, então precisa usá-los estrategicamente para os valores com os quais está trabalhando ativamente.
O sBPF atribui funções específicas a cada registrador:
Os registradores r6 até r9 são callee-saved (salvos pelo chamado), significando que seus valores persistem entre chamadas de função. Use-os para dados importantes que devem sobreviver a invocações de funções.
Os registradores restantes (r0-r5) são sobrescritos durante syscalls e chamadas de função.
O registrador r10 é "especial" porque serve como seu ponteiro de frame, apontando para a base do seu espaço de pilha e permanecendo somente leitura. Você acessa variáveis da pilha usando offsets negativos como [r10 - 8] e [r10 - 16].
Memória
Enquanto os registradores armazenam valores ativamente usados, a memória armazena todo o resto. O sBPF organiza a memória em regiões fixas com layouts idênticos em todos os programas:
A região de texto contém seu código executável e dados somente leitura como constantes de string. Dados definidos com diretivas
.quadtipicamente residem aqui.A região de pilha abriga variáveis locais. Com
r10apontando para a base da pilha, você acessa variáveis locais usando offsets negativos:[r10 - 16],[r10 - 24], etc.A região de entrada contém os parâmetros do seu programa. Na entrada,
r1aponta para esta região (0x400000000), permitindo que você leia dados de contas serializadas e parâmetros de instrução passados para seu programa.
Este layout fixo fornece três benefícios principais: segurança através do isolamento de memória entre programas, determinismo com endereços idênticos em todos os validadores e desempenho através de otimizações do compilador para locais conhecidos.
Usando Registradores e Memória
Veja como registradores e memória trabalham juntos na prática:
.globl entrypoint
entrypoint:
// Na entrada: r1 aponta para dados de entrada em 0x400000000
ldxdw r0, [r1 + 0] // Carregar primeiros 8 bytes da entrada em r0
mov64 r2, 42 // Colocar 42 no registrador r2
add64 r0, r2 // Somar: r0 = r0 + r2
stxdw [r10 - 8], r0 // Armazenar resultado na pilha
mov64 r0, 0 // Retornar sucesso
exitEste programa demonstra o padrão típico:
Usa o registrador
r1(ponteiro de entrada) para ler da memóriaUsa os registradores
r0er2para cálculosUsa o registrador
r10(ponteiro de frame) para acessar a memória da pilhaRetorna o resultado no registrador
r0
O fluxo de trabalho segue um padrão consistente: carregar dados da memória para os registradores, realizar cálculos com os registradores e então armazenar os resultados de volta na memória quando necessário.
Uso da Pilha
A pilha opera com r10 como ponteiro de frame, apontando para a base do frame de pilha atual (o endereço mais alto). Variáveis locais usam offsets negativos:
// Armazenar um valor na pilha
mov64 r0, 42
stxdw [r10 - 8], r0 // Armazenar no primeiro slot da pilha
// Carregar de volta
ldxdw r1, [r10 - 8] // Carregar do primeiro slot da pilhaOs slots da pilha tipicamente têm 8 bytes de largura, então variáveis consecutivas usam offsets como [r10 - 8], [r10 - 16], [r10 - 24], e assim por diante.
Entrada e Saída do Programa
Seu programa começa a execução no símbolo marcado com .globl (tipicamente entrypoint). O estado inicial dos registradores é mínimo e previsível:
.globl entrypoint
entrypoint:
// Na entrada:
// r1 = 0x400000000 (ponteiro do buffer de entrada)
// r10 = 0x200001000 (0x200000000 início da pilha + 0x1000 tamanho do frame de 4KiB)
// r0, r2-r9 = 0 (todos os outros registradores zerados)
// Sua lógica de programa aqui
mov64 r0, 0 // Código de sucesso (0 = SUCCESS)
exit // Retornar ao runtimeO comportamento de saída depende da profundidade de chamada:
Na profundidade de chamada 0, exit termina o programa com
r0como código de resultado.Em níveis de chamada mais profundos, exit atua como uma instrução de retorno, restaurando os registradores salvos pelo chamador (
r6-r9) e o ponteiro de frame do chamador (r10) antes de continuar a execução no endereço de retorno.
O runtime gerencia automaticamente a configuração e desmontagem através de código de prólogo e epílogo implícito, incluindo a configuração do estado inicial dos registradores e o processamento final de retorno.