Dieses Repository enthält die Implementierung für eine einfache virtuelle Maschine sowie einen Treiber, der ein Programm aus einer Datei liest und über diesen virtuellen Computer ausführt.
Zusätzlich zum Interpreter Virtual Machine finden Sie auch:
Diese spezielle virtuelle Maschine ist absichtlich einfach, aber trotzdem wird sie hoffentlich lesbar implementiert. ("Einfachheit" bedeutet hier, dass wir nur eine kleine Anzahl von Anweisungen unterstützen, und die Register, die die virtuelle CPU besitzt, kann Zeichenfolgen und Ganzzahlen speichern, jedoch nicht schwimmende Punktwerte.) Diese bestimmte virtuelle Maschine ist registriert basiert, wobei zehn Register verwendet werden können, die zum Speichern von Strings oder ganzzahligen Werten verwendet werden können.
Da der Compiler und der Dekompiler in Perl geschrieben sind, benötigen sie keine besondere Behandlung.
Der in C geschriebene Interpretator kann wie SO gebaut werden:
$ make
Dies erzeugt simple-vm und embedded aus dem Inhalt von SRC/.
Das Implementieren einer grundlegenden virtuellen Maschine wie dieser ist ein ziemlich gut verstandenes Problem:
case .halt machen oder Anweisungen exit .Die Hauptkomplikationen beim Schreiben einer virtuellen Maschine sind:
goto repeat " nicht " 0x10 0x03 0x00 " schreiben. Diese spezielle virtuelle Maschine enthält nur wenige Grundelemente, enthält jedoch die Unterstützung für Etiketten, Schleifen, bedingte Sprünge, Anrufe und Rückgaben. Es gibt auch einen Stapel, der zum Speichern von temporären Werten verwendet und für call / ret -Handhabung verwendet werden kann.
Die Handhabung von Beschriftungen in dieser Implementierung ist möglicherweise bemerkenswert, da viele einfache/Demonstration virtuelle Maschinen überhaupt nicht umgehen.
Um das Springen zu Beschriftungen zu unterstützen, die noch nicht unbedingt definiert wurden, hat unser Compiler eine laufende Liste aller Etiketten (dh mögliche Sprungziele) und wenn sie auf eine Sprunganweisung oder etwas anderes auf ein Etikett bezieht, gibt es eine Platzhalter-Address aus, z. B.:
JUMP 0x0000x10 0x00 0x00 als Sprunganweisung als 0x10 definiert.Nach Abschluss der Zusammenstellung sollten alle Ziele entdeckt werden, und der Compiler kann die generierten Bytecodes frei aktualisieren, um die entsprechenden Ziele zu füllen.
HINWEIS : In unseren virtuellen Maschinen sind alle Sprünge absolut, sodass sie möglicherweise wie "
JUMP 0x0123" und nicht "JUMP -4" oder "JUMP +30" aussehen.
Hinweis : Gleiches gilt für andere Anweisungen, die Beschriftungen verarbeiten, z.
Diese virtuelle Maschine ist in erster Linie als Lernerfahrung konzipiert, aber sie ist mit der Idee aufgebaut, sich zu berücksichtigen.
Der Standard simple-vm Binary, der Opcodes aus einer Datei liest und sie interpretiert, hat eine Größe von weniger als 40 K.
Da die Verarbeitung von Binär-Opcodes über einen Versandtisch behandelt wird, können Sie dem System Ihre eigenen anwendungsspezifischen Opcodes hinzufügen, mit denen Sie winzige, kompilierte und effiziente Programme ausführen können, die in Ihre Anwendung zurückrufen können, wenn sie dies wünschen.
Es gibt ein Beispiel, um einen benutzerdefinierten Opcode in der Datei src/embedded.c zu definieren. Dieses Beispiel definiert einen benutzerdefinierten Opcode 0xCD und führt ein kleines Programm aus, das diesen Opcode für Demonstrationszwecke verwendet:
$ ./embedded
[stdout] Register R01 => 16962 [Hex:4242]
Custom Handling Here
Our bytecode is 8 bytes long
Es werden mehrere Anweisungen implementiert:
Die Anweisungen sind ziemlich einfach, da dies nur ein Spielzeug ist, aber das Hinzufügen neuer ist nicht schwierig und die verfügbaren Primitiven sind einigermaßen nützlich wie IS.
Im Folgenden sind Beispiele für alle Anweisungen:
:test
:label
goto 0x44ff # Jump to the given address
goto label # Jump to the given label
jmpnz label # Jump to label if Zero-Flag is not set
jmpz label # Jump to label if Zero-Flag is set
store #1, 33 # store 33 in register 1
store #2, "Steve" # Store the string "Steve" in register 1.
store #1, #3 # register1 is set to the contents of register #3.
exit # Stop processing.
nop # Do nothing
print_int #3 # Print the (integer) contents of register 3
print_str #3 # Print the (string) contents of register 3
system #3 # Call the (string) command stored in register 3
add #1, #2, #3 # Add register 2 + register 3 contents, store in reg 1
sub #1, #2, #3 # sub register 2 + register 3 contents, store in reg 1
mul #1, #2, #3 # multiply register 2 + register 3 contents, store in reg 1
concat #1, #2,#3 # store concatenated strings from reg2 + reg3 in reg1.
dec #2 # Decrement the integer in register 2
inc #2 # Increment the integer in register 2
string2int #3 # Change register 3 to have a string from an int
is_integer #3 # Does the given register have an integer content?
int2string #3 # Change register 3 to have an int from a string
is_string #3 # Does the given register have a string-content?
cmp #3, #4 # Compare contents of reg 3 & 4, set the Z-flag.
cmp #3, 42 # Compare contents of reg 3 with the constant 42. sets z.
cmp #3, "Moi" # Compare contents of reg 3 with the constant string "Moi". sets z.
peek #1, #4 # Load register 1 with the contents of the address in #4.
poke #1, #4 # Set the address stored in register4 with the contents of reg1.
random #2 # Store a random integer in register #2.
push #1 # Store the contents of register #1 in the stack
pop #1 # Load register #1 with the contents of the stack.
call 0xFFEE # Call the given address.
call my_label # Call the defined label
ret # Return from a called-routine.
Das folgende Programm gibt nur endlos eine Zeichenfolge aus:
:start
store #1, "I like loops"
print_str #1
goto start
Dieses Programm speichert zunächst die Zeichenfolge " I like loops " in Register 1 und druckt dann diese Registrierung, bevor sie zum Beginn des Programms zurückgeführt werden.
Um dieses Programm tatsächlich in Bytecode -Lauf zu kompilieren:
$ ./compiler ./examples/simple.in
Dadurch werden in der Datei simple.raw eine Ausgabedatei voller Binär-Opcodes erstellt:
$ od -t x1 -An ./examples/simple.raw
01 01 0c 49 20 6c 69 6b 65 20 6c 6f 6f 70 73 03
01 06 00 00
Jetzt können wir diese Reihe von Opcodes ausführen:
./simple-vm ./examples/simple.raw
Wenn Sie die Ausführung debuggen möchten, fahren Sie aus:
DEBUG=1 ./simple-vm ./examples/simple.raw
Unter den examples/ Unterverzeichnissen in diesem Repository befinden sich weitere Beispiele. Die Dateibeispiele/Quine.in bietet ein gutes Beispiel für verschiedene Funktionen - sie gibt ihre eigenen Opcodes aus.
Wenn Sie mit AFL den Fuzz-Test möchten, sollten Sie das ziemlich unkompliziert finden:
afl selbst gemäß den Anweisungen.CC=afl-gcc in der Makefile .Sie können jede unserer gebündelten Proben wie SO zusammenstellen:
cd examples/
for i in *.in; do ../compiler $i; done
cd ..
Dadurch werden examples/*.in examples/*.raw . Platzieren Sie diese in ein Verzeichnis und starten Sie Ihren Fuzzer:
mkdir samples/
cp examples/*.raw samples/
Jetzt haben Sie ./samples/ mit nur kompilierten Programmen. Sie können diese Beispiele unter der Kontrolle des Fuzzers mit einer solchen Befehlszeile mutieren/durchdringen:
export FUZZ=1
afl-fuzz -i ./samples/ -o results/ ./simple-vm @@ 16384
HINWEIS : Hier können wir jeweils nicht mehr als 16384 Anweisungen ausführen. Dadurch wird sichergestellt, dass Programme keine unendlichen Schleifen enthalten.
Wir setzen den Umgebungsvariablen FUZZ so, dass 1 ausschließlich die Verwendung der Funktion des system() eingesetzt werden. Dies kann versehentlich Ihr Heimverzeichnis entfernen, Ihr Laufwerk formatieren oder mir eine Spende schicken!
Ein Golang-Port des Virtual-Machine-Compilers und des Interpreters finden Sie im folgenden Repository:
Darüber hinaus finden Sie in der eingebetteten Skriptmotor, die ich geschrieben habe, auch für Golang eine echte virtuelle Maschine. In diesem Fall wird eine Skriptsprache analysiert und in eine Reihe von Bytecode -Anweisungen umgewandelt, die von einer virtuellen Maschine ausgeführt werden. Ähnlich wie bei diesem Projekt, aber die Bytecode-Operationen sind komplexer und hochrangiger:
Wenn Sie an Compilern und Dolmetscher interessiert sind, können Sie diese anderen Projekte im Allgemeinen auch genießen: