دورة لا نهاية لها لم تكن: قصة حشرة الكأس المقدسة

ذات مرة كانت هناك لعبة لـ GBA تسمى Hello Kitty Collection: Miracle Fashion Maker. كانت لعبة لطيفة مبنية على امتياز Sanrio Hello Kitty الشهير وتم تطويره بواسطة Imagineer. ولكن تحت ستار اسم يبدو بريئا كانت مشكلة خبيثة. لسبب ما ، لم يتم تشغيل هذه اللعبة البسيطة على أي محاكي GBA. ولكن هذا وحده لن يكون كافياً لوصف المشكلة بأنها خطأ في الكأس المقدسة. مثل جميع أخطاء الكأس المقدسة ، كان هذا الخطأ نفسه مربكًا تمامًا. كان التفسير بسيطًا: في مرحلة ما من تسلسل بدء اللعبة ، وقعت في دورة لم تخرج منها أبدًا ، بانتظار قراءة قيمة معينة من ذاكرة لا وجود لها . على الرغم من وجود أخطاء مماثلة في العديد من الألعاب ، على سبيل المثال ، في المقدمة الشائعةThe Legend of Zelda: The Minish Cap ، يعتمدون على السلوك الخاص الناجم عن قراءة عناوين الذاكرة غير الصالحة. ولكن يبدو أن هذه الدورة تنتهك مثل هذا السلوك. ومع ذلك ، عملت اللعبة على معدات حقيقية. علاوة على ذلك ، حدث نفس الخطأ بالضبط عند تحميل إنقاذ في Sonic Pinball Party بعد إعادة تشغيل باردة. هل يمكن أن يكون توقع عناوين الذاكرة غير الصحيحة هذه خاطئًا إلى حد ما؟ ولكن إذا كان الأمر كذلك ، فكيف؟


لكن هذا غير قانوني ، أليس كذلك؟


انتظر دقيقة - إذا كنت تحاول الوصول إلى ذاكرة غير صالحة ، فإن اللعبة تحتاج فقط إلى التعطل ، أليس كذلك؟ يجب أن تحدث عملية لم يتم حلها ، أو خلل ، أو بعض الأخطاء الأخرى. حق؟

حسنًا ، إنه أشبه بـ "نعم". ولكن ليس حقا. على الأقل ليس على GBA.

في بنية معالجات ARM التي تم استخدامها في GBA ، تسمى هذه الحالة الخاطئة بإحباط البيانات وتحدث فقط عندما تحاول الوصول إلى الذاكرة التي لم يخصص مدير الذاكرة إذن القراءة لها 1 . عند حدوث إحباط البيانات ، يكمل المعالج ما كان يفعله ويذهب إلى ناقل الاستثناءالمخصصة لإحباط البيانات الاستثناءات. ثم يمكن لنظام التشغيل اختيار أحد الحلول: إنهاء العملية الحالية ، وتعيين ذاكرة خطأ الصفحة ، والسماح للعملية بمعالجة الموقف ، كما تفعل بعض المحاكيات JIT باستخدام "fastmem" ، أو تنفيذ بعض الإجراءات الأخرى.

كيف تتعامل GBA مع إحباط البيانات؟ يوجد إدخال متجه الاستثناء لإحباط البيانات في ROM التمهيد لوحدة تحكم GBA (أو ، كما يطلق عليه أيضًا ، في BIOS). إذا واجه GBA إحباط البيانات ، فإنه يحاول الانتقال إلى معالج DACS 2إذا كان موجودًا ، يحدث حظر خلاف ذلك. لا يوجد لعبة تجارية معالجات DACS. فلماذا لا تتجمد هذه اللعبة؟ كل شيء بسيط للغاية - لا يقوم GBA أبدًا بإنشاء بيانات. لا يحتوي على مدير ذاكرة (MMU) (أو حتى وحدة حماية ذاكرة ، كما هو الحال في DS) ، لذلك يستمر في العمل ويقرأ ذاكرة غير صالحة.

