该存储库包含一个简单的虚拟机器的实现,以及将从文件中读取程序并通过该虚拟机执行的驱动程序。
除虚拟机解释器外,您还可以找到:
这种特殊的虚拟机有意简单,但是尽管如此,希望它能以可读的方式实施。 (这里的“简单性”是指我们仅支持少量指令,并且虚拟CPU拥有的寄存器可以存储字符串和整数,而不是浮点值。)此特定的虚拟机是基于寄存器的,具有十个寄存器,可用于存储字符串或插图值。
由于编译器和分解器是用Perl编写的,因此不需要特殊处理。
用C编写的解释器可以像这样构建:
$ make
这将生成simple-vm并embedded SRC/的内容。
实施基本的虚拟机,像这样,是一个非常有理由的问题:
case语句解码说明。halt或exit指导为止。编写虚拟机的主要并发症是:
goto repeat ”不是“ 0x10 0x03 0x00 ”。这款特定的虚拟机仅包含一些原语,但确实包括对标签,循环,有条件跳跃,呼叫和返回的支持。还有一个堆栈,可用于存储临时值,并用于call / ret处理。
在此实现中处理标签可能值得一提,因为许多简单/演示的虚拟机根本无法处理它们。
为了支持跳转到不一定定义的标签,我们的编译器会保留所有标签的运行列表(即可能的跳跃目的地),当它遇到跳跃指令或引用标签的其他内容时,它会输出一个位置持有人地址,例如::
JUMP 0x0000x10 0x00 0x00因为跳跃指令定义为0x10 。编译完成后,所有目标都应被发现,并且编译器可以自由更新生成的字节码以填充适当的目的地。
注意:在我们的虚拟机中,所有跳跃都是绝对的,因此它们可能看起来像“
JUMP 0x0123”,而不是“JUMP -4”或“JUMP +30”。
注意:同样的事情适用于处理标签的其他说明,例如存储标签的地址,拨打电话等。
这种虚拟机的设计主要是作为一种学习体验,但它是基于嵌入的想法而构建的。
标准的simple-vm二进制文件将从文件中读取opcodes并解释它们,尺寸小于40k。
由于二进制操作编码的处理是通过调度桌处理的,因此您可以在系统上将自己的特定于应用程序特定的Opcodes添加到系统中,这将使您可以在愿意的情况下执行可以返回应用程序的细微汇总且高效的程序。
有一个示例是在文件src/embedded.c中定义自定义opcode。此示例定义了自定义OPCODE 0xCD ,并执行一个小程序,该程序将OPODE用于演示目的:
$ ./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
该程序首先将字符串“ I like loops ”存储在寄存器1中,然后在跳回程序开始之前打印该寄存器。
实际将此程序编译到字节码运行中:
$ ./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本身。CC=afl-gcc Makefile中的AFL-GCC设置。您可以像这样编译我们的每个捆绑样本:
cd examples/
for i in *.in; do ../compiler $i; done
cd ..
这将汇总examples/*.in examples/*.raw 。将它们放在目录中,然后启动您的fuzzer:
mkdir samples/
cp examples/*.raw samples/
现在,您有./samples/仅包含编译程序。然后,您可以使用这样的命令行突变/缩小这些示例在Fuzzer的控制下:
export FUZZ=1
afl-fuzz -i ./samples/ -o results/ ./simple-vm @@ 16384
注意:在这里,我们将每次运行限制为执行不超过16384个说明。这将确保程序中没有无限循环。
我们将环境变量FUZZ设置为仅包含1以禁用system()函数的使用。这可能会意外删除您的主机,格式化您的驱动器或向我发送捐款!
可从以下存储库中获得虚拟机编译器和解释器的Golang端口:
除此之外,您还可以在我编写的嵌入式脚本引擎中找到真正的虚拟机器,也可以找到Golang。在这种情况下,脚本语言被解析并转换为一系列字节码指令,这些指令由虚拟机执行。与该项目类似,但是字节码操作更加复杂和高级:
如果您对编译器和口译员感兴趣,通常也可能会喜欢这些其他项目: