Assembly
Introduction à l'assembleur

Introduction à l'assembleur

Instructions

Maintenant que vous comprenez les registres et les régions mémoire de sBPF, examinons les instructions qui les manipulent.

Les instructions sont les opérations fondamentales que votre programme effectue—additionner des nombres, charger depuis la mémoire, ou sauter à différents emplacements.

Que sont les instructions ?

Les instructions sont les éléments de base de votre programme. Considérez-les comme des commandes qui indiquent précisément au processeur ce qu'il doit faire :

  • add64 r1, r2 : "Additionne les valeurs dans les registres r1 et r2, stocke le résultat dans r1"

  • ldxdw r0, [r10 - 8] : "Charge 8 octets depuis la mémoire de pile dans le registre r0"

  • jeq r1, 42, +3 : "Si r1 est égal à 42, saute 3 instructions en avant"

Chaque instruction effectue exactement une opération et est encodée en précisément 8 octets de données pour un décodage instantané par la VM.

Les instructions sBPF fonctionnent avec différentes tailles de données :

  • octet = 8 bits (1 octet)

  • demi-mot = 16 bits (2 octets)

  • mot = 32 bits (4 octets)

  • double mot = 64 bits (8 octets)

La plupart des opérations sBPF utilisent des valeurs 64 bits (double mots) puisque les registres font 64 bits, mais vous pouvez charger et stocker des tailles plus petites quand nécessaire pour plus d'efficacité.

Catégories d'instructions et format

Lorsque vous compilez du code Rust, C ou assembleur, la chaîne d'outils émet un flux d'instructions de largeur fixe de 8 octets, empaquetées dans la section .text de votre ELF.

Chaque instruction suit une structure cohérente que la VM peut décoder en un seul passage :

text
   1 byte    4 bits   4 bits     2 bytes         4 bytes
┌──────────┬────────┬────────┬──────────────┬──────────────────┐
│  opcode  │  dst   │  src   │   offset     │      imm         │
└──────────┴────────┴────────┴──────────────┴──────────────────┘
  • opcode : Définit le type d'opération. Les 3 bits supérieurs sélectionnent la classe d'instruction (arithmétique, mémoire, saut, appel, sortie), tandis que les 5 bits inférieurs spécifient la variante exacte (addition, multiplication, chargement, saut-si-égal).

  • dst : Le numéro du registre de destination (r0–r10) où les résultats sont stockés—résultats arithmétiques, valeurs chargées ou retours de fonctions auxiliaires.

  • src : Le registre source fournissant l'entrée. Pour l'arithmétique à deux opérandes (add r1, r2), il fournit la seconde valeur. Pour les opérations mémoire, il peut fournir l'adresse de base. Pour les variantes immédiates (add r1, 10), ces 4 bits sont intégrés dans l'opcode.

  • offset : Un petit entier qui modifie le comportement de l'instruction. Pour les chargements/stockages, il est ajouté à l'adresse source pour atteindre [src + offset]. Pour les sauts, c'est une cible de branchement relative mesurée en instructions.

  • imm : Le champ de valeur immédiate. Les opérations arithmétiques l'utilisent pour les constantes (add r1, 42), CALL l'utilise pour les numéros d'appels système (sol_log = 16), et les opérations mémoire peuvent le traiter comme un pointeur absolu.

Catégories d'instructions

Les différents types d'instructions utilisent ces champs de manières spécifiques :

  • Déplacement de données : Déplacer des valeurs entre les registres et la mémoire :

sbpf
mov64 r1, 42           // Put immediate value 42 into r1
                       // opcode=move_imm, dst=1, src=unused, imm=42

ldxdw r0, [r10 - 8]    // Load 8 bytes from stack into r0  
                       // opcode=load64, dst=0, src=10, offset=-8, imm=unused

stxdw [r1 + 16], r0    // Store r0 to memory at [r1 + 16]
                       // opcode=store64, dst=1, src=0, offset=16, imm=unused
  • Arithmétique : Effectuer des opérations mathématiques :

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
  • Flux de contrôle : Modifier la séquence d'exécution :

sbpf
ja +5                  // Jump forward 5 instructions unconditionally
                       // opcode=jump, dst=unused, src=unused, offset=5, imm=unused

jeq r1, r2, +3         // If r1 == r2, jump forward 3 instructions
                       // opcode=jump_eq_reg, dst=1, src=2, offset=3, imm=unused

jeq r1, 42, +3         // If r1 == 42, jump forward 3 instructions  
                       // opcode=jump_eq_imm, dst=1, src=unused, offset=3, imm=42

Encodage des opcodes

L'encodage de l'opcode capture plusieurs informations au-delà du simple type d'opération :

  • Classe d'instruction : arithmétique, mémoire, saut, appel, etc.

  • Taille de l'opération : opérations 32 bits vs 64 bits

  • Type de source : registre vs valeur immédiate

  • Opération spécifique : addition vs soustraction, chargement vs stockage, etc.

Cela crée des opcodes distincts pour les variantes d'instructions. Par exemple, add64 r1, r2 (source registre) utilise un opcode différent de add64 r1, 42 (source immédiate). De même, add64 et add32 ont des opcodes différents pour différentes tailles d'opération.