يدخل ناقل الذاكرة المشهد.



ما هي الذاكرة غير الصالحة بشكل عام؟ كيف تبدو؟ هذه هي العقبة الرئيسية. هذا موقف صعب: ما تقرأه التعليمات البرمجية يعتمد بشكل كبير على ما فعلته وحدة المعالجة المركزية مؤخرًا ، أو بتعبير أدق ، ما فعله ناقل الذاكرة مؤخرًا . باختصار ، عند الوصول إلى ذاكرة غير صالحة ، تقرأ وحدة المعالجة المركزية ما كان الأخير على ناقل الذاكرة. لفهم ما يلي من هذا ، تحتاج إلى معرفة القليل عن ناقل الذاكرة وكيف يعمل.

ناقل الذاكرة هو جزء من دائرة إلكترونية تربط وحدة المعالجة المركزية بجميع مكونات ذاكرة النظام الأساسي. في GBA ، يتم توصيل العديد من الأجهزة بناقل الذاكرة: ذاكرة الوصول العشوائي العاملة وذاكرة الفيديو وناقل الخرطوشة. عندما تحاول وحدة المعالجة المركزية الوصول إلى الذاكرة ، فإنها تخبر ناقل الذاكرة عن العنوان الذي تحتاج إلى الوصول إليه ، ثم يتم تنشيط المكون المقابل لذلك العنوان. ثم يضع المكون القيمة في هذا العنوان على الناقل ، والذي قد يستغرق عدة دورات 3 ، وبعد ذلك يمكن لوحدة المعالجة المركزية قراءة القيمة من الناقل في النهاية. في حالة GBA، إذا يرتبط أي معدات مع العنوان، ثم هو مكتوب لا قيمة إلى الحافلة، وحدة المعالجة المركزية يقرأ أي قيمة وضعت آخر على متن الحافلة. يمكن أن يختلف الموقف بطرق مختلفة ، على سبيل المثال ، إذا كانت القراءة 16 بت ، وتحاول وحدة المعالجة المركزية إجراء قراءة 32 بت ، ولكن بشكل عام ستكون دائمًا قيمة من الناقل. يطلق المطورون على هذه الميزة اسم "الحافلة المفتوحة". في وقت سابق ، كتبت كيف تؤثر على الألعاب الأخرى .

حسنًا ، يبدو أن كل شيء لا يبدو سيئًا ... أليس كذلك؟


حتى تتمكن من تخزين آخر وصول للذاكرة مؤقتًا؟ ثم أعده مرة أخرى؟ في الحالة العامة ، سيعمل هذا النهج ، ولكن هناك بعض الصعوبات. أولاً ، تحتاج إلى التأكد من أن جميع عمليات الوصول إلى الذاكرة بالترتيب الصحيح. هذا أكثر تعقيدًا مما يبدو ، لأن وحدة المعالجة المركزية تصل إلى الذاكرة مع كل تعليمات للحصول على التعليمات التالية في خط الأنابيب. وفي الواقع ، في الحالة العامة * ، تكون الذاكرة العالقة في الناقل هي آخر تعليمات يتم تلقيها. يؤدي هذا إلى تبسيط العملية ، لأنك تحتاج فقط إلى الحصول على هذه القيمة الأخيرة المحددة مسبقًا. ولكن نظرًا لأن آخر قيمة تم تحديدها مسبقًا تعتمد فقط على المكان الذي ننفذه حاليًا من الذاكرة ، فيجب أن تكون هي نفسها دائمًا. حتى إذا تغير العنوان المستلم وهو غير صالح ،ستحصل دائمًا على نفس الذاكرة.

