تقييم الموضوع :
  • 2 أصوات - بمعدل 3.5
  • 1
  • 2
  • 3
  • 4
  • 5
NT Kernel : Exception Architecture
#1
المشاركة الأصلية كتبت بواسطة GamingMasteR في 02-01-2010, 04:02 PM:



بسم الله الرحمن الرحيم

- مقدمة :
سنقوم في هذا الموضوع بالتعرف على الـException Handling Architecture في أنظمة NT وتحت منصة IA32 ، الـException او الاستثناء هو حدث يحدث أثناء تنفيذ برنامج ما ويستدعي ان يتم نقل التنفيذ الى خارج الجزء الذي يتم تنفيذه حالياً .

يتم حدوث الاستثناء عن طريق تنفيذ تعليمات معينة (مثل Int Xxx) فيقوم الـHardware بإنشاء وإحداث هذا الاستثناء ، وفي حالات أخرى كثيرة يتم إنشاء الاستثناء وتنفيذه عن طريق الـSoftware نفسه (أو نظام التشغيل بمعنى أصح) ، عند حدوث هذا الاستثناء يتم البحث عن Exception handler مناسب ليقوم بمعالجة الاستثناء وهو عبارة عن دالة تكون مبرمجة لهذا الغرض خصيصا.


- الغرض من تصميم الـException Architecture :
1- توفير آلية موحدة لمعالجة الاستثناءات والتي يمكن استخدامها في جميع لغات البرمجة .
2- توفير آلية موحدة لمعالجة الاستثناءات المحدثة عن طريق الـHardware والـSoftware بنفس الشكل .
3- توفير آلية موحدة لمعالجة الاستثناءات المحدثة عن طريق user-mode application او عن طريق kernel-mode components بنفس الشكل (privileged/non privileged).
4- توفير آلية لمعالجة الاستثناءات بشكل مصمم ليعمل بكفاءة مع المنقحات Debuggers .
5- توفير آلية موحدة لمعالجة الاستثناءات يمكنها محاكاة آليات معالجة الاستثناءات الموجودة في أنظمة أخرى مثل OS/2 و POSIX .


- معمارية الاستثناءات في أنظمة NT :
عند انشاء Process فإن هناك منفذين اختياريين يمكن تحديدهم يمسى احدهم بالـDebugger Port والآخر بالـSystem Service Emulation Subsystem Port .

عند بداية حدوث استثناء في Thread معين ، فانه يتم محاولة ارسال اشارة الى الـdebugger عن طريق الـDebugger port الموجود في الـProcess Object والذي يتم تحديده عند انشاء العملية اول مرة ، فان لم يكن هناك Debugger port تم تحديده مسبقا او اذا لم يتمكن الـDebugger من معالجة الاستثناء فانه يتم البحث في الـمكدس Stack او بأدق في الـCall frames الخاص بالـThread الذي حدث له الاستثناء عن أي Exception Handler ليقوم بمعالجة الاستثناء ، فإذا لم يتم ايجاد أي Exception Handler في الـCall frames او لم يتمكن أي منهم بمعالجة الاستثناء فإنه يتم مرة اخرى محاولة ارسال اشارة الى الـDebugger عن طريق الـDebug Port . اذا لم يكن هناك Debugger او اذا كان موجود ولم يتمكن من معالجة الاستثناء فانه تتم محاولة لارسال اشارة الى الـEmulation Subsystem Port والذي يتم تحديده في بداية انشاء الـProcess أول مرة مثل الـDebug port . اذا لم يكن هناك Emulation Subsystem Port تم تحديده او لم يتمكن الـSubsystem من معالجة الاستثناء فانه يتم تمرير الاستثناء الى Exception Handler افتراضي يقوم نظام التشغيل بتحديده بناءاً على نوع الاستثناء .

الغرض من هذا التصميم هو إنشاء معمارية قوية لمعالجة الاستثناءات بالإضافة الى امكانية محاكاة انظمة التشغيل الاخرى في معالجة الاستثناءات بنفس الوقت مثل OS/2 exception handling و POSIX signals .


- عملية الـException Dispatching :
عند حدوث Hardware exception فانه يتم استدعاء الـTrap handler (Interrupt) المناسب وايضا يتم حفظ حالة المعالج Machine state للـCurrent Thread الذي حدث فيه الاستثناء في Context record فيتم حفظ جميع المسجلات العادية ومسجلات التحكم والنظام وكل ما يتعلق بحالة الـThread في هذا الـContext record . الـTrap handler يختلف حسب نوع الاستثناء فمثلا اذا كان سبب الاستثناء هو عملية قسمة على صفر فيكون الـTrap handler هو الـInterrupt Service Routine الخاص بـInterrupt #0 وهكذا. يتم حفظ المعلومات الخاصة بالـinterrupts في جدول يتم انشاءه اثناء عملية الـInitializing للـOS وحفظه في مسجل خاص IDTR .

