Assembly
Introdução ao Assembly

Introdução ao Assembly

Instruções

Agora que você entende os registradores e regiões de memória do sBPF, vamos examinar as instruções que os manipulam.

Instruções são as operações fundamentais que seu programa executa — somando números, carregando da memória ou pulando para diferentes localizações.

O que são Instruções?

Instruções são os blocos básicos de construção do seu programa. Pense nelas como comandos que dizem ao processador exatamente o que fazer:

  • add64 r1, r2: "Somar os valores nos registradores r1 e r2, armazenar o resultado em r1"

  • ldxdw r0, [r10 - 8]: "Carregar 8 bytes da memória da stack no registrador r0"

  • jeq r1, 42, +3: "Se r1 for igual a 42, pular 3 instruções para frente"

Cada instrução executa exatamente uma operação e é codificada em exatamente 8 bytes de dados para decodificação instantânea pela VM.

Instruções sBPF trabalham com diferentes tamanhos de dados:

  • byte = 8 bits (1 byte)

  • halfword = 16 bits (2 bytes)

  • word = 32 bits (4 bytes)

  • doubleword = 64 bits (8 bytes)

A maioria das operações sBPF usa valores de 64 bits (doublewords) já que os registradores são de 64 bits, mas você pode carregar e armazenar tamanhos menores quando necessário para eficiência.

Categorias de Instruções e Formato

Quando você compila código Rust, C ou assembly, a toolchain emite um fluxo de instruções de largura fixa de 8 bytes empacotadas na seção .text do seu ELF.

Cada instrução segue uma estrutura consistente que a VM pode decodificar em uma única passagem:

text
   1 byte    4 bits   4 bits     2 bytes         4 bytes
┌──────────┬────────┬────────┬──────────────┬──────────────────┐
│  opcode  │  dst   │  src   │   offset     │      imm         │
└──────────┴────────┴────────┴──────────────┴──────────────────┘
  • opcode: Define o tipo de operação. Os 3 bits superiores selecionam a classe da instrução (aritmética, memória, jump, call, exit), enquanto os 5 bits inferiores especificam a variante exata (add, multiply, load, jump-if-equal).

  • dst: O número do registrador de destino (r0–r10) onde os resultados são armazenados — resultados aritméticos, valores carregados ou retornos de funções auxiliares.

  • src: O registrador de origem que fornece entrada. Para aritmética de dois operandos (add r1, r2), fornece o segundo valor. Para operações de memória, pode fornecer o endereço base. Para variantes imediatas (add r1, 10), estes 4 bits se incorporam ao opcode.

  • offset: Um pequeno inteiro que modifica o comportamento da instrução. Para loads/stores, é adicionado ao endereço de origem para alcançar [src + offset]. Para jumps, é um destino de branch relativo medido em instruções.

  • imm: O campo de valor imediato. Operações aritméticas o usam para constantes (add r1, 42), CALL o usa para números de syscall (sol_log = 16), e operações de memória podem tratá-lo como um ponteiro absoluto.

Categorias de Instruções

Diferentes tipos de instruções usam estes campos de formas específicas:

  • Movimentação de Dados: Move valores entre registradores e memória:

sbpf
mov64 r1, 42           // Colocar valor imediato 42 em r1
                       // opcode=move_imm, dst=1, src=unused, imm=42

ldxdw r0, [r10 - 8]    // Carregar 8 bytes da stack em r0  
                       // opcode=load64, dst=0, src=10, offset=-8, imm=unused

stxdw [r1 + 16], r0    // Armazenar r0 na memória em [r1 + 16]
                       // opcode=store64, dst=1, src=0, offset=16, imm=unused
  • Aritmética: Realiza operações matemáticas:

sbpf
add64 r1, r2           // r1 = r1 + r2
                       // opcode=add_reg, dst=1, src=2, offset=unused, imm=unused

add64 r1, 100          // r1 = r1 + 100  
                       // opcode=add_imm, dst=1, src=unused, offset=unused, imm=100
  • Fluxo de Controle: Altera a sequência de execução:

sbpf
ja +5                  // Pular 5 instruções incondicionalmente
                       // opcode=jump, dst=unused, src=unused, offset=5, imm=unused

jeq r1, r2, +3         // Se r1 == r2, pular 3 instruções
                       // opcode=jump_eq_reg, dst=1, src=2, offset=3, imm=unused

jeq r1, 42, +3         // Se r1 == 42, pular 3 instruções  
                       // opcode=jump_eq_imm, dst=1, src=unused, offset=3, imm=42

Codificação do Opcode

A codificação do opcode captura múltiplas informações além do tipo de operação:

  • Classe da instrução: Aritmética, memória, jump, call, etc.

  • Tamanho da operação: Operações de 32 bits vs 64 bits

  • Tipo de origem: Registrador vs valor imediato

  • Operação específica: Add vs subtract, load vs store, etc.

