( يا إلهي، من برمج .iris ليكون غير قابل للإيداع؟ )
لعبة تقمّص أدوار نصية مكتوبة بلغة التجميع RISC-V 32-bit، ويتم محاكاتها عبر QEMU.
"الكون قرر أنه لا يريدني أن أفعل شيئًا أفضل، مثل ربما الحصول على حياة."
المتطلبات
riscv32-elf-as— مُجمّع RISC-Vriscv32-elf-ld— رابط RISC-Vqemu-riscv32— محاكي وضع المستخدم لـ RISC-V
على Arch Linux:
sudo pacman -S riscv64-elf-binutils qemu-user
البناء والتشغيل
make build # تجميع وربط إلى main.bin
make run # بناء + تشغيل عبر qemu-riscv32
make debug # تشغيل مع -strace لفحص استدعاءات النظام
هيكل المشروع
Extremis/
├── main.s # نقطة الدخول (_start)، تستدعي run_chapter_1
├── src/
│ ├── include/
│ │ └── function.s # وحدات ماكرو إطار المكدس startF / endF
│ ├── engine/
│ │ ├── print.s # print — طباعة سلسلة نصية منتهية بـ null (a0)
│ │ └── utils.s # printl — طباعة قائمة من السلاسل النصية (a0=عنوان الجدول، a1=العدد)
│ ├── chapters/
│ │ └── chapter_1.s # منطق الفصل الأول؛ تحميل سلاسل المقدمة واستدعاء printl
│ └── dialogue/
│ └── intro.s # بيانات السلاسل النصية لتسلسل المقدمة
└── build/ # ملفات .o المترجمة (تعكس هيكل src/)
البنية المعمارية
المحرك مكتوب بلغة التجميع النقية RISC-V 32-bit مستهدفًا واجهة Linux ABI عبر محاكاة وضع المستخدم لـ QEMU.
اصطلاح إطار المكدس
تستخدم كل دالة وحدات الماكرو startF / endF من src/include/function.s لحفظ واستعادة ra، s0، s1، s2 على المكدس:
startF # دفع: تخصيص 16 بايت، حفظ ra/s0/s1/s2
...
endF # سحب: استعادة ra/s0/s1/s2، إلغاء تخصيص 16 بايت
ret
استدعاءات النظام
تحذير: أرقام استدعاءات بيئة RARS/MARS لا تعمل تحت
qemu-riscv32. يستخدم هذا المشروع أرقام استدعاءات نظام Linux RISC-V ABI.
يُوضع رقم استدعاء النظام في a7، ويُستدعى بـ ecall. تعود القيمة في a0.
الإخراج
| الغرض | RARS a7 |
Linux a7 |
a0 |
a1 |
a2 |
ملاحظات |
|---|---|---|---|---|---|---|
| طباعة سلسلة نصية | 4 |
64 |
1 (وصف الملف stdout) |
عنوان المخزن المؤقت | الطول | Linux لا يتوقف عند null — مرر طول البايت |
| طباعة حرف | 11 |
64 |
1 |
مخزن الحرف | 1 |
خزّن الحرف في الذاكرة، مرر عنوانه |
| طباعة عدد صحيح | 1 |
— | العدد الصحيح | — | — | لا يوجد مقابل في Linux؛ حوّله إلى سلسلة نصية أولاً |
| طباعة عدد سداسي عشري | 34 |
— | العدد الصحيح | — | — | لا يوجد مقابل في Linux؛ حوّله يدويًا |
| طباعة عدد غير موقع | 36 |
— | العدد الصحيح | — | — | لا يوجد مقابل في Linux؛ حوّله يدويًا |
الإدخال
| الغرض | RARS a7 |
Linux a7 |
a0 |
a1 |
a2 |
ملاحظات |
|---|---|---|---|---|---|---|
| قراءة سلسلة نصية | 8 |
63 |
0 (وصف الملف stdin) |
عنوان المخزن المؤقت | أقصى بايت | Linux يعيد البايتات الخام بما في ذلك السطر الجديد |
| قراءة حرف | 12 |
63 |
0 |
عنوان المخزن المؤقت | 1 |
يعيد a0 = البايتات المقروءة؛ الحرف في المخزن المؤقت |
| قراءة عدد صحيح | 5 |
— | — | — | — | لا يوجد مقابل في Linux؛ اقرأ سلسلة نصية، حللها |
العملية
| الغرض | RARS a7 |
Linux a7 |
a0 |
ملاحظات |
|---|---|---|---|---|
| خروج | 10 |
93 |
— | RARS يتجاهل a0؛ Linux يقرؤه كحالة خروج |
| خروج(رمز) | 17 |
93 |
رمز الخروج | Linux exit_group |
الذاكرة
| الغرض | RARS a7 |
Linux a7 |
a0 |
يعيد | ملاحظات |
|---|---|---|---|---|---|
| تخصيص (sbrk) | 9 |
214 |
بايت (RARS) / عنوان brk جديد (Linux) | a0 = بداية الكتلة المخصصة |
Linux brk يعمل بشكل مختلف — تضبط القمة الجديدة، وليس الحجم |
الإدخال/الإخراج للملفات
| الغرض | RARS a7 |
Linux a7 |
a0 |
a1 |
a2 |
a3 |
ملاحظات |
|---|---|---|---|---|---|---|---|
| فتح ملف | 13 |
56 |
واصف الدليل (-100=الدليل الحالي) |
عنوان اسم الملف | الأعلام | الوضع | Linux يستخدم openat |
| قراءة ملف | 14 |
63 |
واصف الملف | عنوان المخزن المؤقت | أقصى بايت | — | يعيد البايتات المقروءة |
| كتابة ملف | 15 |
64 |
واصف الملف | عنوان المخزن المؤقت | عدد البايتات | — | يعيد البايتات المكتوبة |
| إغلاق ملف | 16 |
57 |
واصف الملف | — | — | — | يعيد 0 عند النجاح |
الوقت
| الغرض | RARS a7 |
Linux a7 |
a0 |
a1 |
ملاحظات |
|---|---|---|---|---|---|
| الوقت | 30 |
113 |
معرف الساعة (1=الوقت الحقيقي) |
timespec* المخزن المؤقت |
RARS يعيد lo/hi مقسمين في a0/a1؛ Linux يكتب الهيكل إلى المخزن المؤقت |
| سكون | 32 |
115 |
معرف الساعة | timespec* |
RARS يأخذ ميلي ثانية في a0؛ Linux يستخدم clock_nanosleep مع هيكل |
إضافة محتوى
- السلاسل النصية الجديدة للحوار توضع في
src/dialogue/. - الفصول الجديدة توضع في
src/chapters/ويتم تضمينها (.include) في ملف الفصل الذي يحتاجها. - ملف Makefile يكتشف تلقائيًا جميع ملفات
.sتحتsrc/(باستثناءsrc/include/).