بعد ان يتم حفظ حالة الـCurrentThread ، يتم إنشاء Exception record وهو عبارة عن Structure يحتوي على معلومات مهمة لمعالجة الاستثناء ، معالجة الاستثناء تختلف حسب المستوى الذي كان يعمل به الـThread أثناء حدوث هذا الاستثناء (KernelMode/UserMode) كالتالي :

أ- اذا كان الـThread يعمل في KernelMode (CPL=0) أثناء حدوث الاستثناء فانه يتم استدعاء الـException Dispatcher والذي بدوره يقوم بالبحث داخل الـKernel stack call frames للبحث عن أي Exception handler ، اذا لم يكن هناك أي Exception handler او اذا لم يستطع أي منهم معالجة الاستثناء يقوم الـException Dispatcher مباشرة باستدعاء الدالة KeBugCheck وهي الدالة المسئولة عن إظهار شاشة الموت الزرقاء Blue Screen Of Death (BSOD) ثم عمل dump للذاكرة كي يتمكن المبرمج من تحليل الخطأ فيما بعد ، اذا عليك ان تعي أثناء برمجة أي Third-party driver ان أي خطأ غير متوقع لم تقم بعمل Handling له سيؤدي الى انهيار النظام بأكمله.

ب- اذا كان الـThread يعمل في UserMode (CPL=3) أثناء حدوث الاستثناء فانه يتم محاولة ارسال رسالة الى المنقح Debugger عن طريق الـDebug Port ، تحتوي هذه الرسالة (ليست رسالة نوافذ WM_Xxx) على الـException record و الـClient ID للـThread كي يتمكن المنقح من معرفة سبب الاستثناء وإمكانية معالجته (مثل breakpoints/divide by zero/single step الخ) واستكمال التنفيذ. فإذا قام المنقح بمعالجة الاستثناء فان الـException dispatcher يقوم بإعادة حالة المعالج للـThread بعد ان تم معالجتها عن طريق المنقح ثم استكمال التنفيذ ، اما اذا لم يتمكن المنقح من معالجة الاستثناء او لم يكن هناك منقح أصلا فانه التجهيز لتنفيذ الـException Dispatcher بمستوى الـUser هذه المرة .

يتم تحديد عنوان الـUser stack ثم يتم نقل محتويات كل من الـException record و الـContext record داخل هذا المكدس ويتم تغيير حالة المعالج في الـContext record كي يتم استكمال تنفيذ الـThread لكن بمستوى المستخدم بالتحديد في ntdll!KiUserExceptionDispatcher ، يقوم هذا الـcallback باستدعاء ntdll!RtlDispatchException والتي تقوم بالبحث عن أي Exception handler داخل الـCall frames وتنفيذه ان وجد ثم استكمال تنفيذ الـThread ، أما إن لم يكن هناك أي Exception handler فانه يتم استدعاء الكرنل عن طريق الخدمة NtRaiseException لتمرير الاستثناء الى المنقح مرة أخرى (Last Chance Notification) ثم إعطاء الـEmulation subsystem القدرة على معالجة الاستثناء (ان كان هناك واحد) .

اذا لم يستطع أي مما سبق معالجة الاستثناء فانه يتم إنهاء عمل الـThread ويتم تحميل المكتبة faultrep.dll واستدعاء الدالة ReportFault والتي تقوم بإظهار ديالوج الـCrash المعروف .


- عملية الـUnwind :
أثناء عملية الـDispatching للاستثناء فانه يتم استدعاء وتنفيذ أي Exception handler موجود في الـCall frames ويتم دفع الـ Exception record و الـContext record كـParameters للـException handler ، وعلى ذلك فانه يمكن للـException handler ان يقوم بمعالجة الاستثناء وإكمال تنفيذ الـThread ، أو عدم معالجة الاستثناء وإكمال عملية البحث عن باقي الـException handlers داخل المكدس ، او يمكنه معالجة الاستثناء لكن مع عدم إمكانية استكمال التنفيذ ثم تنفيذ عملية يطلق عليها Unwind وهي بمثابة عمل Clean-up .

معالجة الاستثناء يمكن ان تكون بسيطة جدا مثل تغيير بعض البايتات في الذاكرة او تغيير حالة علم Flag معين او إظهار رسالة خطأ او فعل أي شيء آخر حسب الموقف وحسب إرادة المبرمج ، فان كان هناك إمكانية لمتابعة التنفيذ بعد معالجة الاستثناء فيتم ذلك عن طريق عمل Restore للـMachine state (المسجلات وخلافه الموجودة في الـContext record) ثم متابعة التنفيذ ، ويحدث هذا اذا تم إرجاع قيمة (Status code) من دالة الـException handler تدل على إمكانية متابعة التنفيذ فيقوم الـDispatcher بإيقاف البحث عن المزيد من الـException handlers داخل المكدس ثم استكمال التنفيذ كما ذكر .