Isso cria opcodes distintos para variantes de instruções. Por exemplo, add64 r1, r2 (origem registrador) usa um opcode diferente de add64 r1, 42 (origem imediata). Da mesma forma, add64 e add32 têm opcodes diferentes para diferentes tamanhos de operação.

Operações aritméticas distinguem ainda entre variantes com e sem sinal. udiv64 trata valores como sem sinal (0 a 18 quintilhões), enquanto sdiv64 lida com valores com sinal (-9 quintilhões a +9 quintilhões).

Execução de Instruções

O opcode determina como a VM interpreta os campos restantes.

Quando a VM encontra add64 r1, r2, ela lê o opcode e reconhece isso como uma operação aritmética de 64 bits usando dois registradores:

O campo dst indica que o resultado vai para r1, o campo src especifica r2 como o segundo operando, e os campos offset e immediate são ignorados.

Para add64 r1, 42, o opcode muda para indicar uma operação imediata. Agora dst ainda aponta para r1, mas src torna-se sem significado, e o campo immediate fornece o segundo operando (42).

Operações de memória combinam múltiplos campos de forma significativa:

Para ldxdw r1, [r2+8], o opcode indica um load de memória de 64 bits, dst recebe o valor carregado, src fornece o endereço base, e offset (8) é adicionado para criar o endereço final r2 + 8.

Instruções de fluxo de controle seguem o mesmo padrão:

Quando você escreve jeq r1, r2, +5, o opcode codifica um jump condicional comparando dois registradores. Se r1 for igual a r2, a VM adiciona o offset (5) ao contador de programa, pulando 5 instruções.

O opcode determina quais campos são significativos. O formato da instrução permanece constante: o opcode diz como interpretar cada campo, eliminando modos de endereçamento complexos ou casos especiais.

Chamadas de Função e Syscalls

O mecanismo de chamada do sBPF evoluiu ao longo das versões para maior clareza e segurança. Até o sBPF v3, call imm servia a propósitos duplos: o valor imediato determinava se você estava chamando uma função interna ou invocando uma syscall.

O runtime distinguia entre elas com base na faixa de valores imediatos, com números de syscall tipicamente sendo pequenos inteiros positivos como 16 para sol_log.

A partir do sBPF v3, as instruções foram separadas para comportamento explícito. call off agora lida com chamadas de função internas usando offsets relativos, enquanto syscall imm invoca explicitamente funções do runtime. Essa separação torna as intenções do bytecode claras e permite melhor verificação.

Chamadas indiretas através de callx também evoluíram. Versões anteriores codificavam o registrador de destino no campo imediato, mas a partir da v2, é codificado no campo do registrador de origem para consistência com o formato geral de instrução.

Tabela de Referência de Opcodes

Operações de Load da Memória

opcodeMnemônicoDescrição
lddwlddw dst, immCarregar imediato de 64 bits (primeiro slot)
lddwlddw dst, immCarregar imediato de 64 bits (segundo slot)
ldxwldxw dst, [src + off]Carregar word da memória
ldxhldxh dst, [src + off]Carregar halfword da memória
ldxbldxb dst, [src + off]Carregar byte da memória
ldxdwldxdw dst, [src + off]Carregar doubleword da memória

Operações de Store na Memória

opcodeMnemônicoDescrição
stwstw [dst + off], immArmazenar word imediato
sthsth [dst + off], immArmazenar halfword imediato
stbstb [dst + off], immArmazenar byte imediato
stdwstdw [dst + off], immArmazenar doubleword imediato
stxwstxw [dst + off], srcArmazenar word do registrador
stxhstxh [dst + off], srcArmazenar halfword do registrador
stxbstxb [dst + off], srcArmazenar byte do registrador
stxdwstxdw [dst + off], srcArmazenar doubleword do registrador

Operações Aritméticas (64-bit)

opcodeMnemônicoDescrição
add64add64 dst, immSomar imediato
add64add64 dst, srcSomar registrador
sub64sub64 dst, immSubtrair imediato
sub64sub64 dst, srcSubtrair registrador
mul64mul64 dst, immMultiplicar imediato
mul64mul64 dst, srcMultiplicar registrador
div64div64 dst, immDividir imediato (sem sinal)
div64div64 dst, srcDividir registrador (sem sinal)
sdiv64sdiv64 dst, immDividir imediato (com sinal)
sdiv64sdiv64 dst, srcDividir registrador (com sinal)
mod64mod64 dst, immMódulo imediato (sem sinal)
mod64mod64 dst, srcMódulo registrador (sem sinal)
smod64smod64 dst, immMódulo imediato (com sinal)
smod64smod64 dst, srcMódulo registrador (com sinal)
neg64neg64 dstNegar

Operações Aritméticas (32-bit)

