
Como escrever programas Solana com SBPF Assembly
Índice
- Arquitetura da máquina virtual sBPF
- Arquitetura do conjunto de instruções sBPF
- Modelo de memória do sBPF
- Syscalls da Solana no sBPF
- Configure seu ambiente
- Configure seu projeto
- Exemplo de memo em sBPF Assembly
- Compile e implante seu programa
- Interaja com seu programa
- Validação de entradas
- Verificação dos limites da memória
- Gerenciamento de registradores
- Segurança aritmética
- Validação de parâmetros de syscalls
Atualmente, há uma corrida paralela em busca da otimização máxima de programas no ecossistema Solana.
Em alto nível, bibliotecas como a Pinocchio estão revolucionando o desenvolvimento em Rust, alcançando melhorias de várias ordens de grandeza na eficiência computacional. Enquanto isso, no nível mais baixo possível, um grupo de desenvolvedores dedicados, unidos pelo desrespeito mútuo ao compilador, está indo ainda mais longe. Em vez de escrever programas Solana em linguagens compiladas como Rust ou C, eles se concentram em criar bytecode manualmente e com precisão para extrair o máximo desempenho de cada instrução.
Esses ganhos de baixo nível só são possíveis quando instruímos diretamente a VM em sua linguagem nativa: sBPF Assembly, a variante própria da Solana do extended Berkeley Packet Filter (eBPF), o bytecode usado e executado em cada programa on-chain.
Escrever em sBPF Assembly dá aos desenvolvedores acesso direto à interface de nível mais baixo da Solana Virtual Machine. Embora o compilador Rust e o LLVM tentem fazer otimizações, a expressividade insuficiente da sintaxe da linguagem ou a falta de contexto necessário para tomar melhores decisões de compilação muitas vezes faz com que eles gerem bytecode inferior ao produzido por um desenvolvedor experiente com o controle completo no nível das instruções que o Assembly oferece.
Embora esse nível adicional de controle prejudique a ergonomia, a economia no uso de unidades computacionais e no tamanho do binário — e, portanto, no aluguel — é significativa. Essa economia se torna especialmente importante em operações altamente disputadas, competitivas e críticas para o desempenho.
Ao mesmo tempo, também é possível argumentar que nem todos os programas deveriam ser escritos em Assembly.
Embora a situação tenha melhorado drasticamente, historicamente as ferramentas eram limitadas. Mais importante: os ganhos de desempenho muitas vezes vêm acompanhados de desvantagens significativas, como a verificação manual da corretude e o aumento dos custos de auditoria. Isso acontece pela falta de ferramentas automatizadas e porque a sintaxe é mais difícil de ler, escrever e entender.
Por outro lado, também se pode argumentar que as linguagens compiladas são uma caixa-preta que oculta as decisões tomadas pelo compilador. Assim, a transparência e o controle adicionais do Assembly podem revelar aspectos que não são facilmente visíveis ao trabalhar com linguagens compiladas. Na verdade, a grande maioria dos avanços recentes de desempenho em nossos SDKs Rust foi descoberta quando percebemos que poderíamos criar manualmente um bytecode mais eficiente que o do compilador.
Neste artigo, você aprenderá:
- O que é sBPF Assembly e como ele oferece controle direto sobre a máquina virtual
- A evolução do Berkeley Packet Filter para o eBPF e por que a Solana o adotou
- A arquitetura da máquina virtual, o conjunto de instruções e o modelo de memória do sBPF
- Como configurar seu ambiente de desenvolvimento e compilar programas sBPF
- Programação em Assembly passo a passo por meio de um exemplo prático de memo
- Considerações essenciais de segurança ao escrever código de baixo nível
O que é Assembly?
Assembly é uma variante legível por humanos do código de máquina: a linguagem de programação de nível mais baixo que corresponde diretamente ao conjunto de instruções de uma CPU ou VM.
Em vez de variáveis e funções, o Assembly opera com registradores (locais de armazenamento rápidos e temporários na CPU), endereços de memória (locais físicos na RAM ou no disco) e operações fundamentais, como load (leitura da memória), store (persistência na memória), aritmética e saltos (fluxo de controle).
Cada instrução em Assembly corresponde de forma direta a uma instrução equivalente em código de máquina.
Esse mapeamento direto significa que os programadores controlam precisamente o que o processador executa, inclusive quais registradores armazenam dados, como a memória é acessada e a sequência exata das operações.
Ao contrário das linguagens de alto nível, nas quais uma única função pode gerar dezenas de instruções, o Assembly oferece total transparência e controle sobre o comportamento da máquina, sem abstrações opacas.
O que são Berkeley Packet Filter (BPF) e eBPF?
O Berkeley Packet Filter (BPF) surgiu em 1992 como uma máquina virtual para filtrar pacotes de rede com eficiência em kernels Unix. O BPF original usava um conjunto simples de instruções e uma arquitetura baseada em registradores que permitia executar código isolado com segurança dentro do kernel.
O extended Berkeley Packet Filter (eBPF) modernizou esse conceito, evoluindo de um filtro de pacotes para uma máquina virtual de uso geral. O eBPF introduziu uma arquitetura de 64 bits, mais registradores e conjuntos de instruções mais completos, permitindo que programas complexos fossem executados com segurança no espaço do kernel para redes, segurança e monitoramento de sistemas.
A Solana adotou o eBPF porque ele oferecia um ambiente de execução comprovado e seguro, com isolamento integrado. Esse isolamento impede que programas acessem recursos do sistema, derrubem nós ou interfiram em outros programas, enquanto a execução determinística garante que todos os validadores produzam resultados idênticos.
Além disso, a arquitetura baseada em registradores e o conjunto maduro de ferramentas o tornaram ideal para execução on-chain de alto desempenho, enquanto o backend LLVM existente permitiu que os desenvolvedores compilassem linguagens de alto nível, como Rust.
Arquitetura da máquina virtual sBPF
Quando um programa Solana é executado, o runtime carrega o bytecode sBPF na memória, realiza uma verificação estática para garantir a segurança — procurando loops infinitos, acessos inválidos à memória e uso correto das instruções — e então o executa dentro da máquina virtual.
A VM oferece um ambiente controlado de execução de 64 bits, no qual os programas operam completamente isolados do sistema host e de outros programas, com todo acesso a recursos intermediado pelo runtime.
Arquitetura do conjunto de instruções sBPF
O sBPF opera com onze registradores de 64 bits (r0-r10), sendo que r10 atua como um ponteiro de quadro somente leitura e r0 como registrador de retorno.
As instruções seguem um formato consistente, com opcodes que especificam operações (aritmética, lógica, acesso à memória e saltos) e operandos que indicam registradores de origem/destino, deslocamentos e/ou valores imediatos.
As principais categorias de instruções incluem operações da ALU (adição, subtração e operações bit a bit), operações de memória (load/store) e fluxo de controle (saltos condicionais/incondicionais).
Modelo de memória do sBPF
Os programas sBPF operam em um layout de memória estruturado: uma pilha de 4 KB para variáveis locais e chamadas de função, um heap para alocações dinâmicas, dados de programa somente leitura que contêm o bytecode e as constantes, além de regiões de dados de contas que são mapeadas para contas Solana e podem ser acessadas pelo programa durante a execução.
Todo acesso à memória tem seus limites verificados, e os programas não podem acessar memória fora de suas regiões designadas.
Syscalls da Solana no sBPF
Os programas sBPF não podem acessar diretamente os recursos do sistema nem realizar operações de entrada e saída. Em vez disso, eles solicitam serviços por meio de syscalls, que são instruções especiais que transferem o controle para o runtime da Solana.
No sBPF Assembly, as syscalls são invocadas com a instrução call e um símbolo de chamada que o compilador transforma em um destino de chamada durante a montagem. Atualmente, as syscalls são invocadas por meio de relocações dinâmicas baseadas em texto, um sistema complexo de tabela de consulta de strings que mapeia símbolos para um hash Mumur3 de 32 bits durante a compilação JIT. No entanto, existe uma proposta ativa para substituir isso por syscalls estáticas, simplificando drasticamente as convenções de chamada. Quando uma syscall é invocada, os argumentos são passados pelos registradores de 1 a 5, sendo que o registrador 5 às vezes atua como uma extensão da pilha, e os valores de retorno são gravados em r0.
As syscalls comuns incluem operações de memória (sol_memcpy, sol_memcmp), funções criptográficas (hashing e verificação de assinaturas), registro de logs e invocações entre programas.
Embora todas as instruções sBPF comuns tenham um custo de 1 CU, os cálculos de unidades computacionais para syscalls são tratados de maneira muito diferente. Quando uma syscall é adicionada ao protocolo, ela passa por benchmarking para determinar um custo básico de invocação (para uma invocação CPI, por exemplo, é 1000). Em alguns casos, um custo variável adicional também é aplicado de acordo com a quantidade de dados consumidos.
Tutorial de SBPF Assembly
Configure seu ambiente
Tradicionalmente, escrever em sBPF Assembly exigia todo o conjunto de ferramentas da Solana: um processo pesado, complexo e dependente da plataforma.
Por isso, Dean Little criou o SDK sBPF, que oferece uma solução completa e integrada para inicializar, construir, compilar, testar e implantar programas sBPF.
Você pode instalar o SDK em qualquer sistema operacional usando o Cargo:
cargo install --git https://github.com/blueshift-gg/sbpf.gitAntes de analisar o código, também é recomendável instalar a extensão sBPF Assembly para VS Code, que oferece destaque de sintaxe, preenchimento automático e detecção de erros.
Configure seu projeto
Crie a estrutura de um novo projeto com:
sbpf init <name_of_the_project>Isso cria um projeto com testes Rust usando o Mollusk.
Se preferir criar uma estrutura com testes TypeScript, você pode usar o seguinte comando para inicializá-la:
sbpf init <name_of_the_project> --ts-tests Exemplo de memo em sBPF Assembly
É difícil imaginar um programa mais simples que um memo, e é justamente por isso que ele é a introdução perfeita ao sBPF Assembly.
O programa faz apenas uma coisa: recebe os dados de instrução enviados por você e os registra na blockchain. Sem contas, sem lógica complexa, apenas controle puro no nível das instruções sobre a Solana Virtual Machine.
Vamos começar examinando o programa completo:
.equ NUM_ACCOUNTS, 0x00
.equ DATA_LEN, 0x08
.equ DATA, 0x10
.globl entrypoint
entrypoint:
ldxdw r0, [r1+NUM_ACCOUNTS]
ldxdw r2, [r1+DATA_LEN]
add64 r1, DATA
call sol_log_
exitDefina suas constantes
O programa começa definindo três constantes que mapeiam a estrutura da região de entrada serializada do runtime da Solana:
.equ NUM_ACCOUNTS, 0x00 // Account count offset
.equ DATA_LEN, 0x08 // Data length offset
.equ DATA, 0x10 // Data start offsetEsses deslocamentos correspondem aos locais em que o runtime da Solana armazena os dados de instrução na memória.
Quando a VM chama nosso programa, ela nos entrega um buffer estruturado no registrador r1, e essas constantes permitem navegar por essa estrutura.
Ferramentas como sbpf.xyz podem calcular automaticamente esses deslocamentos com base no layout dos dados de suas contas e instruções.
Crie o ponto de entrada
Em seguida, criamos o ponto de entrada e a validação do nosso programa.
.globl entrypoint
entrypoint:
ldxdw r0, [r1+NUM_ACCOUNTS]A diretiva de ponto de entrada .globl instrui o linker a tornar o símbolo do ponto de entrada visível globalmente. O runtime da Solana procura esse símbolo para saber onde iniciar a execução do programa. Curiosidade: embora normalmente o chamemos de entrypoint, ele pode ter qualquer nome!
A primeira instrução usa uma técnica inteligente de validação específica do Assembly: como r0 é nosso registrador de retorno e qualquer valor de retorno diferente de 0 atua como um código de erro, carregar o número de contas diretamente em r0 força o programa a encerrar com um código de erro diferente de zero caso mais de 0 contas sejam fornecidas.
Isso nos permite evitar a iteração manual pelas contas de entrada para validar o deslocamento dos dados de instrução na VM.
Invoque a syscall sol_log
Por fim, podemos executar a syscall sol_log_:
ldxdw r2, [r1+DATA_LEN] ; Load memo length
add64 r1, DATA ; Point r1 to memo data
call sol_log_ ; Log the memo
exit ; Exit with r0 valueA syscall sol_log_ espera o tamanho da mensagem em r2 e um ponteiro para a mensagem a ser registrada em r1. Felizmente, na região de entrada serializada, o runtime adiciona automaticamente aos dados da instrução um contador de tamanho de 64 bits. Assim, podemos invocar a syscall sol_log_ simplesmente:
- Carregando em r2 o valor no deslocamento DATA_LEN
- Fazendo r1 apontar para o deslocamento do início dos dados de instrução
Quando os dois registradores apontarem para os valores corretos, basta invocar a syscall e encerrar com o valor que estiver em r0.
Compile e implante seu programa
Você pode compilar seu programa usando o montador integrado do SBPF, escrito por Claire Fan: um binário Rust de 5 MB que substitui o conjunto de ferramentas LLVM de mais de 2 GB que você teria que usar com as ferramentas da plataforma Solana.
Para executar uma compilação, basta usar:
sbpf buildDepois de compilar seu programa, você pode implantá-lo com:
sbpf deployInteraja com seu programa
Para testar seu programa usando os testes da estrutura, execute sbpf test. Se preferir, execute o pipeline completo com sbpf e2e para compilar, implantar e testar com um único comando.
Considerações de segurança no sBPF Assembly
Escrever código Assembly significa assumir total responsabilidade pela segurança: não há compilador para detectar seus erros. Cada instrução afeta diretamente a segurança do programa, o que torna essenciais estes princípios fundamentais.
Validação de entradas
Programas em Assembly precisam validar manualmente todas as entradas. Sempre verifique a quantidade de contas, o tamanho dos dados e o tamanho dos buffers antes de usá-los.
Por exemplo, em nosso programa de memo, carregar a quantidade de contas em r0 criou uma validação automática: se contas fossem fornecidas incorretamente, o programa falharia. Para processar dados, confira se os tamanhos estão dentro dos intervalos esperados antes de acessar a memória.
Verificação dos limites da memória
O sBPF não oferece verificação automática de limites. Antes de acessar arrays ou buffers, verifique manualmente se as operações de leitura e gravação permanecem dentro dos limites alocados. Uma simples verificação antes do acesso à memória pode evitar falhas e corrupção de dados:
jgt r2, MAX_LENGTH, error # Check if length exceeds limit
ldxb r3, [r1+r2] # Safe to load if check passesGerenciamento de registradores
Os registradores armazenam estados críticos do programa. Ao chamar funções ou syscalls, preserve valores importantes salvando-os na pilha ou em outros registradores.
O ponteiro de quadro (r10) e os valores de retorno em r0 exigem atenção especial: corrompê-los pode derrubar seu programa ou criar vulnerabilidades de segurança.
Segurança aritmética
A detecção manual de overflow é crucial para operações aritméticas. Antes de somar valores grandes, verifique se o resultado pode exceder os limites de 64 bits.
As operações de divisão exigem verificações explícitas de zero para evitar erros de runtime.
Validação de parâmetros de syscalls
As syscalls esperam parâmetros válidos e falham com entradas inválidas. Antes de chamar syscalls, verifique se os registradores contêm ponteiros, tamanhos e valores corretos. Parâmetros inválidos não apenas causam falhas, mas também consomem unidades computacionais desnecessariamente.
Conclusão
sBPF Assembly não é para todos, e esse é exatamente o ponto.
A maioria dos desenvolvedores deve continuar usando Rust e deixar o compilador cuidar das otimizações. Mas, para quem está otimizando até a última unidade computacional ou criando infraestrutura crítica para o desempenho, o Assembly oferece algo que nenhuma linguagem de alto nível consegue oferecer: controle total.
Abordamos aqui os fundamentos, desde como o sBPF se encaixa na arquitetura da Solana até a criação do seu primeiro programa de memo.
O exemplo de memo pode parecer trivial, mas demonstra os princípios fundamentais que você usará em programas mais complexos:
- Manipulação direta de registradores
- Gerenciamento manual de memória
- Tratamento explícito de syscalls
Esses ganhos têm um custo: você troca proteções por velocidade e abstrações por controle. Escolha o Assembly quando já tiver otimizado seu código Rust e ainda precisar de mais desempenho, quando estiver criando uma infraestrutura em que cada microssegundo importa ou quando precisar fazer algo que o compilador simplesmente não consegue otimizar bem.
Para todos os outros casos, evite a dor de cabeça e continue usando Rust.
As ferramentas estão melhorando, a comunidade está crescendo e os benefícios de desempenho falam por si. Mas lembre-se: “com grandes poderes vêm grandes responsabilidades para não quebrar nada”.
Se quiser ler mais conteúdo sobre como usar sBPF Assembly, confira o Curso de introdução ao Assembly na Blueshift e teste suas habilidades com alguns dos desafios disponíveis por lá!
Artigos relacionados
Assine a Helius
Acompanhe as novidades mais recentes do desenvolvimento Solana e receba atualizações quando publicarmos