اذا لم تكن هناك إمكانية لاستكمال التنفيذ بعد معالجة الاستثناء فان الـException handler يقوم بعملية يطلق عليها Unwinding وهي ان يتم البحث داخل المكدس مرة أخرى لكن في اتجاه معاكس هذه المرة عن أي Exception handler واستدعائه مرة أخرى مع تمرير Flags معينة تدل على ان هذه المرة نقوم بعملية Unwind ، الغرض من هذا هو عمل Clean-up وتحرير أي موارد تم حجزها عن طريق الـException handlers السابقين، يتم بعد ذلك استكمال التنفيذ في عنوان اختياري يتم تحديده في بداية عملية الـUnwind او اذا لم يتم تحديده فانه يتم استدعاء الكرنل لتنفيذ الـLast chance debugger notification ، عملية الـUnwind تتم عن طريق دالة يوفرها نظام التشغيل وهي RtlUnwind .


- تركيب الـException Record :
سجل الاستثناء او الـException record هو عبارة عن بنية structure تصف هذا الاستثناء وتحتوي على المعلومات والبارامترات اللازمة لمعالجة هذا الاستثناء ، وهي بنية ثابتة للاستثناءات سواء كانت محدثة عن طريق الأجزاء الصلبة Hardware او عن طريق نظام التشغيل او الـSoftware .

يتكون الـException record كالتالي :
NTSTATUS ExceptionCode :
وهو عبارة عن رقم DWORD يحدد السبب الذي ادى الى حدوث الاستثناء ، مثلا 0xC0000005 يدل على ان سبب الاستثناء هو Access Violation .

ULONG ExceptionFlags :
عبارة عن DWORD يحدد بعض الأعلام تمثل خصائص الاستثناء مثل :
EXCEPTION_NONCONTINUABLE : يدل على ان الاستثناء لا يمكن تجاوزه واستكمال تنفيذ الـThread ، وأي محاولة لاستكمال التنفيذ ستقوم بإحداث استثناء جديد يكون الـExceptionCode الخاص به هو STATUS_NONCONTINUABLE_EXCEPTION ، ويعتبر هذا العلم هو الوحيد الذي يمكن وضعه عن طريق المبرمج بخلاف الأعلام الأخرى التي يتم وضعها عن طريق الـDispatcher فقط .

EXCEPTION_STACK_INVALID : يدل على ان عنوان المكدس لم يكن في المدى المحدد له في الـTEB اذا كان الاستثناء حدث أثناء عمل الـThread في مستوى المستخدم او في المدى المحدد في KTHREAD اذا كان الـThread يعمل في مستوى النظام .

PEXCEPTION_RECORD ExceptionRecord :
مؤشر اختياري إلى Exception record آخر ، يتواجد في حال وجود استثناءات متشابكة Nested .

PVOID ExceptionAddress :
عنوان التعليمة التي حدث عندها او بسببها الاستثناء .

ULONG NumberParameters :
عدد الحقول الإضافية التي تصف المزيد من المعلومات عن الاستثناء وتكون في ذيل هذه البنية .

ULONG ExceptionInformation[NumberParameters] :
عدد اختياري من الحقول الإضافية التي تذيّل هذه البنية وتعطي معلومات إضافية عن الاستثناء ، عددها يمكن معرفته من الحقل السابق NumberParameters .


- الاستثناءات الخاصة بالـHardware :
تحدث الـHardware exceptions اذا حدث fault (نسميه خطأ ؟) أثناء سير التنفيذ بسبب تنفيذ تعليمة معينة (مثل نقاط التوقف مثلا Breakpoints) ، يقوم نظام التشغيل بجمع المعلومات الضرورية لمعالجة الاستثناء ثم استدعاء الـException dispatcher ليتولى أمر معالجة الاستثناء .

في هذا الجزء سنتعرف على بعض الاستثناءات التي تحدث عن طريق الـHardware وسنعرف الـStatus code لها وما إذا كان هناك Parameters إضافية .

