Assembly
Einführung in Assembly

Einführung in Assembly

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 Registern r1 und r2, speichere das Ergebnis in r1"

  • ldxdw r0, [r10 - 8]: "Lade 8 Bytes aus dem Stack-Speicher in Register r0"

  • jeq r1, 42, +3: "Wenn r1 gleich 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:

text
   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), CALL verwendet 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:

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
  • Arithmetik: Mathematische Operationen durchführen:

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
  • Kontrollfluss: Ausführungssequenz ändern:

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

Opcode-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.

Der Opcode bestimmt, welche Felder relevant sind. Das Anweisungsformat bleibt konstant: Der Opcode sagt dir, wie jedes Feld zu interpretieren ist, wodurch komplexe Adressierungsmodi oder Sonderfälle entfallen.

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

OpcodeMnemonicBeschreibung
lddwlddw dst, imm64-Bit-Unmittelbarwert laden (erster Slot)
lddwlddw dst, imm64-Bit-Unmittelbarwert laden (zweiter Slot)
ldxwldxw dst, [src + off]Wort aus dem Speicher laden
ldxhldxh dst, [src + off]Halbwort aus dem Speicher laden
ldxbldxb dst, [src + off]Byte aus dem Speicher laden
ldxdwldxdw dst, [src + off]Doppelwort aus dem Speicher laden

Speicheroperationen

opcodeMnemonicBeschreibung
stwstw [dst + off], immWort direkt speichern
sthsth [dst + off], immHalbwort direkt speichern
stbstb [dst + off], immByte direkt speichern
stdwstdw [dst + off], immDoppelwort direkt speichern
stxwstxw [dst + off], srcWort aus Register speichern
stxhstxh [dst + off], srcHalbwort aus Register speichern
stxbstxb [dst + off], srcByte aus Register speichern
stxdwstxdw [dst + off], srcDoppelwort aus Register speichern

Arithmetische Operationen (64-bit)

opcodeMnemonicBeschreibung
add64add64 dst, immDirektwert addieren
add64add64 dst, srcRegister addieren
sub64sub64 dst, immDirektwert subtrahieren
sub64sub64 dst, srcRegister subtrahieren
mul64mul64 dst, immMit Direktwert multiplizieren
mul64mul64 dst, srcMit Register multiplizieren
div64div64 dst, immDurch Direktwert dividieren (vorzeichenlos)
div64div64 dst, srcDurch Register dividieren (vorzeichenlos)
sdiv64sdiv64 dst, immDurch Direktwert dividieren (mit Vorzeichen)
sdiv64sdiv64 dst, srcDurch Register dividieren (mit Vorzeichen)
mod64mod64 dst, immModulo mit Direktwert (vorzeichenlos)
mod64mod64 dst, srcModulo mit Register (vorzeichenlos)
smod64smod64 dst, immModulo mit Direktwert (mit Vorzeichen)
smod64smod64 dst, srcModulo mit Register (mit Vorzeichen)
neg64neg64 dstNegieren

Arithmetische Operationen (32-bit)

opcodeMnemonicBeschreibung
add32add32 dst, immDirektwert addieren (32-bit)
add32add32 dst, srcRegister addieren (32-bit)
sub32sub32 dst, immDirektwert subtrahieren (32-bit)
sub32sub32 dst, srcRegister subtrahieren (32-bit)
mul32mul32 dst, immMit Direktwert multiplizieren (32-bit)
mul32mul32 dst, srcMit Register multiplizieren (32-bit)
div32div32 dst, immDurch Direktwert dividieren (32-bit)
div32div32 dst, srcDurch Register dividieren (32-bit)
sdiv32sdiv32 dst, immDurch Direktwert dividieren (mit Vorzeichen, 32-bit)
sdiv32sdiv32 dst, srcDurch Register dividieren (mit Vorzeichen, 32-bit)
mod32mod32 dst, immModulo mit Direktwert (32-bit)
mod32mod32 dst, srcModulo mit Register (32-bit)
smod32smod32 dst, immModulo mit Direktwert (mit Vorzeichen, 32-bit)
smod32smod32 dst, srcModulo mit Register (mit Vorzeichen, 32-bit)

Logische Operationen (64-bit)

