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 registresr1etr2, stocke le résultat dansr1"ldxdw r0, [r10 - 8]: "Charge 8 octets depuis la mémoire de pile dans le registrer0"jeq r1, 42, +3: "Sir1est é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 :
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),CALLl'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 :
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=unusedArithmétique : Effectuer des opérations mathématiques :
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=100Flux de contrôle : Modifier la séquence d'exécution :
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=42Encodage 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.
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
| opcode | Mnémonique | Description |
| lddw | lddw dst, imm | Charger un immédiat de 64 bits (premier slot) |
| lddw | lddw dst, imm | Charger un immédiat de 64 bits (second slot) |
| ldxw | ldxw dst, [src + off] | Charger un mot depuis la mémoire |
| ldxh | ldxh dst, [src + off] | Charger un demi-mot depuis la mémoire |
| ldxb | ldxb dst, [src + off] | Charger un octet depuis la mémoire |
| ldxdw | ldxdw dst, [src + off] | Charger un double mot depuis la mémoire |
Opérations de stockage en mémoire
| opcode | Mnémonique | Description |
| stw | stw [dst + off], imm | Stockage de mot immédiat |
| sth | sth [dst + off], imm | Stockage de demi-mot immédiat |
| stb | stb [dst + off], imm | Stockage d'octet immédiat |
| stdw | stdw [dst + off], imm | Stockage de mot double immédiat |
| stxw | stxw [dst + off], src | Stockage de mot depuis registre |
| stxh | stxh [dst + off], src | Stockage de demi-mot depuis registre |
| stxb | stxb [dst + off], src | Stockage d'octet depuis registre |
| stxdw | stxdw [dst + off], src | Stockage de mot double depuis registre |
Opérations arithmétiques (64 bits)
| opcode | Mnémonique | Description |
| add64 | add64 dst, imm | Addition immédiate |
| add64 | add64 dst, src | Addition de registre |
| sub64 | sub64 dst, imm | Soustraction immédiate |
| sub64 | sub64 dst, src | Soustraction de registre |
| mul64 | mul64 dst, imm | Multiplication immédiate |
| mul64 | mul64 dst, src | Multiplication de registre |
| div64 | div64 dst, imm | Division immédiate (non signée) |
| div64 | div64 dst, src | Division de registre (non signée) |
| sdiv64 | sdiv64 dst, imm | Division immédiate (signée) |
| sdiv64 | sdiv64 dst, src | Division de registre (signée) |
| mod64 | mod64 dst, imm | Modulo immédiat (non signé) |
| mod64 | mod64 dst, src | Modulo de registre (non signé) |
| smod64 | smod64 dst, imm | Modulo immédiat (signé) |
| smod64 | smod64 dst, src | Modulo de registre (signé) |
| neg64 | neg64 dst | Négation |
Opérations arithmétiques (32 bits)
| opcode | Mnémonique | Description |
| add32 | add32 dst, imm | Addition immédiate (32 bits) |
| add32 | add32 dst, src | Addition de registre (32 bits) |
| sub32 | sub32 dst, imm | Soustraction immédiate (32 bits) |
| sub32 | sub32 dst, src | Soustraction de registre (32 bits) |
| mul32 | mul32 dst, imm | Multiplication immédiate (32 bits) |
| mul32 | mul32 dst, src | Multiplication de registre (32 bits) |
| div32 | div32 dst, imm | Division immédiate (32 bits) |
| div32 | div32 dst, src | Division de registre (32 bits) |
| sdiv32 | sdiv32 dst, imm | Division immédiate (32 bits signés) |
| sdiv32 | sdiv32 dst, src | Division de registre (32 bits signés) |
| mod32 | mod32 dst, imm | Modulo immédiat (32 bits) |
| mod32 | mod32 dst, src | Modulo de registre (32 bits) |
| smod32 | smod32 dst, imm | Modulo immédiat (32 bits signés) |
| smod32 | smod32 dst, src | Modulo de registre (32 bits signés) |
Opérations logiques (64 bits)
| opcode | Mnémonique | Description |
| or64 | or64 dst, imm | OU binaire immédiat |
| or64 | or64 dst, src | OU binaire registre |
| and64 | and64 dst, imm | ET binaire immédiat |
| and64 | and64 dst, src | ET binaire registre |
| lsh64 | lsh64 dst, imm | Décalage à gauche immédiat |
| lsh64 | lsh64 dst, src | Décalage à gauche registre |
| rsh64 | rsh64 dst, imm | Décalage à droite immédiat |
| rsh64 | rsh64 dst, src | Décalage à droite registre |
| xor64 | xor64 dst, imm | XOR binaire immédiat |
| xor64 | xor64 dst, src | XOR binaire registre |
| mov64 | mov64 dst, imm | Déplacement immédiat |
| mov64 | mov64 dst, src | Déplacement registre |
| arsh64 | arsh64 dst, imm | Décalage arithmétique à droite imm |
| arsh64 | arsh64 dst, src | Décalage arithmétique à droite reg |
Opérations logiques (32 bits)
| opcode | Mnémonique | Description |
| or32 | or32 dst, imm | OU binaire immédiat (32 bits) |
| or32 | or32 dst, src | OU binaire registre (32 bits) |
| and32 | and32 dst, imm | ET binaire immédiat (32 bits) |
| and32 | and32 dst, src | ET binaire registre (32 bits) |
| lsh32 | lsh32 dst, imm | Décalage à gauche immédiat (32 bits) |
| lsh32 | lsh32 dst, src | Décalage à gauche registre (32 bits) |
| rsh32 | rsh32 dst, imm | Décalage à droite immédiat (32 bits) |
| rsh32 | rsh32 dst, src | Décalage à droite registre (32 bits) |
| xor32 | xor32 dst, imm | XOR binaire immédiat (32 bits) |
| xor32 | xor32 dst, src | XOR binaire registre (32 bits) |
| mov32 | mov32 dst, imm | Déplacement immédiat (32 bits) |
| mov32 | mov32 dst, src | Déplacement registre (32 bits) |
| arsh32 | arsh32 dst, imm | Décalage arith à droite imm (32 bits) |
| arsh32 | arsh32 dst, src | Décalage arith à droite reg (32 bits) |
Opérations de flux de contrôle
| opcode | Mnémonique | Description |
| ja | ja off | Saut inconditionnel (saut 0 = saut au suivant) |
| jeq | jeq dst, imm, off | Saut si égal à l'immédiat |
| jeq | jeq dst, src, off | Saut si égal au registre |
| jgt | jgt dst, imm, off | Saut si supérieur à l'immédiat (non signé) |
| jgt | jgt dst, src, off | Saut si supérieur au registre (non signé) |
| jge | jge dst, imm, off | Saut si supérieur ou égal à l'immédiat (non signé) |
| jge | jge dst, src, off | Saut si supérieur ou égal au registre (non signé) |
| jset | jset dst, imm, off | Saut si bit défini (masque immédiat) |
| jset | jset dst, src, off | Saut si bit défini (masque registre) |
| jne | jne dst, imm, off | Saut si non égal à l'immédiat |
| jne | jne dst, src, off | Saut si non égal au registre |
| jsgt | jsgt dst, imm, off | Saut si supérieur à l'immédiat (signé) |
| jsgt | jsgt dst, src, off | Saut si supérieur au registre (signé) |
| jsge | jsge dst, imm, off | Saut si supérieur ou égal à l'immédiat (signé) |
| jsge | jsge dst, src, off | Saut si supérieur ou égal au registre (signé) |
| jlt | jlt dst, imm, off | Saut si inférieur à l'immédiat (non signé) |
| jlt | jlt dst, src, off | Saut si inférieur au registre (non signé) |
| jle | jle dst, imm, off | Saut si inférieur ou égal à l'immédiat (non signé) |
| jle | jle dst, src, off | Saut si inférieur ou égal au registre (non signé) |
| jslt | jslt dst, imm, off | Saut si inférieur à l'immédiat (signé) |
| jslt | jslt dst, src, off | Saut si inférieur au registre (signé) |
| jsle | jsle dst, imm, off | Saut si inférieur ou égal à l'immédiat (signé) |
| jsle | jsle dst, src, off | Saut si inférieur ou égal au registre (signé) |
Opérations d'appel de fonction
| opcode | Mnémonique | Description |
| call | call imm ou syscall imm | Appel de fonction ou d'appel système |
| callx | callx imm | Appel indirect (registre dans champ imm) |
| exit | exit ou return | Retour de fonction |
Opérations d'échange d'octets
| opcode | Mnémonique | Description |
| be16 | be16 dst | Échange d'octets (16 bits) |
| be32 | be32 dst | Échange d'octets (32 bits) |
| be64 | be64 dst | Échange d'octets (64 bits) |
| le16 | le16 dst | Masque de bits (16 bits) |
| le32 | le32 dst | Masque de bits (32 bits) |
| le64 | le64 dst | Aucune opération (64 bits) |