이 저장소에는 파일에서 프로그램을 읽고 해당 가상 머신을 통해 실행하는 드라이버와 함께 간단한 가상 머신의 구현이 포함되어 있습니다.
가상 머신 통역 외에도 다음을 찾을 수 있습니다.
이 특정 가상 머신은 의도적으로 간단하지만 그럼에도 불구하고 읽을 수있는 방식으로 구현됩니다. (여기서 "단순성"은 우리가 소수의 지침 만 지원하고 가상 CPU가 보유한 레지스터는 문자열과 정수를 저장하지만 부동 소수점 값을 저장할 수 있음을 의미합니다.)이 특정 가상 머신은 레지스터 기반이며 10 개의 레지스터는 문자열 또는 정수 값을 저장하는 데 사용할 수 있습니다.
컴파일러와 디 컴파일러는 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"처럼 보일 수 있습니다.
참고 : 레이블의 주소 저장, 호출 등과 같은 레이블을 처리하는 다른 지침에도 동일한 사항이 적용됩니다.
이 가상 기계는 주로 학습 경험으로 설계되었지만 임베딩을 염두에 두는 아이디어로 구축되었습니다.
파일에서 opcodes를 읽고 해석하는 표준 simple-vm Binary의 크기는 40k 미만입니다.
이진 오피 코드의 처리는 디스패치 테이블을 통해 처리되기 때문에 자신의 애플리케이션 관련 opcode를 시스템에 추가 할 수 있으며, 원하는 경우 소규모 컴파일되고 효율적인 프로그램을 실행할 수있는 시스템을 실행할 수 있습니다.
src/embedded.c 파일에 사용자 정의 Opcode를 정의하는 예가 있습니다. 이 예제는 사용자 정의 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 "를 저장 한 다음 등록 된 인쇄물을 저장하고 프로그램 시작으로 돌아갑니다.
실제로이 프로그램을 바이트 코드 실행으로 컴파일하려면 다음과 같습니다.
$ ./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
이제 우리는 해당 일련의 opcode를 실행할 수 있습니다.
./simple-vm ./examples/simple.raw
실행을 디버그하려면 실행하십시오.
DEBUG=1 ./simple-vm ./examples/simple.raw
이 저장소의 examples/ 서브 디렉토리 아래에 저장된 예가 더 있습니다. 파일 예제/quine.in은 다양한 기능의 좋은 예를 제공합니다.
AFL로 퍼지 테스트를 원한다면 꽤 직접적으로 찾아야합니다.
afl 자체를 설치하십시오.CC=afl-gcc Makefile 에서 설정하십시오.다음과 같은 각 번들 샘플을 컴파일 할 수 있습니다.
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 개 이하의 지침에 실행하는 데 캡을칩니다. 이것은 프로그램에 무한 루프가 없도록합니다.
system() 함수의 사용을 비활성화하기 위해 환경 변수 FUZZ 1 만 포함하도록 설정했습니다. 실수로 주택-디렉토리를 제거하거나 운전을 형식화하거나 기부금을 보낼 수 있습니다!
Virtual-Machine 컴파일러 및 통역사의 Golang 포트는 다음 저장소에서 제공됩니다.
그 외에도 내가 쓴 내장 된 스크립팅 엔진 내부에서 Golang을 위해 실제 가상 기계를 찾을 수 있습니다. 이 경우 스크립팅 언어가 구문 분석되어 일련의 바이트 코드 지침으로 변환되며, 이는 가상 시스템에 의해 실행됩니다. 이 프로젝트와 유사하지만 바이트 코드 작업은 더 복잡하고 높은 수준입니다.
컴파일러와 통역사에 관심이 있다면 일반적으로 이러한 다른 프로젝트도 즐길 수 있습니다.