Этот репозиторий содержит реализацию для простой виртуальной машины вместе с драйвером, который будет читать программу из файла и выполнять ее через эту виртуальную машину.
В дополнение к интерпретатору виртуальной машины вы также найдете:
Эта конкретная виртуальная машина преднамеренно проста, но, несмотря на то, что она надеждается, что она будет реализована в чтении. («Простота» здесь означает, что мы поддерживаем лишь небольшое количество инструкций, и регистры, которыми обладает виртуальный процессор, может хранить строки и целые числа, но не значения с плавающей запятой.) Эта конкретная виртуальная машина основана на регистрации, имея десять регистров, которые могут использоваться для хранения строк или целочисленных ценностей.
Поскольку компилятор и декомпилятор написаны в Perl, они не нуждаются в специальном обращении.
Интерпретатор, написанный в C, может быть построен так:
$ make
Это будет генерировать simple-vm и embedded из содержимого SRC/.
Реализация основной виртуальной машины, подобной этой, является довольно хорошо понятой проблемой:
case .halt или инструкции exit .Основными осложнениями написания виртуальной машины являются:
goto repeat " не " 0x10 0x03 0x00 ". Эта конкретная виртуальная машина содержит лишь несколько примитивов, но она включает в себя поддержку меток, цикл, условные прыжки, вызовы и возвраты. Существует также стек, который можно использовать для хранения временных значений, и используется для обработки call / ret .
Возможно, достойна обработки ярлыков в этой реализации, потому что многие простые/демонстрационные виртуальные машины вообще не справляются с ними.
Чтобы поддерживать прыжки на этикетки, которые не обязательно были определены, но наш компилятор хранит бегущий список всех ярлыков (то есть возможных направлений прыжков), и когда он сталкивается с инструкцией по прыжкам, или что-то еще, что относится к этикетке, он выводит Alcolder-Address, например::
JUMP 0x0000x10 0x00 0x00 поскольку инструкция по прыжкам определяется как 0x10 .После завершения компиляции все должны были быть обнаружены цели, и компилятор может свободно обновить сгенерированные байткоды, чтобы заполнить соответствующие пункты назначения.
Примечание . В наших виртуальных машинах все прыжки абсолютны, поэтому они могут выглядеть как «
JUMP 0x0123», а не «JUMP -4» или «JUMP +30».
Примечание : то же самое относится и к другим инструкциям, которые обрабатывают этикетки, такие как хранение адреса метки, вызов и т. Д.
Эта виртуальная машина разработана в первую очередь как опыт обучения, но она построена с учетом внедрения.
Стандартный двоичный файл simple-vm , который будет считывать Opcodes из файла и интерпретировать их, составляет менее 40 тыс. Размер.
Поскольку обработка бинарных опкодов обрабатывается через диспетчерский стол, вы можете добавить свои собственные Opcodes, специфичные для приложения, в систему, которая позволит вам выполнить крошечные компилированные и эффективные программы, которые могут обратиться к вашему применению, когда они пожелают.
Существует пример определения пользовательского OpCode в файле 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
В этой программе сначала хранится строка « I like loops » в регистрации 1, а затем печатает эту регистрацию, прежде чем вернуться к началу программы.
Чтобы фактически собрать эту программу в запуск 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
Теперь мы можем выполнить эту серию Opcodes:
./simple-vm ./examples/simple.raw
Если вы хотите отладить выполнение, запустите:
DEBUG=1 ./simple-vm ./examples/simple.raw
В этом репозитории хранится больше examples/ . Примеры файла/Quine.in дают хороший пример различных функций - он выводит свои собственные выборы.
Если вы хотите пройти тест с AFL, вы должны найти это довольно прямолинейным:
afl , согласно инструкциям.CC=afl-gcc в Makefile .Вы можете скомпилировать каждый из наших образцов, как SO:
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 с переменной окружающей среды, чтобы содержать 1 исключительно для отключения использования функции system() . Что может случайно удалить ваш домашний ректорог, отформатировать ваш диск или прислать мне пожертвование!
Порт Голанга компилятора виртуальной машины и интерпретатора доступен в следующем репозитории:
В дополнение к этому вы можете найти реальную виртуальную машину внутри встроенного сценариста, который я написал, также для Голанга. В этом случае язык сценариев анализируется и преобразуется в серию инструкций по байт -коде, которые выполняются виртуальной машиной. Подобно этому проекту, но операции по байт-коде более сложны и высокий уровень:
Если вы заинтересованы в компиляторах и переводчиках, как правило, вам также могут наслаждаться этими другими проектами: