
نعم ، نعم ، إنه "البايت" وهو باللغة الهندية (وليس الهندية). سأبدأ بالترتيب. في الآونة الأخيرة هنا ، على Habré ، بدأت تظهر مقالات حول bytecode. ذات مرة ، كنت أستمتع بكتابة أنظمة Fort. بالطبع ، في المجمع. كانوا 16 بت. أنا لم مبرمجة على x86-64. حتى مع 32 لا يمكن أن تلعب. إذن جاء الفكر - لم لا؟ لماذا لا تثير الحصن 64 بت ، وحتى مع الرمز الفرعي؟ نعم ، وعلى نظام Linux ، حيث لم أكتب نظام أي شيء أيضًا.
لدي خادم منزلي مع نظام Linux. بشكل عام ، غوغل قليلاً واكتشفت أن المجمع على Linux يسمى GAS ، والأمر as. أنا أتصل عبر SSH بالخادم ، وكتابته - نعم! لدي بالفعل تثبيته. ما زلت بحاجة إلى رابط ، اكتب ld - نعم! لذلك ، ومحاولة كتابة شيء مثير للاهتمام في المجمع. بدون حضارة ، فقط غابة ، مثل الهنود الحقيقيين :) بدون بيئة تطوير ، فقط سطر الأوامر وقائد منتصف الليل. سيكون المحرر هو Nano ، والذي يتم تعليقه على F4 في mc. كيف تغني فرقة "زيرو"؟ الهندي الحقيقي يحتاج إلى شيء واحد فقط ... ماذا يحتاج الهندي الحقيقي؟ بالطبع ، مصحح أخطاء. نحن اكتب gdb - هو! حسنًا ، اضغط على Shift + F4 ، وانتقل!
العمارة
بالنسبة للمبتدئين ، دعونا نقرر الهندسة المعمارية. مع أعماق البت المحددة بالفعل ، 64 بت. في تطبيقات Fort الكلاسيكية ، تكون شريحة البيانات والرمز هي نفسها. ولكن ، سنحاول القيام بذلك بشكل صحيح. سيكون لدينا فقط الكود في مقطع الكود ، البيانات الموجودة في مقطع البيانات. نتيجة لذلك ، حصلنا على نواة للنظام الأساسي ورمز بايت مستقل تمامًا عن النظام الأساسي.
دعونا نحاول جعل أسرع آلة بايت مكدسة (ولكن بدون JIT). لذلك ، سيكون لدينا جدول يحتوي على 256 عنوانًا - عنوان لكل أمر بايت. أقل من أي شيء - فحص إضافي ، هذا هو 1-2 تعليمات المعالج. ونحن بحاجة بسرعة ، دون حل وسط.
أكوام
عادة ، في تطبيقات Fort ، يتم استخدام مكدس إرجاع المعالج (* SP) كمكدس بيانات ، ويتم تطبيق مكدس إرجاع نظام الحصن باستخدام وسائل أخرى. في الواقع ، سيتم تكديس الجهاز الخاص بنا ، والعمل الرئيسي هو على كومة البيانات. لذلك ، دعونا نفعل الشيء نفسه - RSP سيكون مكدس البيانات. حسنًا ، دع رصة الإرجاع تكون RBP ، والتي تعمل أيضًا افتراضيًا مع مقطع الرصة. وبالتالي ، سيكون لدينا ثلاث شرائح للذاكرة: مقطع شفرة ، مقطع بيانات ومقطع مكدس (سيكون به كومة بيانات ومكدس إرجاع).
سجلات
أذهب إلى وصف سجلات x86-64 ، و عفوا! يوجد ما يصل إلى 8 سجلات للأغراض العامة الإضافية (R8 - R16) ، مقارنة مع أوضاع 32 أو 16 بت. ليس سيئًا ...
قررت بالفعل أنها ستحتاج إلى RSP و RBP. لا تزال بحاجة إلى مؤشر (عداد) لأوامر bytecode. من العمليات على هذا السجل ، هناك حاجة فقط لقراءة الذاكرة. تعد السجلات الرئيسية (RAX ، RBX ، RCX ، RDX ، RSI ، RDI) أكثر مرونة ، عالمية ، معهم العديد من الأوامر الخاصة. ستكون مفيدة لنا في العديد من المهام ، وبالنسبة لعداد تعليمات bytecode ، نأخذ واحدة من السجلات الجديدة بالنسبة لي ، فليكن R8.
لنبدأ
ليس لدي أي خبرة في البرمجة بلغة Linux في لغة التجميع. لذلك ، بالنسبة للمبتدئين ، سنجد "Hello، world" النهائي لفهم كيفية بدء البرنامج وعرض النص. بشكل غير متوقع بالنسبة لي ، لقد وجدت خيارات مع بناء جملة غريب حيث يتم إعادة ترتيب المصدر والمتلقي. كما اتضح ، هذا هو بناء جملة AT & T ، وهو مكتوب بشكل أساسي تحت GAS عليه. ولكن يتم دعم خيار بناء جملة آخر ، يطلق عليه بناء جملة Intel. أفكر ، قررت أن أستخدمها كلها. حسنا ، اكتب في بداية .intel_syntax noprefix.
قم بتجميع وتنفيذ "Hello، world" للتأكد من أن كل شيء يعمل. من خلال قراءة التعليمات والتجارب ، بدأت في استخدام الأمر التالي للترجمة:
$ as fort.asm -o fort.o -g -ahlsm >list.txt
هنا ، يشير رمز التبديل -o إلى ملف النتائج ، ويرشد رمز التبديل -g إلى إنشاء معلومات تصحيح الأخطاء ، ويقوم رمز التبديل -ahlsm بتعيين تنسيق القائمة. وأحتفظ بالإخراج في القائمة ، حيث يمكنك رؤية الكثير من الأشياء المفيدة. أعترف ، في بداية العمل ، لم أقم بإدخال القائمة ، ولم أحدد حتى رمز التبديل -g. بدأت في استخدام رمز التبديل -g بعد أول استخدام لمصحح الأخطاء ، وبدأت في عمل القائمة بعد ظهور وحدات الماكرو في الكود :)
بعد ذلك ، نستخدم الرابط ، لكن هنا ليس أكثر بساطة:
$ ld forth.o -o forth
حسنا ، ركض!
$ ./forth
Hello, world!
إنه يعمل.
كان هذا هو أول ما يشبه. (في الواقع هو "يا عالم!" ، بالطبع) .intel_syntax noprefix .section .data msg: .ascii "Hello, world!\n" len = . - msg # len .section .text .global _start # _start: mov eax, 4 # № 4 — sys_write mov ebx, 1 # № 1 — stdout mov ecx, OFFSET FLAT:msg # mov edx, len # int 0x80 # mov eax, 1 # № 1 — sys_exit xor ebx, ebx # 0 int 0x80 #
بالمناسبة ، بعد ذلك بقليل اكتشفت أنه في الفترة من 86 إلى 64 عامًا ، كان من الصحيح استخدام syscall لإجراء مكالمة نظام ، بدلاً من int 0x80. تعتبر استدعاء 0x80 قديمة لهذه البنية ، على الرغم من أنها مدعومة.
لقد تم البدء ، والآن ...
دعنا نذهب!
أنه سيكون هناك بعض التفاصيل على الأقل ، سوف نكتب شفرة أمر بايت واحد. فليكن كلمة Fort "0" ، مع وضع 0 أعلى المكدس:
bcmd_num0: push 0 jmp _next
في الوقت الذي يتم فيه تنفيذ هذا الأمر ، يشير R8 بالفعل إلى أمر البايت التالي. من الضروري قراءتها ، وزيادة R8 ، وتحديد العنوان القابل للتنفيذ بواسطة رمز أمر البايت ، ونقل التحكم إليه.
ولكن ... ما عمق البت سيكون جدول عنوان بايت الأمر؟ ثم اضطررت للحفر في نظام قيادة x86-64 الجديد بالنسبة لي. للأسف ، لم أجد أوامر تسمح لك بالذهاب إلى الإزاحة في الذاكرة. لذلك ، إما حساب العنوان ، أو أن العنوان سيكون جاهزًا - 64 بت. ليس هناك وقت لنا لحساب ، وهو ما يعني - 64 بت. في هذه الحالة ، سيكون حجم الجدول 256 * 8 = 4096 بايت. حسنًا ، أخيرًا ، قم بترميز استدعاء _next:
_next: movzx rcx, byte ptr [r8] inc r8 jmp [bcmd + rcx*8] # bcmd - -
ليس سيئًا ، يبدو لي ... لا يوجد سوى ثلاثة تعليمات للمعالج ، عند التبديل من أمر بايت واحد إلى آخر.
في الواقع ، لم تكن هذه الأوامر سهلة بالنسبة لي. اضطررت إلى الخوض في نظام الأوامر 0x86-64 مرة أخرى والعثور على أمر MOVZX جديد بالنسبة لي. في الواقع ، يحول هذا الأمر قيمة 8 أو 16 أو 32 بت إلى سجل 64 بت. هناك نوعان مختلفان من هذا الأمر: غير موقعين ، حيث يتم تعبئة الأرقام العليا بالأصفار ، والأرقام الموقعة هي MOVSX. في متغير الإشارة ، تتوسع الإشارة ، أي بالنسبة للأرقام الموجبة ، ستذهب الأصفار إلى الأرقام العليا والأرقام السالبة. هذا الخيار مفيد لنا أيضًا لأمر بايت المضاء.
بالمناسبة ، هل هذا الخيار هو الأسرع؟ ربما شخص ما سوف توحي أسرع؟
حسنًا ، لدينا الآن آلة بايت يمكن تشغيلها عبر سلسلة من أوامر البايت وتنفيذها. من الضروري اختباره في الممارسة العملية ، لإجبار على تنفيذ فريق واحد على الأقل. لكن أي واحد؟ صفر على المكدس؟ ولكن هنا لا تعرف النتيجة ، فإذا لم تنظر إلى المكدس أسفل مصحح الأخطاء ... ولكن في حالة بدء تشغيل البرنامج ، يمكن إكماله :)
نكتب أمرًا وداعًا يكمل البرنامج ويكتب عنه ، خاصةً لأن لدينا "عالم رائع!".
bcmd_bye: mov eax, 4 # № 4 — sys_write mov ebx, 1 # № 1 — stdout mov ecx, offset msg_bye # mov edx, msg_bye_len # int 0x80 # mov eax, 1 # № 1 — sys_exit mov ebx, 0 # 0 int 0x80 #
الشيء الوحيد المتبقي هو إنشاء جدول عناوين أمر البايت وتهيئة السجلات وبدء تشغيل آلة البايت. لذلك ... هناك 256 قيمة في الجدول ، وهناك أمرين. ماذا في الخلايا الأخرى؟
سيكون للباقي رمز تشغيل غير صالح. لكن ، لا يمكنك إجراء فحص عليه ، فهذه فرق إضافية ، ولدينا الآن ثلاثة فرق ، وسيصبح العدد خمسة. لذلك ، سوف نجعل مثل هذا الأمر روتينًا - فريق سيء. أولاً ، نملأ الجدول بأكمله لذلك ، ثم نبدأ في شغل الخلايا بأوامر مفيدة. اسمح لفريق سيء بالحصول على الرمز 0x00 ، وسيحصل الفريق على الرمز 0x01 ، وسيحصل الرمز "0" على الرمز 0x02 ، بمجرد كتابته بالفعل. سيقوم الفريق السيئ في الوقت الحالي بالقيام بنفس الشيء مثل وداعًا ، فقط مع رمز إتمام ونص مختلف (سأضعه في المفسد ، ونفس الشيء مع وداعًا):
bcmd_bad bcmd_bad: mov eax, 4 # № 4 — sys_write mov ebx, 1 # № 1 — stdout mov ecx, offset msg_bad_byte # mov edx, msg_bad_byte_len # int 0x80 # mov eax, 1 # № 1 — sys_exit mov ebx, 1 # 1 int 0x80 #
ارسم الآن جدول العناوين. للراحة ، سنضع ثمانية صفوف في كل صف ، وسيكون هناك 16 صفًا ، والجدول كبير جدًا في الحجم:
بايت عنوان جدول الأوامر bcmd: .quad bcmd_bad, bcmd_bye, bcmd_num0, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad
نكتب نص برنامج بايت. للقيام بذلك ، قم بتعيين رموز الأوامر لمتغيرات المجمّع. سيكون لدينا الاتفاقيات التالية:
- تبدأ عناوين تنفيذ أوامر البايت على bcmd_
- سيتم تخزين رموز الأوامر نفسها في متغيرات تبدأ بـ b_
وبالتالي ، سيكون نص برنامج البايت مثل هذا:
start: .byte b_bye
قم بتعريف حجم مكدس البيانات كما stack_size. فليكن 1024 حتى الآن. في التهيئة ، سنفعل RBP = RSP - stack_size.
في الواقع ، نحصل على رمز البرنامج هذا (forth.asm) .intel_syntax noprefix stack_size = 1024 .section .data msg_bad_byte: .ascii "Bad byte code!\n" msg_bad_byte_len = . - msg_bad_byte # len msg_bye: .ascii "bye!\n" msg_bye_len = . - msg_bye msg_hello: .ascii "Hello, world!\n" msg_hello_len = . - msg_hello bcmd: .quad bcmd_bad, bcmd_bye, bcmd_num0, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad start: .byte b_bye .section .text .global _start # _start: mov rbp, rsp sub rbp, stack_size lea r8, start jmp _next _next: movzx rcx, byte ptr [r8] inc r8 jmp [bcmd + rcx*8] b_bad = 0x00 bcmd_bad: mov eax, 4 # № 4 — sys_write mov ebx, 1 # № 1 — stdout mov ecx, offset msg_bad_byte # mov edx, msg_bad_byte_len # int 0x80 # mov eax, 1 # № 1 — sys_exit mov ebx, 1 # 1 int 0x80 # b_bye = 0x01 bcmd_bye: mov eax, 4 # № 4 — sys_write mov ebx, 1 # № 1 — stdout mov ecx, offset msg_bye # mov edx, msg_bye_len # int 0x80 # mov eax, 1 # № 1 — sys_exit mov ebx, 0 # 0 int 0x80 # b_num0 = 0x02 bcmd_num0: push 0 jmp _next
ترجمة ، تشغيل:
$ as fort.asm -o fort.o -g -ahlsm >list.txt
$ ld forth.o -o forth
$ ./forth
bye!
إنه يعمل! تم إطلاق برنامج bytecode الأول من بايت واحد :)
بالطبع ، سيكون هذا هو الحال إذا تم كل شيء بشكل صحيح. وإذا لم يكن الأمر كذلك ، فمن المرجح أن تكون النتيجة هي:
$ ./forth
بالطبع ، هناك خيارات أخرى ممكنة ، لكني صادفت ذلك في أغلب الأحيان. ونحن بحاجة إلى مصحح أخطاء.
كلمات مصحح الأخطاءكما سبق ذكره ، اعتدت GDB. هذا هو مصحح أخطاء قوية جدا ، ولكن مع واجهة سطر الأوامر. تشغيله بسيط للغاية:
$ gdb ./forth GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1 Copyright (C) 2016 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from ./forth...done. (gdb)
بعد ذلك ، عن طريق إدخال الأوامر ، نقوم بتصحيح الأخطاء. كان لدي ساعات كافية للعثور على بعض الأوامر اللازمة ومعرفة كيفية استخدامها لتصحيح الأخطاء. ها هم:
ب <label> - تعيين نقطة توقف
ل <label> - عرض شفرة المصدر
ص - بدء أو إعادة تشغيل البرنامج
الأشعة تحت الحمراء - المعالج يسجل حالة
ق - الخطوة
بالمناسبة ، تذكر أنك بحاجة إلى ترجمة البرنامج مع رمز التبديل -g؟ خلاف ذلك ، لن تكون العلامات والكود المصدري متاحًا. في هذه الحالة ، سيكون من الممكن تصحيح الأخطاء فقط عن طريق التعليمات البرمجية المفككة واستخدام العناوين في الذاكرة. بالطبع ، نحن هنود ، لكن ليس بالقدر نفسه ...
ولكن بطريقة ما البرنامج لا يفعل سوى القليل جدا. نقول فقط "مرحباً" لها ، وتقول على الفور "وداعاً!". لنجعل العالم الحقيقي "Hello Hello!" على bytecode. للقيام بذلك ، ضع العنوان وطول السلسلة على المكدس ، ثم قم بتنفيذ الأمر الذي يعرض السلسلة ، ثم الأمر bye. للقيام بكل هذا ، هناك أوامر جديدة مطلوبة: اكتب لإخراج السلسلة ، وأضاءت لوضع عنوان السلسلة وطولها. أولا نكتب نوع ، واسمحوا رمزها يكون 0x80. نحن ، مرة أخرى ، نحتاج إلى هذه الشفرة مع استدعاء sys_write:
b_type = 0x80 bcmd_type: mov eax, 4 # № 4 — sys_write mov ebx, 1 # № 1 — stdout pop rdx pop rcx push r8 int 0x80 # pop r8 jmp _next
هنا نأخذ العنوان وطول السلسلة من مكدس البيانات باستخدام أوامر POP. يمكن استدعاء int 0x80 تغيير سجل R8 ، لذلك نحن حفظه. لم نفعل ذلك من قبل لأن البرنامج انتهى. محتويات هذه السجلات لم تهتم. الآن هذا أمر بايت عادي ، وبعد ذلك يستمر تنفيذ شفرة البايت ، وعليك أن تتصرف بنفسك.
الآن لنكتب الضوء. سيكون هذا الفريق الأول لدينا مع المعلمات. بعد البايت الذي يحتوي على رمز هذا الأمر ، سيكون هناك بايت تحتوي على الرقم الذي ستضعه على المكدس. السؤال الذي يطرح نفسه على الفور - ما هو عمق البت المطلوب هنا؟ لوضع أي رقم ، تحتاج 64 بت. لكن ، في كل مرة يشغل فيها الأمر 9 بايت ، ما الذي سيضع رقمًا واحدًا؟ لذلك فقدنا الاكتناز ، واحدة من الخصائص الرئيسية ل bytecode ، ورمز الحصن أيضًا ...
الحل بسيط - سنقوم بعمل عدة أوامر لأعماق البت المختلفة. ستكون هذه lit8 و lit16 و lit32 و lit64. بالنسبة للأعداد الصغيرة ، سوف نستخدم lit8 و lit16 ، للأعداد الأكبر - lit32 و lit64. غالبًا ما يتم استخدام الأرقام الصغيرة ، وسيكون هناك أقصر الأوامر بالنسبة إليهم ، والتي تأخذ وحدتي بايت. ليس سيئًا! .. سنجعل أكواد هذه الأوامر من 0x08 إلى 0x0B.
b_lit8 = 0x08 bcmd_lit8: movsx rax, byte ptr [r8] inc r8 push rax jmp _next b_lit16 = 0x09 bcmd_lit16: movsx rax, word ptr [r8] add r8, 2 push rax jmp _next b_lit32 = 0x0A bcmd_lit32: movsx rax, dword ptr [r8] add r8, 4 push rax jmp _next b_lit64 = 0x0B bcmd_lit64: mov rax, [r8] add r8, 8 push rax jmp _next
نحن هنا نستخدم أمر MOVSX - هذا هو الإصدار الأيقوني لأمر MOVZX المعروف بالفعل لدينا. R8 لدينا عداد قيادة بايت. نقوم بتحميل قيمة الحجم المطلوب عليها ، وننقلها إلى الأمر التالي ، ونضع القيمة المحولة إلى 64 بت على المكدس.
لا تنس إضافة عناوين الفرق الجديدة في الجدول إلى المواضع المطلوبة.هذا كل شيء جاهز لكتابة برنامجك الأول "Hello، world!" على bytecode لدينا. دعونا نعمل مع المترجم! :)
start: .byte b_lit64 .quad msg_hello .byte b_lit8 .byte msg_hello_len .byte b_type .byte b_bye
نحن نستخدم أمرين مختلفين: lit64 ، والذي من شأنه أن يضع عنوان السلسلة على المكدس ، و lit8 ، حيث نضع طول المكدس. بعد ذلك ، نقوم بتنفيذ أمرين بايت آخرين: type and bye.
ترجمة ، تشغيل:
$ as fort.asm -o fort.o -g -ahlsm >list.txt $ ld forth.o -o forth $ ./forth Hello, world! bye!
حصل على bytecode لدينا! هذه هي النتيجة التي يجب أن تكون إذا كان كل شيء طبيعيًا.
المصدر الكامل .intel_syntax noprefix stack_size = 1024 .section .data msg_bad_byte: .ascii "Bad byte code!\n" msg_bad_byte_len = . - msg_bad_byte # len msg_bye: .ascii "bye!\n" msg_bye_len = . - msg_bye msg_hello: .ascii "Hello, world!\n" msg_hello_len = . - msg_hello bcmd: .quad bcmd_bad, bcmd_bye, bcmd_num0, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x00 .quad bcmd_lit8, bcmd_lit16, bcmd_lit32, bcmd_lit64, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x10 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x20 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x30 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x40 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x60 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_type, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x80 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad start: .byte b_lit64 .quad msg_hello .byte b_lit8 .byte msg_hello_len .byte b_type .byte b_bye .section .text .global _start # _start: mov rbp, rsp sub rbp, stack_size lea r8, start jmp _next _next: movzx rcx, byte ptr [r8] inc r8 jmp [bcmd + rcx*8] b_bad = 0x00 bcmd_bad: mov eax, 4 # № 4 — sys_write mov ebx, 1 # № 1 — stdout mov ecx, offset msg_bad_byte # mov edx, msg_bad_byte_len # int 0x80 # mov eax, 1 # № 1 — sys_exit mov ebx, 1 # 1 int 0x80 # b_bye = 0x01 bcmd_bye: mov eax, 4 # № 4 — sys_write mov ebx, 1 # № 1 — stdout mov ecx, offset msg_bye # mov edx, msg_bye_len # int 0x80 # mov eax, 1 # № 1 — sys_exit mov ebx, 0 # 0 int 0x80 # b_num0 = 0x02 bcmd_num0: push 0 jmp _next b_lit8 = 0x08 bcmd_lit8: movsx rax, byte ptr [r8] inc r8 push rax jmp _next b_lit16 = 0x09 bcmd_lit16: movsx rax, word ptr [r8] add r8, 2 push rax jmp _next b_lit32 = 0x0A bcmd_lit32: movsx rax, dword ptr [r8] add r8, 4 push rax jmp _next b_lit64 = 0x0B bcmd_lit64: mov rax, [r8] add r8, 8 push rax jmp _next b_type = 0x80 bcmd_type: mov eax, 4 # № 4 — sys_write mov ebx, 1 # № 1 — stdout pop rdx pop rcx push r8 int 0x80 # pop r8 jmp _next
لكن الاحتمالات لا تزال بدائية للغاية ، لا يمكنك وضع شرط ، دورة.
كيف يكون ذلك مستحيلاً؟ يمكنك ، كل شيء في أيدينا! دعونا نفعل هذا الخط في الحلقة 10 مرات. سيتطلب هذا الأمر أمرًا بفرع الفرع الشرطي ، بالإضافة إلى جزء من حساب المكدس: أمر يخفض القيمة على المكدس بمقدار 1 (في الحصن "1-") وأمر الازدواج في الرأس ("dup").
مع الحساب ، كل شيء بسيط ، لن أعلق حتى:
b_dup = 0x18 bcmd_dup: push [rsp] jmp _next b_wm = 0x20 bcmd_wm: decq [rsp] jmp _next
الآن قفزة مشروطة. بالنسبة للمبتدئين ، لنجعل المهمة أكثر بساطة - انتقال غير مشروط. بوضوح ، تحتاج فقط إلى تغيير قيمة التسجيل R8. أول ما يتبادر إلى الذهن هو أمر البايت ، يليه معلمة - عنوان النقل هو 64 بت. مرة أخرى تسعة بايت. هل نحتاج إلى هذه البايتات التسعة؟ تحدث التحولات عادة على مسافات قصيرة ، غالبًا في بضع مئات من البايتات. لذلك ، سوف نستخدم ليس العنوان ، ولكن الإزاحة!
عمق قليلا؟ في كثير من الحالات ، ستكون 8 بتات (127 للأمام / للخلف) كافية ، لكن في بعض الأحيان لن يكون هذا كافياً. لذلك ، سنفعل الشيء نفسه كما هو الحال مع الأمر lit ، سنقوم بعمل خيارين - 8 و 16 رقمًا ، ستكون رموز الأوامر هي 0x10 و 0x11:
b_branch8 = 0x10 bcmd_branch8: movsx rax, byte ptr [r8] add r8, rax jmp _next b_branch16 = 0x11 bcmd_branch16: movsx rax, word ptr [r8] add r8, rax jmp _next
الآن الانتقال المشروط سهل التنفيذ. إذا كانت الكومة 0 ، فانتقل إلى _next ، وإذا لم تكن كذلك ، فانتقل إلى الأمر branch!
b_qbranch8 = 0x12 bcmd_qbranch8: pop rax or rax, rax jnz bcmd_branch8 inc r8 jmp _next b_qbranch16 = 0x13 bcmd_qbranch16: pop rax or rax, rax jnz bcmd_branch16 add r8, 2 jmp _next
الآن لدينا كل شيء لعمل حلقة: start: .byte b_lit8 .byte 10 # # m0: .byte b_lit64 .quad msg_hello .byte b_lit8 .byte msg_hello_len .byte b_type .byte b_wm .byte b_dup .byte b_qbranch8 .byte m0 - . .byte b_bye
أول أمرين - وضعنا عداد الحلقة على المكدس. بعد ذلك ، اطبع السلسلة Hello. ثم نطرح 1 من العداد ونكرره وننفذ (أو لا ننفذ) الانتقال. مطلوب الأمر الازدواجية لأن أمر الفرع الشرطي يأخذ القيمة من أعلى المكدس. الانتقال هنا هو ثمانية بت ، لأن المسافة ليست سوى بضع بايت.نضع عناوين الأوامر الجديدة في جدول ، نجمع وننفذ.سوف أضعها في جناح ، وإلا فقد أصبح برنامجنا مطولًا) $ as fort.asm -o fort.o -g -ahlsm >list.txt $ ld forth.o -o forth $ ./forth Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! bye!
حسنا ، يمكننا أن نفعل بالفعل الظروف ودورات!المصدر الكامل .intel_syntax noprefix stack_size = 1024 .section .data msg_bad_byte: .ascii "Bad byte code!\n" msg_bad_byte_len = . - msg_bad_byte # len msg_bye: .ascii "bye!\n" msg_bye_len = . - msg_bye msg_hello: .ascii "Hello, world!\n" msg_hello_len = . - msg_hello bcmd: .quad bcmd_bad, bcmd_bye, bcmd_num0, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x00 .quad bcmd_lit8, bcmd_lit16, bcmd_lit32, bcmd_lit64, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_branch8, bcmd_branch16, bcmd_qbranch8, bcmd_qbranch16, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x10 .quad bcmd_dup, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_wm, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x20 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x30 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x40 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x60 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_type, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x80 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad start: .byte b_lit8 .byte 10 # # m0: .byte b_lit64 .quad msg_hello .byte b_lit8 .byte msg_hello_len .byte b_type .byte b_wm .byte b_dup .byte b_qbranch8 .byte m0 - . .byte b_bye .section .text .global _start # _start: mov rbp, rsp sub rbp, stack_size lea r8, start jmp _next _next: movzx rcx, byte ptr [r8] inc r8 jmp [bcmd + rcx*8] b_bad = 0x00 bcmd_bad: mov eax, 4 # № 4 — sys_write mov ebx, 1 # № 1 — stdout mov ecx, offset msg_bad_byte # mov edx, msg_bad_byte_len # int 0x80 # mov eax, 1 # № 1 — sys_exit mov ebx, 1 # 1 int 0x80 # b_bye = 0x01 bcmd_bye: mov eax, 4 # № 4 — sys_write mov ebx, 1 # № 1 — stdout mov ecx, offset msg_bye # mov edx, msg_bye_len # int 0x80 # mov eax, 1 # № 1 — sys_exit mov ebx, 0 # 0 int 0x80 # b_num0 = 0x02 bcmd_num0: push 0 jmp _next b_lit8 = 0x08 bcmd_lit8: movsx rax, byte ptr [r8] inc r8 push rax jmp _next b_lit16 = 0x09 bcmd_lit16: movsx rax, word ptr [r8] add r8, 2 push rax jmp _next b_lit32 = 0x0A bcmd_lit32: movsx rax, dword ptr [r8] add r8, 4 push rax jmp _next b_lit64 = 0x0B bcmd_lit64: mov rax, [r8] add r8, 8 push rax jmp _next b_type = 0x80 bcmd_type: mov eax, 4 # № 4 — sys_write mov ebx, 1 # № 1 — stdout pop rdx pop rcx push r8 int 0x80 # pop r8 jmp _next b_dup = 0x18 bcmd_dup: push [rsp] jmp _next b_wm = 0x20 bcmd_wm: decq [rsp] jmp _next b_branch8 = 0x10 bcmd_branch8: movsx rax, byte ptr [r8] add r8, rax jmp _next b_branch16 = 0x11 bcmd_branch16: movsx rax, word ptr [r8] add r8, rax jmp _next b_qbranch8 = 0x12 bcmd_qbranch8: pop rax or rax, rax jnz bcmd_branch8 inc r8 jmp _next b_qbranch16 = 0x13 bcmd_qbranch16: pop rax or rax, rax jnz bcmd_branch16 add r8, 2 jmp _next
ولكن حتى يفتقد الجهاز البايت وظيفة أخرى مهمة للغاية. لا يمكننا استدعاء آخر من الرمز البريدي. ليس لدينا ما يسمى الإجراءات ، الإجراءات ، إلخ. وفي الحصن ، بدون هذا ، لا يمكننا استخدام كلمات غير كلمات kernel في بعض الكلمات.نأتي العمل إلى النهاية. هنا لأول مرة نحتاج إلى كومة من العائدات. هناك حاجة إلى أمرين - الأمر call وأمر الإرجاع (call and exit).يقوم الأمر call ، من حيث المبدأ ، بالشيء نفسه الذي يقوم به الفرع - بنقل التحكم إلى جزء آخر من الرمز الفرعي. ولكن ، بخلاف الفرع ، لا تزال بحاجة إلى حفظ عنوان المرسل في مكدس الإرجاع حتى تتمكن من العودة ومتابعة التنفيذ. هناك فرق آخر - مثل هذه المكالمات يمكن أن تحدث على مسافات أكبر بكثير. لذلك ، نجعل الأمر call في شبه الفرع ، ولكن في ثلاثة إصدارات - 8 و 16 و 32 بت. b_call8 = 0x0C bcmd_call8: movsx rax, byte ptr [r8] sub rbp, 8 inc r8 mov [rbp], r8 add r8, rax jmp _next b_call16 = 0x0D bcmd_call16: movsx rax, word ptr [r8] sub rbp, 8 add r8, 2 mov [rbp], r8 add r8, rax jmp _next b_call32 = 0x0E bcmd_call32: movsx rax, dword ptr [r8] sub rbp, 8 add r8, 4 mov [rbp], r8 add r8, rax jmp _next
كما ترون ، هنا ، على عكس التحولات ، تتم إضافة 3 فرق. يقوم أحدهم بإعادة ترتيب R8 إلى أمر البايت التالي ، ويقوم المخزنان المتبقيان بتخزين القيمة المستلمة في مكدس الإرجاع. بالمناسبة ، حاولت هنا ألا أضع إرشادات المعالج على بعضها البعض جنبًا إلى جنب ، حتى يتمكن ناقل المعالج من تنفيذ الأوامر بالتوازي. لكنني لا أعرف مقدار تأثير ذلك. إذا رغبت في ذلك ، يمكنك التحقق من الاختبارات.يجب أن يؤخذ في الاعتبار أن تكوين وسيطة لأمر call مختلف بعض الشيء عن الفرع. بالنسبة للفرع ، يتم حساب الإزاحة كالفرق بين عنوان الفرع وعنوان البايت الذي يتبع الأمر البايت. ولأمر call ، هذا هو الفرق بين عنوان القفز وعنوان الأمر التالي. لماذا هذا مطلوب؟
ينتج عن ذلك عدد أقل من إرشادات المعالج.الآن أمر الإرجاع. في الواقع ، فإن وظيفتها هي استعادة R8 من مكدس الإرجاع ونقل التحكم إلى الجهاز البايت بشكل إضافي: b_exit = 0x1F bcmd_exit: mov r8, [rbp] add rbp, 8 jmp _next
سيتم استخدام هذه الأوامر في كثير من الأحيان ، ويجب تحسينها إلى الحد الأقصى. يشغل أمر خروج البايت ثلاثة تعليمات للجهاز. هل من الممكن تقليل شيء هنا؟ اتضح أنك تستطيع! يمكنك ببساطة إزالة أمر الانتقال :)للقيام بذلك ، ضعه أعلى نقطة إدخال الجهاز بايت _next: b_exit = 0x1F bcmd_exit: mov r8, [rbp] add rbp, 8 _next: movzx rcx, byte ptr [r8] inc r8 jmp [bcmd + rcx*8]
بالمناسبة ، يجب وضع الأوامر الأكثر استخدامًا والأكثر استخدامًا (على سبيل المثال ، مثل call) بالقرب من البايت ، بحيث يمكن للمترجم أن يشكل أمر قفزة قصيرة. هذا مرئي بوضوح في القائمة. هنا مثال. 262 0084 490FBE00 bcmd_lit8: movsx rax, byte ptr [r8] 263 0088 49FFC0 inc r8 264 008b 50 push rax 265 008c EB90 jmp _next 266 267 b_lit16 = 0x09 268 008e 490FBF00 bcmd_lit16: movsx rax, word ptr [r8] 269 0092 4983C002 add r8, 2 270 0096 50 push rax 271 0097 EB85 jmp _next 272 273 b_lit32 = 0x0A 274 0099 496300 bcmd_lit32: movsx rax, dword ptr [r8] 275 009c 4983C004 add r8, 4 276 00a0 50 push rax 277 00a1 E978FFFF jmp _next 277 FF 278
هنا ، على الخط 265 و 271 ، يأخذ الأمر jmp 2 بايت لكل منهما ، وفي السطر 277 ، يتم تجميع نفس الأمر بالفعل إلى 5 بايت ، لأن مسافة الانتقال تجاوزت طول الأمر القصير.لذلك ، يتم إعادة ترتيب أوامر البايت مثل السيئة ، وداعا ، والنوع ، ومثل الدعوة ، فرع ، مضاءة أقرب. لسوء الحظ ، لا يوجد الكثير الذي يمكن أن يصلح في انتقال 127 بايت.نضيف أوامر جديدة إلى جدول عناوين الأوامر وفقًا لرموزها.لذلك ، لدينا الآن تحد وعودة ، وسوف نختبرها! للقيام بذلك ، حدد خط الطباعة في إجراء منفصل ، وسوف نسميها في حلقة مرتين. ويتم تقليل عدد مرات تكرار الدورة إلى ثلاثة. start: .byte b_lit8 .byte 3 # # m0: .byte b_call16 .word sub_hello - . - 2 .byte b_call16 .word sub_hello - . - 2 .byte b_wm .byte b_dup .byte b_qbranch8 .byte m0 - . .byte b_bye sub_hello: .byte b_lit64 .quad msg_hello .byte b_lit8 .byte msg_hello_len .byte b_type .byte b_exit
يمكن استخدام Call8 هنا ، لكنني قررت استخدام call16 باعتباره الأكثر استخدامًا. تُطرح القيمة 2 نظرًا لخصائص حساب عنوان أمر بايت الاستدعاء الذي كتبت عنه. بالنسبة إلى call8 ، سيتم خصم 1 هنا من أجل call32 ، على التوالي ، 4. نحن نجمعونطلب: $ as forth.asm -o forth.o -g -ahlsm>list.txt $ ld forth.o -o forth $ ./forth Hello, world! Bad byte code!
عفوًا ... كما يقولون ، حدث خطأ ما :) حسنًا ، نطلق GDB ونرى ما يحدث هناك. لقد قمت بتعيين نقطة توقف فورًا على bcmd_exit ، لأنه من الواضح أن استدعاء sub-hello يمر ، وأن نص الإجراء ينفذ ... تم إطلاقه ... ولم يصل البرنامج إلى نقطة الإيقاف هذه. على الفور كان هناك شك في وجود رمز أمر بايت. وبالفعل ، كان السبب فيه. b_exit قمت بتعيين القيمة 0x1f ، وتم وضع العنوان نفسه في رقم خلية الجدول 0x17. حسنًا ، سأقوم بتصحيح قيمة b_exit إلى 0x17 وأعد المحاولة: $ as forth.asm -o forth.o -g -ahlsm>list.txt $ ld forth.o -o forth $ ./forth Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! Hello, world! bye!
بالضبط ست مرات تحية ، وداعا مرة واحدة. كما ينبغي أن يكون :)المصدر الكامل .intel_syntax noprefix stack_size = 1024 .section .data msg_bad_byte: .ascii "Bad byte code!\n" msg_bad_byte_len = . - msg_bad_byte # len msg_bye: .ascii "bye!\n" msg_bye_len = . - msg_bye msg_hello: .ascii "Hello, world!\n" msg_hello_len = . - msg_hello bcmd: .quad bcmd_bad, bcmd_bye, bcmd_num0, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x00 .quad bcmd_lit8, bcmd_lit16, bcmd_lit32, bcmd_lit64, bcmd_call8, bcmd_call16, bcmd_call32, bcmd_bad .quad bcmd_branch8, bcmd_branch16, bcmd_qbranch8, bcmd_qbranch16, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_exit # 0x10 .quad bcmd_dup, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_wm, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x20 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x30 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x40 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x60 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_type, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad # 0x80 .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad .quad bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad, bcmd_bad start: .byte b_lit8 .byte 3 # # m0: .byte b_call16 .word sub_hello - . - 2 .byte b_call16 .word sub_hello - . - 2 .byte b_wm .byte b_dup .byte b_qbranch8 .byte m0 - . .byte b_bye sub_hello: .byte b_lit64 .quad msg_hello .byte b_lit8 .byte msg_hello_len .byte b_type .byte b_exit .section .text .global _start # _start: mov rbp, rsp sub rbp, stack_size lea r8, start jmp _next b_exit = 0x17 bcmd_exit: mov r8, [rbp] add rbp, 8 _next: movzx rcx, byte ptr [r8] inc r8 jmp [bcmd + rcx*8] b_num0 = 0x02 bcmd_num0: push 0 jmp _next b_lit8 = 0x08 bcmd_lit8: movsx rax, byte ptr [r8] inc r8 push rax jmp _next b_lit16 = 0x09 bcmd_lit16: movsx rax, word ptr [r8] add r8, 2 push rax jmp _next b_call8 = 0x0C bcmd_call8: movsx rax, byte ptr [r8] sub rbp, 8 inc r8 mov [rbp], r8 add r8, rax jmp _next b_call16 = 0x0D bcmd_call16: movsx rax, word ptr [r8] sub rbp, 8 add r8, 2 mov [rbp], r8 add r8, rax jmp _next b_call32 = 0x0E bcmd_call32: movsx rax, dword ptr [r8] sub rbp, 8 add r8, 4 mov [rbp], r8 add r8, rax jmp _next b_lit32 = 0x0A bcmd_lit32: movsx rax, dword ptr [r8] add r8, 4 push rax jmp _next b_lit64 = 0x0B bcmd_lit64: mov rax, [r8] add r8, 8 push rax jmp _next b_dup = 0x18 bcmd_dup: push [rsp] jmp _next b_wm = 0x20 bcmd_wm: decq [rsp] jmp _next b_branch8 = 0x10 bcmd_branch8: movsx rax, byte ptr [r8] add r8, rax jmp _next b_branch16 = 0x11 bcmd_branch16: movsx rax, word ptr [r8] add r8, rax jmp _next b_qbranch8 = 0x12 bcmd_qbranch8: pop rax or rax, rax jnz bcmd_branch8 inc r8 jmp _next b_qbranch16 = 0x13 bcmd_qbranch16: pop rax or rax, rax jnz bcmd_branch16 add r8, 2 jmp _next b_bad = 0x00 bcmd_bad: mov eax, 4 # № 4 — sys_write mov ebx, 1 # № 1 — stdout mov ecx, offset msg_bad_byte # mov edx, msg_bad_byte_len # int 0x80 # mov eax, 1 # № 1 — sys_exit mov ebx, 1 # 1 int 0x80 # b_bye = 0x01 bcmd_bye: mov eax, 4 # № 4 — sys_write mov ebx, 1 # № 1 — stdout mov ecx, offset msg_bye # mov edx, msg_bye_len # int 0x80 # mov eax, 1 # № 1 — sys_exit mov ebx, 0 # 0 int 0x80 # b_type = 0x80 bcmd_type: mov eax, 4 # № 4 — sys_write mov ebx, 1 # № 1 — stdout pop rdx pop rcx push r8 int 0x80 # pop r8 jmp _next
ما هي النتيجة
لقد قمنا باختبار آلة بايت مكدس 64 بت كاملة وسريعة إلى حد ما. في السرعة ، ربما تكون آلة البايت هذه واحدة من أسرع الأجهزة في فئتها (آلة بايت مكدس بدون JIT). إنها تعرف كيفية تنفيذ الأوامر بالتسلسل ، والانتقال الشرطي وغير المشروط ، وإجراءات الاتصال ، والعودة منها. في الوقت نفسه ، يكون الرمز الثانوي المستخدم مضغوطًا إلى حد معقول. في الأساس ، تأخذ أوامر البايت 1-3 بايت ، وأكثر نادر جدًا (الأرقام الكبيرة فقط ، واستدعاءات الإجراءات البعيدة جدًا). يتم أيضًا رسم مجموعة صغيرة من أوامر البايت ، والتي يسهل توسيعها. افترض أن جميع الأوامر الأساسية للعمل مع الحزمة (يمكن إسقاطها وإسقاطها وإغلاقها وإغلاقها وما إلى ذلك في 20 دقيقة ، وسيذهب نفس المبلغ إلى أوامر عدد صحيح حسابي).نقطة أخرى مهمة. لا يحتوي الكود الجانبي ، على عكس رمز الحصن المخيط المباشر الكلاسيكي ، على تعليمات الماكينة ، لذلك يمكن نقله دون إعادة تجميعه إلى منصة أخرى. يكفي إعادة كتابة النواة مرة واحدة إلى نظام التعليمات الخاص بالمعالج الجديد ، ويمكن القيام بذلك بسرعة كبيرة.الإصدار الحالي من جهاز البايت لا يقتصر على أي لغة معينة. لكنني أرغب في تنفيذ لغة Fort على ذلك لأن لدي خبرة في ذلك ، ويمكن إجراء برنامج التحويل البرمجي بسرعة كبيرة.إذا كان هناك اهتمام بهذا ، استنادًا إلى هذا الجهاز ، في المقالة التالية ، فسوف أقوم بإدخال مدخلات من السلاسل والأرقام وقاموس الحصن ومترجم فوري. يمكنك "لمس" الفريق بيديك. حسنًا ، في المادة الثالثة ، سنقوم بإنشاء مترجم ، وسنحصل على نظام حصن كامل تقريبًا. بعد ذلك ، سيكون من الممكن كتابة وتجميع بعض الخوارزميات القياسية ومقارنة الأداء باللغات والأنظمة الأخرى. يمكنك استخدام ، على سبيل المثال ، غربال إراتوستينس ، وما شابه.من المثير للاهتمام تجربة الخيارات. على سبيل المثال ، اجعل جدول الأوامر 16 بت ، وانظر كيف سيؤثر هذا على الأداء. يمكنك أيضًا تحويل نقطة إدخال _next إلى ماكرو ، وفي هذه الحالة ، سيزداد حجم رمز الجهاز لكل أمر من وحدات البايت بأمرين (مخصومًا من الانتقال بالإضافة إلى ثلاثة أوامر من _next). بمعنى أنه في النهاية لن يكون هناك انتقال إلى _next ، ولكن محتويات نقطة _next نفسها (هذه هي 14 بايت). من المثير للاهتمام معرفة كيف سيؤثر هذا على الأداء. يمكنك أيضًا محاولة القيام بالتحسين باستخدام السجلات. على سبيل المثال ، تقوم حلقة قياسية مع عداد في الحصن بتخزين العداد في مكدس الإرجاع. يمكنك عمل نسخة من السجل واختباره أيضًا.يمكنك أيضًا إنشاء مترجم التعبيرات المكتوبة في النموذج الكلاسيكي (على سبيل المثال ، A = 5 + (B + C * 4)).بشكل عام ، هناك مجال للتجريب! :)
استمرار: آلة بايت للحصن (وليس فقط) في أمريكا الأصلية (الجزء 2)