اه ... توقف. لكن هذه الدورة موجودة ، ولا يمكن الخروج منها إذا تم تحديد هذه القيمة مسبقًا. ماذا يحدث؟ إذا تلقى التعليمات التالية باستمرار ، فماذا يحدث بين هذه العمليات؟ حاولت تشغيل مثل هذه الحلقات التي لا نهاية لها على ROM ROM للتحقق للتحقق ، على سبيل المثال ، من أن القيمة يمكن أن تسوء. يمكن أن يحدث هذا بالتأكيد إذا لم يتم تحديث القيمة مؤخرًا ، ولكن يتم تحديث القيمة في كل تعليمة ، لذلك ليس لديها الوقت للتلف. اختباراتي لم تترك الحلقة أبداً. لقد فعلت شيئًا مختلفًا عن هذه الألعاب ، على الرغم من أنني أعدت إنشاء الدورة بالضبط. أي خطأ ارتكبت؟

Pokémon Emerald و ACE ، يحدثان فقط على الحديد


إلى الأمام في الوقت المناسب ، في يناير 2020. كان تقرير الخطأ في حزب Sonic Pinball Party في ذلك الوقت حوالي ثلاث سنوات ونصف. في المحاكيات الأخرى ، كان معروفًا لسنوات عديدة. لقد نفدت نظريات العمل. في نهاية هذا الشهر ، مستخدم يحمل لقب merrpانضم إلى مجتمع Discord لمحاكي mGBA وقال إن Pokémon Emerald لديه خلل جديد في تنفيذ التعليمات البرمجية التعسفية (ACE) يعمل فقط على الأجهزة. علاوة على ذلك ، من المرجح أن يستخدم هذا الخلل من قبل المتسابقين الذين قد يرغبون في ممارسة المحاكي. من الواضح أن هذا الخطأ أصبح هدفًا جذابًا لإصلاح الخطأ ، على الرغم من أنه سيكون من الأفضل إذا اكتشفته قبل الإصدار 0.8.0. بدأت في البحث عن خلل وأكدت ملاحظة merrp أنه يعمل فقط على الأجهزة. في جميع المحاكيات التي جربتها ، علقت اللعبة بشاشة سوداء. لكن merrp أبلغتني أنها معلقة على القراءة من ذاكرة غير صالحة في حلقة ، وأدركت أنه من المحتمل ألا أتمكن من إصلاح الخطأ في المستقبل القريب. هذا هو نفس الخطأ مرة أخرى .

هذه المرة ، أعطاني التعرف على وظائف التكرار ميزة. بفضل مشروع pokeemerald ، استطعت بسهولة إجراء تغييرات مستهدفة على الوظيفة لمحاولة معرفة كيف تمكنت من الخروج من الحلقة. تبدو النسخة المبسطة من هذه الحلقة شيئًا مثل هذا:

uint16_t type = /* ... */;
for (int32_t i = 0; table[type][i] != 0xFFFF; ++i) {
	uint16_t value = table[type][i] & 0xFE00;
	if (value > 0x7E00) {
		break;
	}
	/* ... */
}

تقوم الحلقة بمهمة بسيطة إلى حد ما. هناك جدول قيم ثنائي الأبعاد. في كل صف من جدول العمود هذا ، typeتحاول الحلقة أولاً تحديد ما إذا كانت القيمة هي قيمة خفير معينة. إذا كان الأمر كذلك ، تنتهي الحلقة. وإلا ، فإنه يطبق قناعًا على القيمة ويتحقق مما إذا كان أكبر من القيمة التي يتم التحقق منها. خلاف ذلك ، فإنه يذهب إلى أسفل الدورة. في حالة معينة من خلل ، typeتتجاوز القيمة حدود الجدول ، مما يؤدي إلى ظهور مؤشر غير صالح. هذا يعني أنه عند محاولة الوصولiلهذا العنصر من هذا العمود غير موجود ، سنصل دائمًا إلى ذاكرة غير صالحة. على الرغم من أن إزاحة الجدول تزداد مع كل تكرار للحلقة قبل العودة إلى الذاكرة الفعلية ، فقد تحتاج إلى مئات الملايين من التكرار. لذلك ، من الواضح أنه لا يفعل ذلك. فكيف يخرج البرنامج من حلقة؟

