يحتوي هذا المستودع على تنفيذ آثار افتراضية بسيطة ، إلى جانب برنامج تشغيل سيقرأ برنامجًا من ملف وتنفيذه عبر هذا الجهاز الظاهري.
بالإضافة إلى مترجم الجهاز الظاهري ستجد أيضًا:
هذا الجهاز الظاهري بالذات بسيط عن قصد ، ولكن على الرغم من أنه نأمل أن يتم تنفيذه بطريقة قابلة للقراءة. ("البساطة" هنا تعني أننا ندعم عددًا صغيرًا فقط من التعليمات ، وتسجلات وحدة المعالجة المركزية الافتراضية التي تمتلكها تخزين السلاسل والأعداد الصحيحة ، ولكن ليس قيم النقطة العائمة.) هذا الجهاز الظاهري المعينة يعتمد على السجل ، حيث يحتوي على عشرة سجلات يمكن استخدامها لتخزين الأوتار أو القيم الصحيح.
لأن المترجم و decompiler مكتوب في بيرل ، لا يحتاجون إلى علاج خاص.
يمكن بناء التفسير ، المكتوب في C ، مثل ذلك:
$ make
سيؤدي ذلك إلى توليد simple-vm embedded من محتويات SRC/.
إن تطبيق جهاز افتراضي أساسي ، مثل هذا الجهاز ، يمثل مشكلة مفهومة جيدًا:
case طويل.halt ، أو تعليمات exit .المضاعفات الرئيسية لكتابة الجهاز الظاهري هي:
goto repeat " وليس " 0x10 0x03 0x00 ". يحتوي هذا الجهاز الظاهري المعين على عدد قليل من البدائية ، ولكنه يشمل دعم الملصقات والحلقات والقفزات الشرطية والمكالمات والعوائد. هناك أيضًا مكدس يمكن استخدامه لتخزين القيم المؤقتة ، ويستخدم في معالجة call / ret .
ربما يكون التعامل مع الملصقات في هذا التنفيذ أمرًا جديرًا بالملاحظة ، لأن العديد من الأجهزة الافتراضية البسيطة/العرضية لا تتعامل معها على الإطلاق.
من أجل دعم القفز إلى الملصقات التي لم يتم تعريفها بالضرورة ، يحتفظ برنامج التحويل البرمجي الخاص بنا بقائمة جارية لجميع الملصقات (أي وجهات القفز المحتملة) وعندما تواجه تعليمات قفزة ، أو أي شيء آخر يشير إلى ملصق ، فإنه يخرج من العناصر النائبة ، مثل:
JUMP 0x0000x10 0x00 0x00 حيث يتم تعريف تعليمات القفز على أنها 0x10 .بعد اكتمال التجميع ، كان ينبغي اكتشاف جميع الأهداف ويكون المترجم حرًا في تحديث Bytecodes التي تم إنشاؤها لملء الوجهات المناسبة.
ملاحظة : في أجهزتنا الافتراضية ، تكون جميع القفزات مطلقة ، لذلك قد تبدو مثل "
JUMP 0x0123" بدلاً من "JUMP -4" أو "JUMP +30".
ملاحظة : ينطبق الشيء نفسه على التعليمات الأخرى التي تتعامل مع الملصقات ، مثل تخزين عنوان الملصق ، وإجراء مكالمة ، إلخ.
تم تصميم هذا الجهاز الظاهري بشكل أساسي كتجربة تعليمية ، ولكنه تم تصميمه مع وضع فكرة التضمين في الاعتبار.
يبلغ حجم الثنائي simple-vm القياسي ، والذي سيقرأ الرموز المفروضة من ملف وتفسيرها ، أقل من 40 ألفًا.
نظرًا لأن معالجة الرموز العادية الثنائية تتم معالجتها عبر جدول إرسال ، فمن الممكن أن تضيف برامج OpCshes الخاصة بالتطبيق الخاصة بك إلى النظام والتي من شأنها أن تسمح لك بتنفيذ البرامج المترجمة ، والفعالة ، والتي يمكنها الاتصال مرة أخرى إلى تطبيقك عندما يرغبون.
هناك مثال على تحديد رمز 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
الآن يمكننا تنفيذ هذه السلسلة من الرموز العليا:
./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 . ضع تلك الموجودة في الدليل ، وابدأ fuzzer الخاص بك:
mkdir samples/
cp examples/*.raw samples/
الآن لديك ./samples/ تحتوي على برامج تم تجميعها فقط. يمكنك بعد ذلك تحوير/تمييز تلك الأمثلة تحت سيطرة الفوزر مع سطر الأوامر مثل هذا:
export FUZZ=1
afl-fuzz -i ./samples/ -o results/ ./simple-vm @@ 16384
ملاحظة : هنا نقوم بتشغيل كل تشغيل لتنفيذ ما لا يزيد عن 16384 تعليمات. سيضمن ذلك عدم وجود برامج لا حصر لها.
قمنا بتعيين FUZZ 1 فقط لتعطيل استخدام وظيفة system() . والتي قد تزيل عن طريق الخطأ في دليل منزلك ، أو تنسيق محرك الأقراص ، أو أرسل لي تبرعًا!
يتوفر منفذ Golang لمتوجيم ومترجم آلات الافتراضية من المستودع التالي:
بالإضافة إلى ذلك ، يمكنك العثور على آثار افتراضية حقيقية داخل محرك البرمجة النصية المدمجة التي كتبتها ، أيضًا لـ Golang. في هذه الحالة ، يتم تحليل لغة البرمجة النصية وتحويلها إلى سلسلة من تعليمات Bytecode ، والتي يتم تنفيذها بواسطة جهاز ظاهري. على غرار هذا المشروع ، لكن عمليات Bytecode أكثر تعقيدًا وعالية المستوى:
إذا كنت مهتمًا بالمترجمين والمترجمين الفوريين ، فقد تستمتع عمومًا بهذه المشاريع الأخرى: