go.vm
This project is a golang based compiler and interpreter for a simple virtual machine. It is a port of the existing project:
(The original project has a perl based compiler/decompiler and an interpreter written in C.)
You can get a feel for what it looks like by referring to either the parent project, or the examples contained in this repository.
This particular virtual machine is intentionally simple, but despite that it is hopefully implemented in a readable fashion. ("Simplicity" here means that we support only a small number of instructions, and the 16-registers the virtual CPU possesses can store strings and integers, but not floating-point values.)
InstallationInstall the code via:
$ go get -u github.com/skx/go.vm
$ go install github.com/skx/go.vm
Once installed there are three sub-commands of interest:
go.vm compile $file.ingo.vm execute $file.rawgo.vm run $file.in
examples/hello.in
$ go.vm compile examples/hello.in
Then to execute the resulting bytecode:
$ go.vm execute examples/hello.raw
Or you can handle both steps at once:
$ go.vm run examples/hello.in
Opcodes
The virtual machine has 16 registers, each of which can store an integer or a string. For example to set the first two registers you might write:
store #0, "This is a string"
store #1, 0xFFFF
In addition to this there are several mathematical operations which have the general form:
$operation $result, $src1, $src2
For example to add the contents of register #1 and register #2, storing the result in register #0 you would write:
add #0, #1, #2
Strings and integers may be displayed to STDOUT via:
print_str #1
print_int #3
callretjmpZincdec
store #1, 0x42
cmp #1, 0x42
jmpz ok
store #1, "Something weird happened!\n"
print_str #1
exit
:ok
store #1, "Comparing register #01 to 0x42 succeeded!\n"
print_str #1
exit
Further instructions are available and can be viewed beneath examples/. The instruction-set is pretty limited, for example there is no notion of reading from STDIN - however this is supported via the use of traps, as documented below.
NotesSome brief notes on parts of the code / operation:
The compiler
The compiler is built in a traditional fashion:
The approach to labels is the same as in the inspiring-project: Every time we come across a label we output a pair of temporary bytes in our bytecode. Later, once we've read the whole program and assume we've found all existing labels, we go back up and fix the generated addresses.
dump
$ go.vm dump ./examples/hello.in
{STORE store}
{IDENT #1}
{, ,}
{STRING Hello, World!
}
{PRINT_STR print_str}
{IDENT #1}
{EXIT exit}
The interpreter
The core of the interpreter is located in the file cpu.go and is as simple and naive as you would expect. There are some supporting files in the same directory:
- register.go
- The implementation of the register-related functions.
- stack.go
- The implementation of the stack.
Changes
Compared to the original project there are two main changes:
DBDATAtraps
DB/DATA Changes
For example in simple.vm project this is possible:
DB 0x01, 0x02,
But this is not:
DB "This is a string, with terminator to follow"
DB 0x00
go.vm
Traps
int
int 0x00#0#0int 0x01#0int 0x02#0
Adding your own trap-functions should be as simple as editing cpu/traps.go.
Fuzzing
Fuzz-testing is a powerful technique to discover bugs, in brief it consists of running a program with numerous random inputs and waiting for it to die.
I've fuzzed this repository repeatedly via go-fuzz and fixed a couple of minor issues.
Note however that fuzzing will trigger some expected failures. Our virtual CPU has only 16 registers, so for example a program that tries to set register #30 to a particular value is invalid, and will terminate the virtual machine.
Because fuzzing involves using "random" input it is possible there are bugs lurking in the virtual-machine which I've not been lucky enough to catch, so if you wish to fuzz this is how you do it. First of all install the tool:
$ go get github.com/dvyukov/go-fuzz/go-fuzz
$ go get github.com/dvyukov/go-fuzz/go-fuzz-build
Now you can build the interpreter using it:
$ go-fuzz-build github.com/skx/go.vm/fuzz
Finally you can launch the fuzzer:
$ go-fuzz -nprocs=1 -bin=fuzz-fuzz.zip -workdir=workdir
workdir/crashers/*.output
$ cat workdir/crashers/92108737efbd0ac6b42ae4473db5a257314b36cf.output
Register 99 out of range
exit status 1