للتحقيق في ذلك ، قمت بتغيير الدورة ونظرت في ما سيحدث إذا خرجت من الدورة على الفور. اتضح أن كل شيء بسيط للغاية: في هذه اللحظة ، عملت ACE على كل من الأجهزة والمحاكي ، ولم يتم تعليق أي شيء. لذا ، بدلاً من ذلك ، حاولت تعيين لون الشاشة على القيمة التي يقرأها البرنامج عندما يخرج من الحلقة ويتجمد بحيث لا يتغير اللون. قمت بإعادة تجميع الرمز وتشغيله على GBA حقيقي. بعد بضع ثوانٍ من التجمد على شاشة سوداء ، أصبح لونًا أزرق رائعًا.


أزرق شديد

لكن المحاكي لا يزال معلقًا على شاشة سوداء. ما القيمة التي سيقرأها إذا قرأ القيمة المستلمة سابقًا؟ بدلاً من ذلك ، أصبح لونًا فيروزيًا داكنًا.


فو

، أي أن البرنامج ، قبل أن يتمكن من الخروج من الدورة ، اجتازه بالتأكيد مرة واحدة على الأقل. اتضح أيضًا أن الوقت اللازم للهروب من دورة الحديد يختلف. يستغرق ذلك عادةً من 2 إلى 30 ثانية. ما الذي يجري؟

نظرية العمل الجديدة


ثم لاحظت الفرق بين اختبار ROM و Pokémon Emerald عند تعليقه. لعب بوكيمون الموسيقى. كما لعبت Sonic Pinball Party الموسيقى. لم تقم Hello Kitty بتشغيل الموسيقى ، لكنها أعطتني فكرة. ماذا يحدث في حالة حدوث مقاطعة بين الجلب المسبق وتحميل البيانات؟ هل يبدأ البرنامج في الجلب المسبق لناقل المقاطعة قبل الوصول إلى الذاكرة غير الصالحة؟ لقد أنشأت بسرعة تخطيطًا لهذا الموقف في mGBA ، وقمت بتشغيل المقاطعات في اختبار ROM ، وبالطبع خرجت من الحلقة. ثم جربت نفس اختبار ROM على الأجهزة و ... لم تخرج من الحلقة. وهكذا ظهرت النظرية. في النهاية ، أدركت شيئًا. أنا متأكد من أنك لاحظت علامة النجمة أعلاه ، لذلك نعم ، يمكن أن يكون هناك حدث واحد بين الجلب المسبق والذاكرة ،ولكن فقط إذا قام ناقل الذاكرة ، بين الجلب المسبق والوصول إلى ذاكرة غير صالحة ، بإرسال طلب ليس إلى وحدة المعالجة المركزية ، ولكن إلى شيء آخر.

قلت أن وحدة المعالجة المركزية تتحكم في ناقل الذاكرة. بالنسبة للجزء الأكبر ، هذا صحيح ، ولكن هناك معدات مهمة أخرى يمكنها أيضًا الوصول إلى ناقل الذاكرة بتجاوز المعالج. هذه العملية تسمى الوصول المباشر للذاكرة . لقد تحدثت عن DMA في مقال سابق ، لذلك لن أخوض في مبادئ عملها. إذا قمت بإعادة قراءة المقالة ، فقد تلاحظ أنني قلت أن وحدة المعالجة المركزية الرئيسية تتوقف مؤقتًا أثناء تشغيل DMA. هذا يعني أنه أثناء تشغيل DMA ، ستكون القيمة الموجودة على الناقل هي آخر وصول إلى ذاكرة DMA. هذا مهم بشكل رئيسي إذا تجاوز DMA الذاكرة الفعلية إلى منطقة غير صالحة ؛ ومع ذلك ، فإنه يكرر آخر قيمة جيدة.