Access Violation :
يحدث استثناء الـAccess Violation عند محاولة الوصول سواء بالقراءة او الكتابة الى عنوان غير متاح الوصول اليه عن طريق الـعملية الحالية Current Process ، ويحدث أيضا عند محاولة تنفيذ تعليمة موجودة في عنوان لا يمكن الوصول اليه عن طريق العملية الحالية .
الـException code : STATUS_ACCESS_VIOLATION .
يتبع هذا الاستثناء 2 Parameters إضافيين :
Read/Write : اذا كان هذا الباراميتر صفر فيدل على ان الاستثناء حدث بسبب محاولة القراءة Read من عنوان لا يمكن الوصول اليه ، اما ان كان واحد فيدل على ان العملية كان محاولة للقراءة Write .
Virtual Address : عنوان الذاكرة الوهمية الذي تم محاولة الوصول اليه .

Illegal Instruction :
يحدث هذا الاستثناء عند محاولة تنفيذ تعليمة لا يتعرف المعالج عليها .
الـException code : STATUS_ILLEGAL_INSTRUCTION .
لا يوجد Parameters إضافية لهذا الاستثناء.

Privileged Instruction :
يحدث هذا الاستثناء اذا كانت هناك محاولة لتنفيذ تعليمة غير مصرّح باستخدامها في حالة المعالج الحالية ، مثل ان يتم تنفيذ تعليمة من User-mode لا يسمح بتنفيذها الا من خلال Kernel-mode .
الـException code : STATUS_PRIVILEGED_INSTRUCTION .
لا يوجد Parameters إضافية لهذا الاستثناء.

Integer Divide By Zero :
يحدث هذا الاستثناء عند تنفيذ تعليمة للقسمة وكان المقسوم عليه هو العدد صفر .
الـException code : STATUS_INTEGER_DIVIDE_BY_ZERO .
لا يوجد Parameters إضافية لهذا الاستثناء.

Breakpoint :
يحدث هذا الاستثناء عند حدوث نقطة توقف سواء كانت Software Breakpoint عن طريق تنفيذ التعليمة Int3 او Hardware Breakpoint عن طريق مسجلات التنقيح Debug registers .
الـException code : STATUS_BREAKPOINT .
لا يوجد Parameters إضافية لهذا الاستثناء.

Single Step :
يحدث عند تفعيل الـSingle-step mode عن طريق وضع علم الـTrap في مسجل الأعلام EFLAGS .
الـException code : STATUS_SINGLE_STEP .
لا يوجد Parameters إضافية لهذا الاستثناء.


- الاستثناءات الخاصة بالـSoftware :
يتم إحداث هذا الاستثناء عن طريق نظام التشغيل نفسه System Software في حالات معينة كعدم القدرة على القراءة من الـPagefile مثلا ، يقوم نظام التشغيل بجمع المعلومات الضرورية لمعالجة الاستثناء ثم استدعاء الـException dispatcher ليتولى أمر معالجة الاستثناء .

سنتعرف على بعض الاستثناءات التي تحدث عن طريق نظام التشغيل وسنعرف الـStatus code لها وما إذا كان هناك Parameters إضافية .

Guard Page Violation :
يحدث هذا الاستثناء عند محاولة القراءة او الكتابة في صفحة محمية من الذاكرة Guard Page ، فيقوم نظام التشغيل فورا بإحداث هذا الاستثناء وعدم إكمال عملية الـRead/Write .
الـException code : STATUS_GUARD_PAGE_VIOLATION .
يتبع هذا الاستثناء 2 Parameters إضافيين :
Read/Write : اذا كان هذا الباراميتر صفر فيدل على ان الاستثناء حدث بسبب محاولة القراءة Read من عنوان موجود داخل Page محمية ، اما ان كان واحد فيدل على ان العملية كان محاولة للقراءة Write .
Virtual Address : عنوان الذاكرة الوهمية داخل الصفحة المحمية .

Page Read Error :
يحدث هذا الاستثناء عند محاولة القراءة من Paged Memory وأثناء عمل Page in حدث خطأ ما ولم يتم قراءة الذاكرة كلها او جزء منها من الـPagefile .
الـException code : STATUS_IN_PAGE_ERROR .
يتبع هذا الاستثناء Parameter واحد إضافي :
Virtual Address : عنوان الذاكرة الوهمية داخل الصفحة المراد القراءة منها .




ربما هذا الموضوع ليس دسم كما توقعت ، لكن كان الغرض منه هو إلقاء نظرة عامة على الـException Architecture من عين الطائر بدون التركيز على جزئيات محددة وبدون التطرق لكيفية معالجة الاستثناءات برمجيا او وضع مثال لأكواد ، أعدكم ان يكون الموضوع القادم أكثر تشويقا وتوغلا في الـNT Internals .


المراجع :
David Cutler : The father of the NT kernel .
Ken Johnson .
سبحان الله وبحمده، سبحان الله العظيم
أعضاء أعجبوا بهذه المشاركة : M!X0R


التنقل السريع :


يقوم بقرائة الموضوع: بالاضافة الى ( 3 ) ضيف كريم