opcodeMnemônicoDescrição
add32add32 dst, immSomar imediato (32-bit)
add32add32 dst, srcSomar registrador (32-bit)
sub32sub32 dst, immSubtrair imediato (32-bit)
sub32sub32 dst, srcSubtrair registrador (32-bit)
mul32mul32 dst, immMultiplicar imediato (32-bit)
mul32mul32 dst, srcMultiplicar registrador (32-bit)
div32div32 dst, immDividir imediato (32-bit)
div32div32 dst, srcDividir registrador (32-bit)
sdiv32sdiv32 dst, immDividir imediato (com sinal 32-bit)
sdiv32sdiv32 dst, srcDividir registrador (com sinal 32-bit)
mod32mod32 dst, immMódulo imediato (32-bit)
mod32mod32 dst, srcMódulo registrador (32-bit)
smod32smod32 dst, immMódulo imediato (com sinal 32-bit)
smod32smod32 dst, srcMódulo registrador (com sinal 32-bit)
neg32neg32 dstNegar

Operações Lógicas (64-bit)

opcodeMnemônicoDescrição
or64or64 dst, immOR bit a bit imediato
or64or64 dst, srcOR bit a bit registrador
and64and64 dst, immAND bit a bit imediato
and64and64 dst, srcAND bit a bit registrador
lsh64lsh64 dst, immDeslocamento à esquerda imediato
lsh64lsh64 dst, srcDeslocamento à esquerda registrador
rsh64rsh64 dst, immDeslocamento à direita imediato
rsh64rsh64 dst, srcDeslocamento à direita registrador
xor64xor64 dst, immXOR bit a bit imediato
xor64xor64 dst, srcXOR bit a bit registrador
mov64mov64 dst, immMover imediato
mov64mov64 dst, srcMover registrador
arsh64arsh64 dst, immDeslocamento aritmético à direita imediato
arsh64arsh64 dst, srcDeslocamento aritmético à direita registrador

Operações Lógicas (32-bit)

opcodeMnemônicoDescrição
or32or32 dst, immOR bit a bit imediato (32-bit)
or32or32 dst, srcOR bit a bit registrador (32-bit)
and32and32 dst, immAND bit a bit imediato (32-bit)
and32and32 dst, srcAND bit a bit registrador (32-bit)
lsh32lsh32 dst, immDeslocamento à esquerda imediato (32-bit)
lsh32lsh32 dst, srcDeslocamento à esquerda registrador (32-bit)
rsh32rsh32 dst, immDeslocamento à direita imediato (32-bit)
rsh32rsh32 dst, srcDeslocamento à direita registrador (32-bit)
xor32xor32 dst, immXOR bit a bit imediato (32-bit)
xor32xor32 dst, srcXOR bit a bit registrador (32-bit)
mov32mov32 dst, immMover imediato (32-bit)
mov32mov32 dst, srcMover registrador (32-bit)
arsh32arsh32 dst, immDesloc. aritm. à direita imediato (32-bit)
arsh32arsh32 dst, srcDesloc. aritm. à direita registrador (32-bit)

Operações de Fluxo de Controle

opcodeMnemônicoDescrição
jaja offJump incondicional (jump 0 = pular para o próximo)
jeqjeq dst, imm, offJump se igual ao imediato
jeqjeq dst, src, offJump se igual ao registrador
jgtjgt dst, imm, offJump se maior que imediato (sem sinal)
jgtjgt dst, src, offJump se maior que registrador (sem sinal)
jgejge dst, imm, offJump se maior ou igual ao imediato (sem sinal)
jgejge dst, src, offJump se maior ou igual ao registrador (sem sinal)
jsetjset dst, imm, offJump se bit definido (máscara imediata)
jsetjset dst, src, offJump se bit definido (máscara de registrador)
jnejne dst, imm, offJump se diferente do imediato
jnejne dst, src, offJump se diferente do registrador
jsgtjsgt dst, imm, offJump se maior que imediato (com sinal)
jsgtjsgt dst, src, offJump se maior que registrador (com sinal)
jsgejsge dst, imm, offJump se maior ou igual ao imediato (com sinal)
jsgejsge dst, src, offJump se maior ou igual ao registrador (com sinal)
jltjlt dst, imm, offJump se menor que imediato (sem sinal)
jltjlt dst, src, offJump se menor que registrador (sem sinal)
jlejle dst, imm, offJump se menor ou igual ao imediato (sem sinal)
jlejle dst, src, offJump se menor ou igual ao registrador (sem sinal)
jsltjslt dst, imm, offJump se menor que imediato (com sinal)
jsltjslt dst, src, offJump se menor que registrador (com sinal)
jslejsle dst, imm, offJump se menor ou igual ao imediato (com sinal)
jslejsle dst, src, offJump se menor ou igual ao registrador (com sinal)

Operações de Chamada de Função

opcodeMnemônicoDescrição
callcall imm or syscall immChamar função ou syscall
callxcallx immChamada indireta (registrador no campo imm)
exitexit or returnRetornar de função

Operações de Byte Swap

opcodeMnemônicoDescrição
be16be16 dstByte swap (16-bit)
be32be32 dstByte swap (32-bit)
be64be64 dstByte swap (64-bit)
le16le16 dstBit mask (16-bit)
le32le32 dstBit mask (32-bit)
le64le64 dstNo op (64-bit)
Blueshift © 2026Commit: 1b88646