opcodeMnemonicBeschreibung
or64or64 dst, immBitweises ODER mit Konstante
or64or64 dst, srcBitweises ODER mit Register
and64and64 dst, immBitweises UND mit Konstante
and64and64 dst, srcBitweises UND mit Register
lsh64lsh64 dst, immLinksverschiebung mit Konstante
lsh64lsh64 dst, srcLinksverschiebung mit Register
rsh64rsh64 dst, immRechtsverschiebung mit Konstante
rsh64rsh64 dst, srcRechtsverschiebung mit Register
xor64xor64 dst, immBitweises XOR mit Konstante
xor64xor64 dst, srcBitweises XOR mit Register
mov64mov64 dst, immKonstante verschieben
mov64mov64 dst, srcRegister verschieben
arsh64arsh64 dst, immArithmetische Rechtsversch. mit Konst.
arsh64arsh64 dst, srcArithmetische Rechtsversch. mit Reg.

Logische Operationen (32-bit)

opcodeMnemonicBeschreibung
or32or32 dst, immBitweises ODER mit Konstante (32-bit)
or32or32 dst, srcBitweises ODER mit Register (32-bit)
and32and32 dst, immBitweises UND mit Konstante (32-bit)
and32and32 dst, srcBitweises UND mit Register (32-bit)
lsh32lsh32 dst, immLinksverschiebung mit Konstante (32-bit)
lsh32lsh32 dst, srcLinksverschiebung mit Register (32-bit)
rsh32rsh32 dst, immRechtsverschiebung mit Konstante (32-bit)
rsh32rsh32 dst, srcRechtsverschiebung mit Register (32-bit)
xor32xor32 dst, immBitweises XOR mit Konstante (32-bit)
xor32xor32 dst, srcBitweises XOR mit Register (32-bit)
mov32mov32 dst, immKonstante verschieben (32-bit)
mov32mov32 dst, srcRegister verschieben (32-bit)
arsh32arsh32 dst, immArithm. Rechtsversch. mit Konst. (32-bit)
arsh32arsh32 dst, srcArithm. Rechtsversch. mit Reg. (32-bit)

Ablaufsteuerungsoperationen

opcodeMnemonicBeschreibung
jaja offUnbedingter Sprung (jump 0 = Sprung zum nächsten)
jeqjeq dst, imm, offSprung, wenn gleich Direktwert
jeqjeq dst, src, offSprung, wenn gleich Register
jgtjgt dst, imm, offSprung, wenn größer als Direktwert (vorzeichenlos)
jgtjgt dst, src, offSprung, wenn größer als Register (vorzeichenlos)
jgejge dst, imm, offSprung, wenn größer oder gleich Direktwert (vorzeichenlos)
jgejge dst, src, offSprung, wenn größer oder gleich Register (vorzeichenlos)
jsetjset dst, imm, offSprung, wenn Bit gesetzt (Direktwert-Maske)
jsetjset dst, src, offSprung, wenn Bit gesetzt (Register-Maske)
jnejne dst, imm, offSprung, wenn ungleich Direktwert
jnejne dst, src, offSprung, wenn ungleich Register
jsgtjsgt dst, imm, offSprung, wenn größer als Direktwert (mit Vorzeichen)
jsgtjsgt dst, src, offSprung, wenn größer als Register (mit Vorzeichen)
jsgejsge dst, imm, offSprung, wenn größer oder gleich Direktwert (mit Vorzeichen)
jsgejsge dst, src, offSprung, wenn größer oder gleich Register (mit Vorzeichen)
jltjlt dst, imm, offSprung, wenn kleiner als Direktwert (vorzeichenlos)
jltjlt dst, src, offSprung, wenn kleiner als Register (vorzeichenlos)
jlejle dst, imm, offSprung, wenn kleiner oder gleich Direktwert (vorzeichenlos)
jlejle dst, src, offSprung, wenn kleiner oder gleich Register (vorzeichenlos)
jsltjslt dst, imm, offSprung, wenn kleiner als Direktwert (mit Vorzeichen)
jsltjslt dst, src, offSprung, wenn kleiner als Register (mit Vorzeichen)
jslejsle dst, imm, offSprung, wenn kleiner oder gleich Direktwert (mit Vorzeichen)
jslejsle dst, src, offSprung, wenn kleiner oder gleich Register (mit Vorzeichen)

Funktionsaufruf-Operationen

opcodeMnemonicBeschreibung
callcall imm oder syscall immFunktion oder Syscall aufrufen
callxcallx immIndirekter Aufruf (Register im imm-Feld)
exitexit oder returnRückkehr aus Funktion

Byte-Swap-Operationen

OpcodeMnemonicBeschreibung
be16be16 dstByte-Tausch (16-Bit)
be32be32 dstByte-Tausch (32-Bit)
be64be64 dstByte-Tausch (64-Bit)
le16le16 dstBitmaske (16-Bit)
le32le32 dstBitmaske (32-Bit)
le64le64 dstKeine Operation (64-Bit)
Blueshift © 2025Commit: e573eab