Anweisungen
Nachdem du nun die Register und Speicherbereiche von sBPF verstanden hast, wollen wir die Anweisungen untersuchen, die sie manipulieren.
Anweisungen sind die grundlegenden Operationen, die dein Programm ausführt – Zahlen addieren, aus dem Speicher laden oder zu verschiedenen Stellen springen.
Was sind Anweisungen?
Anweisungen sind die grundlegenden Bausteine deines Programms. Denke an sie als Befehle, die dem Prozessor genau sagen, was zu tun ist:
add64 r1, r2: "Addiere die Werte in den Registernr1undr2, speichere das Ergebnis inr1"ldxdw r0, [r10 - 8]: "Lade 8 Bytes aus dem Stack-Speicher in Registerr0"jeq r1, 42, +3: "Wennr1gleich 42 ist, springe 3 Anweisungen vorwärts"
Jede Anweisung führt genau eine Operation aus und wird als exakt 8 Bytes Daten kodiert, damit die VM sie sofort dekodieren kann.
sBPF-Anweisungen arbeiten mit verschiedenen Datengrößen:
Byte = 8 Bits (1 Byte)
Halbwort = 16 Bits (2 Bytes)
Wort = 32 Bits (4 Bytes)
Doppelwort = 64 Bits (8 Bytes)
Die meisten sBPF-Operationen verwenden 64-Bit-Werte (Doppelwörter), da Register 64 Bit groß sind, aber du kannst bei Bedarf auch kleinere Größen laden und speichern, um die Effizienz zu steigern.
Anweisungskategorien und Format
Wenn du Rust-, C- oder Assembly-Code kompilierst, erzeugt die Toolchain einen Strom von Anweisungen mit fester Breite von 8 Bytes, die in den .textAbschnitt deiner ELF-Datei gepackt werden.
Jede Anweisung folgt einer einheitlichen Struktur, die die VM in einem einzigen Durchgang dekodieren kann:
1 byte 4 bits 4 bits 2 bytes 4 bytes
┌──────────┬────────┬────────┬──────────────┬──────────────────┐
│ opcode │ dst │ src │ offset │ imm │
└──────────┴────────┴────────┴──────────────┴──────────────────┘opcode: Definiert den Operationstyp. Die obersten 3 Bits wählen die Anweisungsklasse (Arithmetik, Speicher, Sprung, Aufruf, Beenden), während die unteren 5 Bits die genaue Variante angeben (Addition, Multiplikation, Laden, Sprung-wenn-gleich).dst: Die Nummer des Zielregisters (r0–r10), in dem Ergebnisse gespeichert werden – arithmetische Ergebnisse, geladene Werte oder Rückgabewerte von Hilfsfunktionen.src: Das Quellregister, das Eingaben liefert. Bei zweioperandiger Arithmetik (add r1, r2) liefert es den zweiten Wert. Bei Speicheroperationen kann es die Basisadresse liefern. Bei Immediate-Varianten (add r1, 10) werden diese 4 Bits in den Opcode eingebettet.offset: Eine kleine Ganzzahl, die das Verhalten der Anweisung modifiziert. Bei Lade-/Speicheroperationen wird sie zur Quelladresse addiert, um[src + offset]zu erreichen. Bei Sprüngen ist es ein relatives Sprungziel, gemessen in Anweisungen.imm: Das Feld für den Immediate-Wert. Arithmetische Operationen verwenden es für Konstanten (add r1, 42),CALLverwendet es für Syscall-Nummern (sol_log = 16), und Speicheroperationen können es als absolute Zeiger behandeln.
Befehlskategorien
Verschiedene Befehlstypen verwenden diese Felder auf spezifische Weise:
Datenbewegung: Werte zwischen Registern und Speicher bewegen:
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=unusedArithmetik: Mathematische Operationen durchführen:
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=100Kontrollfluss: Ausführungssequenz ändern:
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=42Opcode-Kodierung
Die Opcode-Kodierung erfasst mehrere Informationen über den Operationstyp hinaus:
Befehlsklasse: Arithmetik, Speicher, Sprung, Aufruf usw.
Operationsgröße: 32-Bit vs. 64-Bit Operationen
Quellentyp: Register vs. Direktwert
Spezifische Operation: Addition vs. Subtraktion, Laden vs. Speichern usw.
Dies erzeugt unterschiedliche Opcodes für Befehlsvarianten. Zum Beispiel verwendet add64 r1, r2 (Register-Quelle) einen anderen Opcode als add64 r1, 42 (Direktwert-Quelle). Ähnlich haben add64 und add32 unterschiedliche Opcodes für verschiedene Operationsgrößen.
Arithmetische Operationen unterscheiden weiter zwischen vorzeichenbehafteten und vorzeichenlosen Varianten. udiv64 behandelt Werte als vorzeichenlos (0 bis 18 Trillionen), während sdiv64 vorzeichenbehaftete Werte verarbeitet (-9 Trillionen bis +9 Trillionen).
Befehlsausführung
Der Opcode bestimmt, wie die VM die übrigen Felder interpretiert.
Wenn die VM auf add64 r1, r2 trifft, liest sie den Opcode und erkennt dies als 64-Bit arithmetische Operation mit zwei Registern:
Das Feld dst zeigt an, dass das Ergebnis in r1 gespeichert wird, das Feld src spezifiziert r2 als zweiten Operanden, und die Felder offset und immediate werden ignoriert.
Bei add64 r1, 42 ändert sich der Opcode, um eine Direktwert-Operation anzuzeigen. Jetzt zeigt dst immer noch auf r1, aber src wird bedeutungslos, und das Feld immediate liefert den zweiten Operanden (42).
Speicheroperationen kombinieren mehrere Felder sinnvoll:
Bei ldxdw r1, [r2+8] zeigt der Opcode einen 64-Bit Speicherladevorgang an, dst empfängt den geladenen Wert, src liefert die Basisadresse, und offset (8) wird hinzugefügt, um die endgültige Adresse r2 + 8 zu erzeugen.
Kontrollflussanweisungen folgen dem gleichen Muster:
Wenn du jeq r1, r2, +5 schreibst, kodiert der Opcode einen bedingten Sprung, der zwei Register vergleicht. Wenn r1 gleich r2 ist, addiert die VM den offset (5) zum Programmzähler und springt 5 Anweisungen vorwärts.
Funktionsaufrufe und Systemaufrufe
Der Aufrufmechanismus von sBPF hat sich über verschiedene Versionen hinweg für bessere Klarheit und Sicherheit weiterentwickelt. Bis sBPF v3 diente call imm zwei Zwecken: Der Unmittelbarwert bestimmte, ob du eine interne Funktion aufrufst oder einen Systemaufruf tätigst.
Die Laufzeitumgebung unterschied zwischen diesen basierend auf dem Wertebereich des Unmittelbarwerts, wobei Systemaufrufnummern typischerweise kleine positive Ganzzahlen wie 16 für sol_log waren.
Ab sBPF v3 wurden die Anweisungen für explizites Verhalten getrennt. call behandelt jetzt interne Funktionsaufrufe mit relativen Offsets, während syscall imm explizit Laufzeitfunktionen aufruft. Diese Trennung macht die Absichten des Bytecodes klar und ermöglicht eine bessere Überprüfung.
Indirekte Aufrufe durch callx haben sich ebenfalls weiterentwickelt. In früheren Versionen wurde das Zielregister im Unmittelbarfeld kodiert, aber ab v2 wird es im Quellregisterfeld kodiert, um Konsistenz mit dem allgemeinen Anweisungsformat zu gewährleisten.
Opcodes-Referenztabelle
Speicherlade-Operationen
| Opcode | Mnemonic | Beschreibung |
| lddw | lddw dst, imm | 64-Bit-Unmittelbarwert laden (erster Slot) |
| lddw | lddw dst, imm | 64-Bit-Unmittelbarwert laden (zweiter Slot) |
| ldxw | ldxw dst, [src + off] | Wort aus dem Speicher laden |
| ldxh | ldxh dst, [src + off] | Halbwort aus dem Speicher laden |
| ldxb | ldxb dst, [src + off] | Byte aus dem Speicher laden |
| ldxdw | ldxdw dst, [src + off] | Doppelwort aus dem Speicher laden |
Speicheroperationen
| opcode | Mnemonic | Beschreibung |
| stw | stw [dst + off], imm | Wort direkt speichern |
| sth | sth [dst + off], imm | Halbwort direkt speichern |
| stb | stb [dst + off], imm | Byte direkt speichern |
| stdw | stdw [dst + off], imm | Doppelwort direkt speichern |
| stxw | stxw [dst + off], src | Wort aus Register speichern |
| stxh | stxh [dst + off], src | Halbwort aus Register speichern |
| stxb | stxb [dst + off], src | Byte aus Register speichern |
| stxdw | stxdw [dst + off], src | Doppelwort aus Register speichern |
Arithmetische Operationen (64-bit)
| opcode | Mnemonic | Beschreibung |
| add64 | add64 dst, imm | Direktwert addieren |
| add64 | add64 dst, src | Register addieren |
| sub64 | sub64 dst, imm | Direktwert subtrahieren |
| sub64 | sub64 dst, src | Register subtrahieren |
| mul64 | mul64 dst, imm | Mit Direktwert multiplizieren |
| mul64 | mul64 dst, src | Mit Register multiplizieren |
| div64 | div64 dst, imm | Durch Direktwert dividieren (vorzeichenlos) |
| div64 | div64 dst, src | Durch Register dividieren (vorzeichenlos) |
| sdiv64 | sdiv64 dst, imm | Durch Direktwert dividieren (mit Vorzeichen) |
| sdiv64 | sdiv64 dst, src | Durch Register dividieren (mit Vorzeichen) |
| mod64 | mod64 dst, imm | Modulo mit Direktwert (vorzeichenlos) |
| mod64 | mod64 dst, src | Modulo mit Register (vorzeichenlos) |
| smod64 | smod64 dst, imm | Modulo mit Direktwert (mit Vorzeichen) |
| smod64 | smod64 dst, src | Modulo mit Register (mit Vorzeichen) |
| neg64 | neg64 dst | Negieren |
Arithmetische Operationen (32-bit)
| opcode | Mnemonic | Beschreibung |
| add32 | add32 dst, imm | Direktwert addieren (32-bit) |
| add32 | add32 dst, src | Register addieren (32-bit) |
| sub32 | sub32 dst, imm | Direktwert subtrahieren (32-bit) |
| sub32 | sub32 dst, src | Register subtrahieren (32-bit) |
| mul32 | mul32 dst, imm | Mit Direktwert multiplizieren (32-bit) |
| mul32 | mul32 dst, src | Mit Register multiplizieren (32-bit) |
| div32 | div32 dst, imm | Durch Direktwert dividieren (32-bit) |
| div32 | div32 dst, src | Durch Register dividieren (32-bit) |
| sdiv32 | sdiv32 dst, imm | Durch Direktwert dividieren (mit Vorzeichen, 32-bit) |
| sdiv32 | sdiv32 dst, src | Durch Register dividieren (mit Vorzeichen, 32-bit) |
| mod32 | mod32 dst, imm | Modulo mit Direktwert (32-bit) |
| mod32 | mod32 dst, src | Modulo mit Register (32-bit) |
| smod32 | smod32 dst, imm | Modulo mit Direktwert (mit Vorzeichen, 32-bit) |
| smod32 | smod32 dst, src | Modulo mit Register (mit Vorzeichen, 32-bit) |
Logische Operationen (64-bit)
| opcode | Mnemonic | Beschreibung |
| or64 | or64 dst, imm | Bitweises ODER mit Konstante |
| or64 | or64 dst, src | Bitweises ODER mit Register |
| and64 | and64 dst, imm | Bitweises UND mit Konstante |
| and64 | and64 dst, src | Bitweises UND mit Register |
| lsh64 | lsh64 dst, imm | Linksverschiebung mit Konstante |
| lsh64 | lsh64 dst, src | Linksverschiebung mit Register |
| rsh64 | rsh64 dst, imm | Rechtsverschiebung mit Konstante |
| rsh64 | rsh64 dst, src | Rechtsverschiebung mit Register |
| xor64 | xor64 dst, imm | Bitweises XOR mit Konstante |
| xor64 | xor64 dst, src | Bitweises XOR mit Register |
| mov64 | mov64 dst, imm | Konstante verschieben |
| mov64 | mov64 dst, src | Register verschieben |
| arsh64 | arsh64 dst, imm | Arithmetische Rechtsversch. mit Konst. |
| arsh64 | arsh64 dst, src | Arithmetische Rechtsversch. mit Reg. |
Logische Operationen (32-bit)
| opcode | Mnemonic | Beschreibung |
| or32 | or32 dst, imm | Bitweises ODER mit Konstante (32-bit) |
| or32 | or32 dst, src | Bitweises ODER mit Register (32-bit) |
| and32 | and32 dst, imm | Bitweises UND mit Konstante (32-bit) |
| and32 | and32 dst, src | Bitweises UND mit Register (32-bit) |
| lsh32 | lsh32 dst, imm | Linksverschiebung mit Konstante (32-bit) |
| lsh32 | lsh32 dst, src | Linksverschiebung mit Register (32-bit) |
| rsh32 | rsh32 dst, imm | Rechtsverschiebung mit Konstante (32-bit) |
| rsh32 | rsh32 dst, src | Rechtsverschiebung mit Register (32-bit) |
| xor32 | xor32 dst, imm | Bitweises XOR mit Konstante (32-bit) |
| xor32 | xor32 dst, src | Bitweises XOR mit Register (32-bit) |
| mov32 | mov32 dst, imm | Konstante verschieben (32-bit) |
| mov32 | mov32 dst, src | Register verschieben (32-bit) |
| arsh32 | arsh32 dst, imm | Arithm. Rechtsversch. mit Konst. (32-bit) |
| arsh32 | arsh32 dst, src | Arithm. Rechtsversch. mit Reg. (32-bit) |
Ablaufsteuerungsoperationen
| opcode | Mnemonic | Beschreibung |
| ja | ja off | Unbedingter Sprung (jump 0 = Sprung zum nächsten) |
| jeq | jeq dst, imm, off | Sprung, wenn gleich Direktwert |
| jeq | jeq dst, src, off | Sprung, wenn gleich Register |
| jgt | jgt dst, imm, off | Sprung, wenn größer als Direktwert (vorzeichenlos) |
| jgt | jgt dst, src, off | Sprung, wenn größer als Register (vorzeichenlos) |
| jge | jge dst, imm, off | Sprung, wenn größer oder gleich Direktwert (vorzeichenlos) |
| jge | jge dst, src, off | Sprung, wenn größer oder gleich Register (vorzeichenlos) |
| jset | jset dst, imm, off | Sprung, wenn Bit gesetzt (Direktwert-Maske) |
| jset | jset dst, src, off | Sprung, wenn Bit gesetzt (Register-Maske) |
| jne | jne dst, imm, off | Sprung, wenn ungleich Direktwert |
| jne | jne dst, src, off | Sprung, wenn ungleich Register |
| jsgt | jsgt dst, imm, off | Sprung, wenn größer als Direktwert (mit Vorzeichen) |
| jsgt | jsgt dst, src, off | Sprung, wenn größer als Register (mit Vorzeichen) |
| jsge | jsge dst, imm, off | Sprung, wenn größer oder gleich Direktwert (mit Vorzeichen) |
| jsge | jsge dst, src, off | Sprung, wenn größer oder gleich Register (mit Vorzeichen) |
| jlt | jlt dst, imm, off | Sprung, wenn kleiner als Direktwert (vorzeichenlos) |
| jlt | jlt dst, src, off | Sprung, wenn kleiner als Register (vorzeichenlos) |
| jle | jle dst, imm, off | Sprung, wenn kleiner oder gleich Direktwert (vorzeichenlos) |
| jle | jle dst, src, off | Sprung, wenn kleiner oder gleich Register (vorzeichenlos) |
| jslt | jslt dst, imm, off | Sprung, wenn kleiner als Direktwert (mit Vorzeichen) |
| jslt | jslt dst, src, off | Sprung, wenn kleiner als Register (mit Vorzeichen) |
| jsle | jsle dst, imm, off | Sprung, wenn kleiner oder gleich Direktwert (mit Vorzeichen) |
| jsle | jsle dst, src, off | Sprung, wenn kleiner oder gleich Register (mit Vorzeichen) |
Funktionsaufruf-Operationen
| opcode | Mnemonic | Beschreibung |
| call | call imm oder syscall imm | Funktion oder Syscall aufrufen |
| callx | callx imm | Indirekter Aufruf (Register im imm-Feld) |
| exit | exit oder return | Rückkehr aus Funktion |
Byte-Swap-Operationen
| Opcode | Mnemonic | Beschreibung |
| be16 | be16 dst | Byte-Tausch (16-Bit) |
| be32 | be32 dst | Byte-Tausch (32-Bit) |
| be64 | be64 dst | Byte-Tausch (64-Bit) |
| le16 | le16 dst | Bitmaske (16-Bit) |
| le32 | le32 dst | Bitmaske (32-Bit) |
| le64 | le64 dst | Keine Operation (64-Bit) |