このリポジトリには、ファイルからプログラムを読み、その仮想マシンを介して実行するドライバーとともに、単純な仮想マシンの実装が含まれています。
仮想マシンインタープリターに加えて、あなたも見つける:
この特定の仮想マシンは意図的に単純ですが、それにもかかわらず、読みやすい方法で実装されていることを願っています。 (ここでの「シンプルさ」とは、少数の命令のみをサポートすることを意味し、仮想CPUが所有するレジスタは文字列や整数を保存できますが、浮動小数点値はありません。)
コンパイラとディセパイラーはPerlで書かれているため、特別な治療は必要ありません。
Cで書かれた解釈者は、そうするように構築できます。
$ make
これにより、Src/の内容からsimple-vmが生成され、 embedded 。
このような基本的な仮想マシンを実装することは、かなりよく理解されている問題です。
caseステートメントを介して命令をデコードします。haltするか、命令exitまで続けます。仮想マシンを書くことの主な合併症は次のとおりです。
0x10 0x03 0x00 」ではない「 goto repeat 」を書きたいと考えています。この特定の仮想マシンには、いくつかのプリミティブのみが含まれていますが、ラベル、ループ、条件付きジャンプ、コール、リターンのサポートが含まれています。また、一時的な値を保存するために使用し、 call / retハンドリングに使用できるスタックもあります。
多くの単純/デモンストレーション仮想マシンがそれらをまったく処理しないため、この実装でのラベルの取り扱いはおそらく注目に値します。
必ずしも定義されていないラベルへのジャンプをサポートするために、当社のコンパイラはすべてのラベル(つまり、ジャンプの可能性のある目的地)の実行リストを保持し、ジャンプ命令またはラベルを参照する他の何かに遭遇すると、次のようなプレースホルダーアドレスを出力します。
JUMP 0x0000x10として定義されているため、3バイトシーケンス0x10 0x00 0x00であるバイトコードでは。コンパイルが完了した後、すべてのターゲットが発見されるはずであり、コンパイラは生成されたバイトコードを自由に更新して適切な宛先を埋めることができます。
注:仮想マシンでは、すべてのジャンプは絶対的であるため、「
JUMP -4」または「JUMP +30」ではなく「JUMP 0x0123」のように見えるかもしれません。
注:同じことは、ラベルのアドレスを保存したり、電話をかけるなど、ラベルを処理する他の手順にも当てはまります。
この仮想マシンは、主に学習体験として設計されていますが、埋め込みを念頭に置いて構築されています。
ファイルからオペコードを読み取り、それらを解釈する標準のsimple-vmバイナリのサイズは40k未満です。
バイナリオペコードの処理はディスパッチテーブルを介して処理されるため、独自のアプリケーション固有のオペコードをシステムに追加することができます。これにより、希望時にアプリケーションに呼び戻すことができる小さなコンパイルされた効率的なプログラムを実行できます。
ファイルsrc/embedded.cにカスタムオペコードを定義する例があります。この例では、カスタムOpcode 0xCDを定義し、デモンストレーションのためにそのopcodeを使用する小さなプログラムを実行します。
$ ./embedded
[stdout] Register R01 => 16962 [Hex:4242]
Custom Handling Here
Our bytecode is 8 bytes long
いくつかの命令タイプが実装されています。
これは単なるおもちゃであるため、指示はかなり基本的ですが、新しいものを追加することは難しくなく、利用可能なプリミティブはかなり便利です。
以下はすべての指示の例です。
: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.
次のプログラムは、文字列を際限なく出力します。
:start
store #1, "I like loops"
print_str #1
goto start
このプログラムは、最初に登録1に文字列「 I like loops 」を保存し、その後、プログラムの開始に戻る前にその登録を印刷します。
実際にこのプログラムをbytecode実行にコンパイルするには:
$ ./compiler ./examples/simple.in
これにより、ファイルのバイナリオプコードでいっぱいの出力ファイルが生成されましたsimple.raw :
$ 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
これで、その一連のオペコードを実行できます。
./simple-vm ./examples/simple.raw
実行をデバッグしたい場合は、実行してください。
DEBUG=1 ./simple-vm ./examples/simple.raw
このリポジトリにはexamples/サブディレクトリの下に保存されている例があります。ファイルの例/quine.inは、さまざまな機能の良い例を提供します。独自のオペコードを出力します。
AFLでファズテストを希望する場合は、それを非常に簡単に見つける必要があります。
afl自体をインストールします。MakefileにCC=afl-gccを設定します。バンドルされた各サンプルを次のようにコンパイルできます。
cd examples/
for i in *.in; do ../compiler $i; done
cd ..
これによりexamples/*.inに例をコンパイルしますexamples/*.rawそれらをディレクトリに配置し、ファッツァーを起動します。
mkdir samples/
cp examples/*.raw samples/
これで、 ./samples/がコンパイルされたプログラムのみを含むことができます。次に、このようなコマンドラインを使用して、ファッツァーの制御下にあるこれらの例を変異/並べ替えることができます。
export FUZZ=1
afl-fuzz -i ./samples/ -o results/ ./simple-vm @@ 16384
注:ここでは、16384年以下の指示を実行することにそれぞれの実行をキャップします。これにより、プログラムには無限のループが含まれていないことが保証されます。
環境変数FUZZを設定して、 system()関数の使用を無効にするためだけに1含みます。これは誤ってあなたのホームディレクトリを削除したり、ドライブをフォーマットしたり、寄付を送ったりするかもしれません!
Virtual-MachineコンパイラとインタープリターのGolangポートは、次のリポジトリから入手できます。
それに加えて、私が書いた埋め込まれたスクリプトエンジン内に、Golangのために、本物の仮想マシンを見つけることができます。その場合、スクリプト言語は解析され、仮想マシンによって実行される一連のバイトコード命令に変換されます。このプロジェクトと同様ですが、ByteCode操作はより複雑で高レベルです。
コンパイラーや通訳者に興味がある場合は、一般的にこれらの他のプロジェクトを楽しむことができます。