من المعروف منذ فترة طويلة أنه إذا قمت بتحميل ذاكرة غير صالحة في DMA ، فستحصل على آخر قيمة DMA ، لكنني قمت بتطبيقها في mGBA لفترة طويلة ونسيتها بالفعل. عندما رأيت هذا في رمز الوصول للذاكرة غير الصالحة عند دراسة الخطأ ، تم النقر على شيء في رأسي. ماذا لو بقيت قيمة DMA في الحافلة لتعليمات واحدة؟ إذا انتهت التعليمات الأولى بعد انتهاء DMA من تحميل الذاكرة غير الصالحة قبل أن تحصل على القيمة التالية ، فمن المفترض أن يؤدي ذلك نظريًا إلى إعادة تحميل قيمة DMA. علاوة على ذلك ، يستخدم تشغيل الموسيقى في GBA عادةً DMA لنقل إخراج الصوت. لتنفيذ ذلك بشكل صحيح ، تحتاج إلى محاكي دقيق لللباقة يمكنه حظر وحدة المعالجة المركزية في منتصف تنفيذ التعليمات ، بين بداية التعليمات والوصول إلى الذاكرة ، ومحاكاة وحدة تحكم GBA في محاكي mGBA ليست دقيقة باللباقة.وهذا شيء لي.يتذكر . لحسن الحظ ، تمكنت من التغلب على هذه المشكلة. الحل غير كامل ، ولكن يمكنني الآن مقارنة عنوان وحدة المعالجة المركزية المتوقع للإرشادات بعد DMA بعنوان وحدة المعالجة المركزية الحالي على تحميل غير صالح واستخدام عنوان واحد بدلاً من القيمة المحددة مسبقًا لقيمة DMA هذه.

القرار الذي طال انتظاره


لقد قمت بتشغيل عمليات DMA لـ H-blank في ذاكرة القراءة فقط الاختبارية وقمت بمزامنتها مع V-blank حتى تكون التوقيتات مستقرة ، وقمت بتشغيلها على الأجهزة ، و ... هذه المرة نجحت! خرج اختبار ROM باستمرار من الحلقة بعد نفس عدد التكرارات عندما تمت قراءة قيمة DMA من الناقل. كنت على حق! من أجل التنفيذ الصحيح لهذا في mGBA ، كانت هناك حاجة إلى عدة محاولات ، ولكن الآن يخرج البرنامج من الدورة بنفس النتائج كما هو الحال على الأجهزة. حصلت أخيرًا على ظل أزرق على mGBA. تم تمهيد Hello Kitty. كسب الادخار في حزب Sonic Pinball.

أنا فعلت هذا.

ربما كان هذا أطول وقت قضيته في حشرة واحدة. لمدة ثلاث سنوات ، استثمرت الكثير من الوقت في تصحيح الأخطاء لدرجة أنني فقدت العد ، وأنا متأكد من أن المطورين الآخرين واجهوا أيضًا مواقف مماثلة في محاكياتهم. بدون هذه البصيرة ، كان يمكن أن يستغرق الأمر مني عامًا آخر ، أو حتى أكثر ، ولكن الشاشة السوداء ، التي لم يحدث فيها شيء سوى تشغيل الموسيقى ، أصبحت بلاط الدومينو الذي أدى إلى انهيار المشكلة بأكملها.

الآن بعد العثور على الحل ، يمكن تنفيذه في محاكيات GBA الأخرى ، ووضع حد لهذا الخطأ. سيتم إصلاح الخطأ في mGBA 0.9.0 ، والذي آمل أن يتم إصداره هذا العام ، وقد تم إصلاحه بالفعل في إصدارات الاختبار. يمكنك أخيرًا لعب مجموعة Hello Kitty: Miracle Fashion Maker. ما لم تكن بالطبع ترغب في أن لا أحكم عليك.

صورة

  1. إذا حاولت تنفيذ ذاكرة لا تحتوي على أذونات تنفيذ ، فإن هذا يسمى إحباط الجلب المسبق.
  2. DACS (اختصار لنظام التصحيح والاتصال) جزء من مجموعة تطوير GBA.
  3. تسمى هذه الدورات الخاملة أثناء القراءة من الحافلة أحيانًا حالات الانتظار.

All Articles