Les opérations arithmétiques distinguent également les variantes signées et non signées. udiv64 traite les valeurs comme non signées (de 0 à 18 quintillions), tandis que sdiv64 gère les valeurs signées (de -9 quintillions à +9 quintillions).

Exécution des instructions

L'opcode détermine comment la VM interprète les champs restants.

Lorsque la VM rencontre add64 r1, r2, elle lit l'opcode et reconnaît qu'il s'agit d'une opération arithmétique 64 bits utilisant deux registres :

Le champ dst indique que le résultat va dans r1, le champ src spécifie r2 comme second opérande, et les champs offset et immediate sont ignorés.

Pour add64 r1, 42, l'opcode change pour indiquer une opération immédiate. Maintenant dst pointe toujours vers r1, mais src devient sans importance, et le champ immediate fournit le second opérande (42).

Les opérations de mémoire combinent plusieurs champs de manière significative :

Pour ldxdw r1, [r2+8], l'opcode indique un chargement mémoire 64 bits, dst reçoit la valeur chargée, src fournit l'adresse de base, et offset (8) est ajouté pour créer l'adresse finale r2 + 8.

Les instructions de flux de contrôle suivent le même modèle :

Lorsque vous écrivez jeq r1, r2, +5, l'opcode encode un saut conditionnel comparant deux registres. Si r1 est égal à r2, la VM ajoute l'offset (5) au compteur de programme, sautant ainsi 5 instructions vers l'avant.

L'opcode détermine quels champs sont significatifs. Le format d'instruction reste constant : l'opcode vous indique comment interpréter chaque champ, éliminant les modes d'adressage complexes ou les cas particuliers.

Appels de fonctions et appels système

Le mécanisme d'appel de sBPF a évolué à travers différentes versions pour une meilleure clarté et sécurité. Jusqu'à sBPF v3, call imm servait à deux fins : la valeur immédiate déterminait si vous appeliez une fonction interne ou si vous invoquiez un appel système.

Le runtime faisait la distinction entre ces deux cas en fonction de la plage de valeurs immédiates, les numéros d'appels système étant généralement de petits entiers positifs comme 16 pour sol_log.

À partir de sBPF v3, les instructions ont été séparées pour un comportement explicite. call gère maintenant les appels de fonctions internes en utilisant des décalages relatifs, tandis que syscall imm invoque explicitement les fonctions d'exécution. Cette séparation clarifie les intentions du bytecode et permet une meilleure vérification.

Les appels indirects via callx ont également évolué. Les versions antérieures encodaient le registre cible dans le champ immédiat, mais à partir de la v2, il est encodé dans le champ du registre source pour plus de cohérence avec le format général d'instruction.

Tableau de référence des opcodes

Opérations de chargement mémoire

opcodeMnémoniqueDescription
lddwlddw dst, immCharger un immédiat de 64 bits (premier slot)
lddwlddw dst, immCharger un immédiat de 64 bits (second slot)
ldxwldxw dst, [src + off]Charger un mot depuis la mémoire
ldxhldxh dst, [src + off]Charger un demi-mot depuis la mémoire
ldxbldxb dst, [src + off]Charger un octet depuis la mémoire
ldxdwldxdw dst, [src + off]Charger un double mot depuis la mémoire

Opérations de stockage en mémoire

opcodeMnémoniqueDescription
stwstw [dst + off], immStockage de mot immédiat
sthsth [dst + off], immStockage de demi-mot immédiat
stbstb [dst + off], immStockage d'octet immédiat
stdwstdw [dst + off], immStockage de mot double immédiat
stxwstxw [dst + off], srcStockage de mot depuis registre
stxhstxh [dst + off], srcStockage de demi-mot depuis registre
stxbstxb [dst + off], srcStockage d'octet depuis registre
stxdwstxdw [dst + off], srcStockage de mot double depuis registre

Opérations arithmétiques (64 bits)

opcodeMnémoniqueDescription
add64add64 dst, immAddition immédiate
add64add64 dst, srcAddition de registre
sub64sub64 dst, immSoustraction immédiate
sub64sub64 dst, srcSoustraction de registre
mul64mul64 dst, immMultiplication immédiate
mul64mul64 dst, srcMultiplication de registre
div64div64 dst, immDivision immédiate (non signée)
div64div64 dst, srcDivision de registre (non signée)
sdiv64sdiv64 dst, immDivision immédiate (signée)
sdiv64sdiv64 dst, srcDivision de registre (signée)
mod64mod64 dst, immModulo immédiat (non signé)
mod64mod64 dst, srcModulo de registre (non signé)
smod64smod64 dst, immModulo immédiat (signé)
smod64smod64 dst, srcModulo de registre (signé)
neg64neg64 dstNégation

Opérations arithmétiques (32 bits)

opcodeMnémoniqueDescription
add32add32 dst, immAddition immédiate (32 bits)
add32add32 dst, srcAddition de registre (32 bits)
sub32sub32 dst, immSoustraction immédiate (32 bits)
sub32sub32 dst, srcSoustraction de registre (32 bits)
mul32mul32 dst, immMultiplication immédiate (32 bits)
mul32mul32 dst, srcMultiplication de registre (32 bits)
div32div32 dst, immDivision immédiate (32 bits)
div32div32 dst, srcDivision de registre (32 bits)
sdiv32sdiv32 dst, immDivision immédiate (32 bits signés)
sdiv32sdiv32 dst, srcDivision de registre (32 bits signés)
mod32mod32 dst, immModulo immédiat (32 bits)
mod32mod32 dst, srcModulo de registre (32 bits)
smod32smod32 dst, immModulo immédiat (32 bits signés)
smod32smod32 dst, srcModulo de registre (32 bits signés)

Opérations logiques (64 bits)

opcodeMnémoniqueDescription
or64or64 dst, immOU binaire immédiat
or64or64 dst, srcOU binaire registre
and64and64 dst, immET binaire immédiat
and64and64 dst, srcET binaire registre
lsh64lsh64 dst, immDécalage à gauche immédiat
lsh64lsh64 dst, srcDécalage à gauche registre
rsh64rsh64 dst, immDécalage à droite immédiat
rsh64rsh64 dst, srcDécalage à droite registre
xor64xor64 dst, immXOR binaire immédiat
xor64xor64 dst, srcXOR binaire registre
mov64mov64 dst, immDéplacement immédiat
mov64mov64 dst, srcDéplacement registre
arsh64arsh64 dst, immDécalage arithmétique à droite imm
arsh64arsh64 dst, srcDécalage arithmétique à droite reg

Opérations logiques (32 bits)

opcodeMnémoniqueDescription
or32or32 dst, immOU binaire immédiat (32 bits)
or32or32 dst, srcOU binaire registre (32 bits)
and32and32 dst, immET binaire immédiat (32 bits)
and32and32 dst, srcET binaire registre (32 bits)
lsh32lsh32 dst, immDécalage à gauche immédiat (32 bits)
lsh32lsh32 dst, srcDécalage à gauche registre (32 bits)
rsh32rsh32 dst, immDécalage à droite immédiat (32 bits)
rsh32rsh32 dst, srcDécalage à droite registre (32 bits)
xor32xor32 dst, immXOR binaire immédiat (32 bits)
xor32xor32 dst, srcXOR binaire registre (32 bits)
mov32mov32 dst, immDéplacement immédiat (32 bits)
mov32mov32 dst, srcDéplacement registre (32 bits)
arsh32arsh32 dst, immDécalage arith à droite imm (32 bits)
arsh32arsh32 dst, srcDécalage arith à droite reg (32 bits)

Opérations de flux de contrôle

opcodeMnémoniqueDescription
jaja offSaut inconditionnel (saut 0 = saut au suivant)
jeqjeq dst, imm, offSaut si égal à l'immédiat
jeqjeq dst, src, offSaut si égal au registre
jgtjgt dst, imm, offSaut si supérieur à l'immédiat (non signé)
jgtjgt dst, src, offSaut si supérieur au registre (non signé)
jgejge dst, imm, offSaut si supérieur ou égal à l'immédiat (non signé)
jgejge dst, src, offSaut si supérieur ou égal au registre (non signé)
jsetjset dst, imm, offSaut si bit défini (masque immédiat)
jsetjset dst, src, offSaut si bit défini (masque registre)
jnejne dst, imm, offSaut si non égal à l'immédiat
jnejne dst, src, offSaut si non égal au registre
jsgtjsgt dst, imm, offSaut si supérieur à l'immédiat (signé)
jsgtjsgt dst, src, offSaut si supérieur au registre (signé)
jsgejsge dst, imm, offSaut si supérieur ou égal à l'immédiat (signé)
jsgejsge dst, src, offSaut si supérieur ou égal au registre (signé)
jltjlt dst, imm, offSaut si inférieur à l'immédiat (non signé)
jltjlt dst, src, offSaut si inférieur au registre (non signé)
jlejle dst, imm, offSaut si inférieur ou égal à l'immédiat (non signé)
jlejle dst, src, offSaut si inférieur ou égal au registre (non signé)
jsltjslt dst, imm, offSaut si inférieur à l'immédiat (signé)
jsltjslt dst, src, offSaut si inférieur au registre (signé)
jslejsle dst, imm, offSaut si inférieur ou égal à l'immédiat (signé)
jslejsle dst, src, offSaut si inférieur ou égal au registre (signé)

Opérations d'appel de fonction

opcodeMnémoniqueDescription
callcall imm ou syscall immAppel de fonction ou d'appel système
callxcallx immAppel indirect (registre dans champ imm)
exitexit ou returnRetour de fonction

Opérations d'échange d'octets

opcodeMnémoniqueDescription
be16be16 dstÉchange d'octets (16 bits)
be32be32 dstÉchange d'octets (32 bits)
be64be64 dstÉchange d'octets (64 bits)
le16le16 dstMasque de bits (16 bits)
le32le32 dstMasque de bits (32 bits)
le64le64 dstAucune opération (64 bits)
Blueshift © 2025Commit: e573eab