ناقشنا في المرة الماضية كيف انه بامكاننا استعمال المقاطعا لكتابة برامج تقوم بتنفيذ اجرائيات معينة وفق دفع قيم معينة الى المكدس وطلب مقاطعة معينة من النظام (DOS).
مثالنا السابق كان عبارة عن برنامج يقوم بالتعامل مع الملفات النصية.. في هذا المقال سنقوم بكتابة نفس المثال السابق لكن ليعمل تحت بيئة 32bit وبالتالي فاننا سنقوم في هذا المقال باستخدام ما يدعى API اختصارا لـ Application Program-ming Interface.
كلنا يسمع بهذا المصطلح API لكن قلة يعرفون مامعناه بشكل صحيح .. اذن دعونا في البداية نتعرف على ما سنقوم باعتماده في بيئة 32bit..
ما هي API؟
API هي عبارة عن مجموعة تعاريف تبين الطرق التي بواسطتها يقوم برنامج ما بالتخاطب مع الاخر. ان احدى الغايات الاساسية من الـ API هو توفير مجموعة من الوظائف المشتركة. بالتالي فان المبرمجين يستطيعون استغلال هذه الوظائف مما يوفر عليهم الوقت للبدء بكتابة كل شئ من البداية.
اذن ناتي الى تلخيص الفكرة العامة للـ API بانها مجموعة القواعد او الوظائف التي من خلالها يستطيع المبرمجون توفير الوقت والجهد اللازم للقيام بعمل ما, كرسم دائرة على الشاشة, عن طريق استخدام احدى هذه الوظائف بتمرير قيم معينة اليها وطلبها من النظام للتنفيذ -- مما يسمح للتطبيقات بالاشتراك بقاعدة وظائف معينة لا تخرج عنها الامر الذي يسهل بالتالي امكانية نقل هذه التطبيقات من جهاز كمبيوتر لاخر نظرا لان هذه القواعد ستكون موجودة بدون شك على الجهاز الاخر.
لكن السؤال الذي قد يتبادر للذهن.. هل بالامكان تشغيل برنامج مكتوب لويندوز على جهاز يحوي على يونكس؟ الجواب هو لا فهذه القواعد تختلف من نظام لاخر وبالتالي فانت تحتاج الى شئ ما يقوم بتفسير هذه القواعد للنظام الجديد ليقوم بتنفيذ المقابلة لها من قاعدته.
لكن اين توجد هذه الوظائف؟
نظرا لان هذه الوظائف ستكون مشتركة بين جميع البرامج فان المبرمجين قد عكفوا على توفير طريقة تمكنهم من حفظ هذه الوظائف في مكان واحد بحيث تستطيع جميع البرامج استخدام هذه الوظائف في نفس الوقت وبدون اي جهد اضافي يذكر, وهذا ما ادى الى ظهور ما يسمى DLL اختصارا لـ Dynamic Link Library - فمكتبات الربط الديناميكية هي الاصل الذي يحوي على وظائف الـ API المطلوبة لتشغيل برنامجك ان كان مكتوبا لبيئة 32bit - هناك ما يسمى kernel32.dll, user32.dll, comdlg32.dll الخ.. كل هذه المكتبات تم توفيرها وتم تقسيم الوظائف الى مجموعات بحيث اخذت كل مكتبة ربط مجموعة من هذه الوظائف بحيث تكون منسقة مع بعضها البعض.
اذن كانت الغاية الرئيسية من وراء DLL هو توفير بيئة يستطيع من خلالها المبرمجون حفظ اجرائيات هامة من برامجهم, او حفظ وظائف الـ API فيها, اي حفظ كل ما قد يحتاجه المبرمج في اكثر من برنامج (فقد يضطر المبرمج الى استخدام اجرائية معينة في اكثر من برنامج وبالتالي لم يعد مضطرا الى اعادة كتابة هذا الجزء للبرنامج الجديد - يكفي ان يحفظ هذه الاجزاء في مكاتب DLL ويقوم باستدعائها من اي برنامج عند اللزوم عن طريق API).
قد يخطر للقارئ الان السؤال التالي, بما ان DLL ظهرت لتقوم بتجميع وحفظ API حسب الحاجة, هذا يعني ان البرنامج بالاصل لم يعد يحوي على هذه الـ API فكيف له ان يقوم بما يقوم به من وظائف, كيف له بالاصل ان يصل الى وظيفة معينة في الـ DLL, الا يحتاج الى API؟
الجواب هو نعم, فهذا الامر لا يحل من قبل المبرمجين وانما يقوم به المجمع الذي تستخدمه, فانت عندما تريد ان تقوم بكتابة تطبيق 32bit فانك تطلب من المجمع ان يوفر لك امكانية استخدام الوظيفة الفلانية من مكتبة الربط الفلانية ويقوم هو بالتالي باجراء طلب من مكتبة الربط لاستخدام هذه الوظيفة مما يسمح لك باستخدامها متى شئت في البرنامج حتى ولو طلبتها عند نهاية التنفيذ فان المجمع قام بوضع البيانات اللازمة لطلب هذه الوظيفة من مكتبة الربط ضمن بيئة الملف التنفيذي ويبقى على الـ loader الخاص بالويندوز القيام بملئ الفراغات الخاصة بعناوين الوظائف في ما يسمى IT اختصارا لـ Import Table او جدول الوارد. وبالتالي فان برنامجك عندما يطلب هذه الوظيفة فانه سيستخدم هذه العناوين المخزنة فيه.
ارجو ان تكون الفكرة واضحة وساحاول ان اقوم بتزويد شرح شبه مفصل عن الـ loader الخاص بالويندوز وكيف يقوم بعمله لاحقا.
دعونا نبدا بكتابة برنامجنا:
عند البداية في كتابة اي برنامج اسمبلي (واي برنامج بشكل عام) عليك ان تقوم بتحليل الخطوات اللازمة للتنفيذ, ففي برنامجنا الذي سنقوم بكتابته سنقوم بمناقشة كيفية استخدام دوال API اليوم دون الحاجة الى استخدام واجهة للبرنامج, اي ان برنامجنا سيكون برنامج محجم كثيرا حيث سيقوم "تقريبا" بنفس الخطوات التي قام بها برنامجنا الذي كتبناه لـ 32bit (وباء بفشل التنفيذ لتجاوزه الطول المسموح لتطبيقات 16bit :P) -- السبب الذي اقول من اجله "تقريبا" هو اننا لن نستخدم واجهات GUI (Graphical User Interface) لاختيار مثلا ان نقوم بانشاء ملف بدلا من فتحه, سيقوم برنامجنا فورا بما ان العملية ستكون اسهل بالنسبة لتطبيقات 32bit, سيقوم بفحص المسار الحالي للبرنامج ليبحث عن ملف باسم ثابت محدد في احد المتغيرات المعرفة في البرنامج, فان وجده قام بفتحه لقراءة طول معين من البايتات منه, وان لم يجده قام بانشائه وكتابة بعض البايتات الثابتة المحددة ضمن البرنامج فيه -- سنقوم ايضا بالقاء الضوء على بقية الدوال التي تشبه في عملها الخدمات (او الوظائف) التي استخدمناها (طلبناها) من مقاطعة 21h وذلك تمهيدا للمقال القادم الذي سيتناول توسيع عمل البرنامج ليتناول تصميم الواجهات المرئية سواء بلغة الاسمبلي دون الاستعانة باي برنامج خارجي او بعمل resource وربطها مع الملف التنفيذي.
فاذن برنامجنا لليوم سيقوم بـ: 1- البحث في المسار الحالي عن ملف معين وليكن test.txt
2- في حال كان الملف موجودا فتح الملف لقراءة اول 10 بايتات منه مثلا ويعرضها برسالة على الشاشة, وان لم يكن موجودا يقوم بانشائه وكتابة سلسلة نصية ما فيه.
3- اغلاق البرنامج.
الموضوع بسيط, ولن يحتاج الى وقت طويل للفهم.. دعونا اذن نبدا في تعريف بنية الملف التنفيذي الخاص بـ 32bit عندما نريد كتابة مثل هذا البرنامج.
سنقوم باستخدام RadAsm مع Masm32 ويفترض هذا المقال انك تعرف كيف تضيف masm الى قائمة المجمعات التي يدعمها radasm.
ماذا نحتاج لمعرفته عن تطبيقات 32bit؟
(ماخوذ من مقال Iczelion)
تعمل تطبيقات 32بت في ما يسمى النمط المحمي الذي كان متوفرا منذ ظهور i286. لكن i286 اصبح قديما الان. لذا سيكون اهتمامنا مركزا على i386 وما لحقه من معالجات. يقوم نظام ويندوز بتشغيل كل تطبيق 32بت في ذاكرة افتراضية منفصلة. هذا يعني بان كل تطبيق 32bit يملك مقدار عنونة حتى 4GB !. مع ذلك, هذا لا يعني ان كل تطبيق 32بت يملك ذاكرة فيزيائية تصل الى 4GB, فقط ان البرنامج يستطيع العنونة لحدود تصل الى هذا المقدار. فيما يخص شكل الذاكرة المعنونة ففي 32بت لم يعد يهمنا نوع الذاكرة التي سيحتاجها برنامجنا فكل ما نملكه هو شكل واحد من انماط العنونة وهي الـ flat memory. لم يعد هناك مقاطع ملزمة بـ 64k. هذا يعني انك لم تعد بحاجة الى التلاعب بقيم مسجلات المقاطع segment registers.
لننظر الان الى بداية ترويسة اي كود برنامج 32bit لنناقش الامور غير الواضحة فيه, ان كنت تملك اي كود 32بت اطلع عليه وستجد ما يشبه التالي:
.386 .model flat, stdcall option casemap: none include \masm32\include\windows.inc include \masm32\include\kernel32.inc include \masm32\include\user32.inc includelib \masm32\lib\kernel32.lib includelib \masm32\lib\user32.lib
.386
يعد هذا السطر موجه للمجمع والذي يخبر المجمع بان يقوم باستخدام تعليمات المعالج i386. طبعا بامكانك استخدام .486, .586 لكن الافضل ان تلتزم بهذا المعالج.
.model flat, stdcall
يقوم السطر .model الذي هو عبارة عن توجيه directive للمجمع يخبره فيه بان يقوم باستخدام نمط العنونة flat والذي لا نملك سواه تحت بيئة 32bit.
بالنسبة لـ stdcall فهذا يخبر المجمع ايضا بكيفية تمرير البيانات, من اليمين الى اليسار, او من اليسار الى اليمين, هل تذكر مبدا LIFO؟ الامر "تقريبا" مشابه لهذا المبدا, فالمعطيات في بيئة 16بت تملك نوعين من التحويل, C و PASCAL, بالنسبة لـ C فان تمرير البارامترات يتم من اليمين الى اليسار وبالتالي فان البارامتر الى اقصى اليمين يتم تمريره اولا الى المكدس right most, للتوضيح اكثر افرض انك تريد استدعاء الوظيفة test التي تملك 3 بارامترات (int first_param, int second_param, int third_param) وبالتالي حسب صيغة C فان التمرير والاستدعاء يكون بالشكل:
push third_param push second_param push first_param call test add sp, 12 ; adjusts the stack
وبالتالي ترى بانه كما تم تعريف المعطيات في C فان البارامتر الذي يقع الى اقصى اليمين وهو البارامتر الثالث قد تم دفعه اولا الى المكدس, بشكل عام هذا هو الشكل الذي ستصادفه في معظم تطبيقات 32بت.
فيما يخص صيغة PASCAL فهي عكس C تماما حيث يتم دفع المعطيات من اليسار الى اليمين وهذا الامر مستخدم في تطبيقات 16بت نظرا لانه يقوم بتوليد احجام اصغر للبرامج. فيما يخص عبارة stdcall الان فهي عبارة ان صيغة هجينة بين C و PASCAL حيث يتم تمرير المعطيات من اليمين الى اليسار بينما يكون امر الاستدعاء مسؤولا عن اعادة موازنة المكدس وهذه الصيغة حصرية الاستخدام على ويندوز.
include / includelib
هذا ايضا هو عبارة عن توجيه يستخدم للدلالة على المكتبة التي سيتم طلب الوظائف منها والتي تحوي ايضا على تعريفات لهذه الوظائف وبارامترات معينة خاصة بوظائف خاصة ومعقدة بعض الشئ.
option casemap: none
هذا السطر يدل Masm على ان اسماء الوظائف حساسة لحالة الاحرف اي ان وظيفة exitprocess تختلف عن ExitProcess (لا تنسى ان اسمبلي تشترك مع C في كيفية تقبل حالة الحروف بين كبيرة وصغيرة لكن ASM تملك بعض المرونة في هذا الخصوص).
الان ماذا بقي لدينا للبدء بكتابة التطبيق؟
كما في تطبيقات 16بت, كنا نقوم بتعريف المقاطع التي نريد استخدامها فيه, لكن فيما يخص 32بت فلم يعد هناك حاجة لهذا الامر فتوجيه model اخذ عن عاتقنا مهمة تعريف اسماء المقاطع ولم يبق لدينا الا الاستخدام الصحيح لهذه المقاطع.
.DATA و .CODE
في تطبيقات 32bit كل ما نحتاجه هو مقطعين هما .DATA وقد سبق الحديث عنه في مقالات سابقة, و .CODE والذي سيحوي على التعليمات الخاصة بالبرنامج. في تطبيقات 16بت لو لاحظت كنا نقوم بتعريف البيانات ضمن مقطع .CODE ونقوم باستخدام تعليمة JMP لتخطي هذه البيانات الى بداية التعليمات الصحيحة للتنفيذ, في 32بت هذا الامر مرفوض, لا يمكنك ان تقوم باستخدام بيانات ضمن مقطع الكود لكن طبعا يمكنك ان تطلب من المجمع ان يقوم بتخزينها لاحقا عند القيام بتشكيل الملف التنفيذي في مقطع الكود (احدى اشكال الحماية الغبية :P), ايضا لم تعد هناك حاجة لاستخدام تعليمة JMP في بداية المقطع لان بدايته ستكون اول تعليمة تنفيذية تريد طلبها من البرنامج..
هناك امر نسيت ان اذكره بخصوص inlcude و includelib, فيما يخص هذين التوجين فكما ذكرت فانهما ضروريان لطلب الوظائف من مكاتب الربط لكن ايضا بدون استخدام include ...kernel32.inc مثلا فانك ستبقى قادرا على استخدام الوظيفة المطلوبة لكن لن يعود بامكانك استخدام الشكل invoke لطلب هذه الوظيفة وستضطر لاستخدام الشكل العادي (Push param1, Push param2, Push param3, CALL Func)
دعونا نعود الان للتعرف على الوظائف (دوال API) التي سنحتاجها:
تجدون على الرابط التالي: http://spiff.tripnet.se/~iczelion/files/win32api.zip ملف بسيط يحوي على تقريبا جميع دوال API التي قد تحتاجونها, طبعا هذا الملف قديم وجرى احداث بعض التعديلات على هذه الوظائف فمنها من لم يعد مدعوما من قبل بعض الانظمة والاخر تم تعديل امور بسيطة فيه.. كل هذا يمكن ايجاده ان كنت تملك مكتبة MSDN فان لم تملكها يمكن مراجعة معلومات عن اي وظيفة عن طريق مكتبة MSDN عبر موقع مايكروسوفت: http://msdn.microsoft.com
بالنسبة لما سنقوم باستخدامه من دوال API فهم التالي: دالة (او وظيفة -- يختلف المبرمجون العرب في التعريب -- انا اطلق عليها الاسمين في اوقات غير محددة ويمكنك ان تطلق عليها الاسم الذي تريد لانها معروفة من شكلها) CreateFileA (اسم الدالة ضمن مكتبة MSDN او في win32api.hlp سيكون بدون حرف الـ A في نهايته -- حرف الـ A هذا يدل على ان الدالة ظهرت مع انظمة 32bit -- بعض الدوال تتطلب وجود هذا الحرف للتنفيذ والبعض الاخر لا تحتاجه وسنوضح الامر في وقته) + دالة ReadFile + دالة CloseHandle + دالة MessageBox.. اعتقد ان هذه هي الدوال التي سنحتاجها فقط (نظريا - اي ان هناك دوال نحتاجها لبدء تنفيذ الملف بشكل صحيح واخرى لانهاء البرنامج بشكل صحيح).
دالة CreateFile:
تستخدم هذه الدالة في فتح او انشاء ملف واعادة مقبض الملف لاستخدامات لاحقة.
الشكل العام لها هو التالي:
HANDLE CreateFile( LPCTSTR lpFileName, // pointer to name of the file DWORD dwDesiredAccess, // access (read-write) mode DWORD dwShareMode, // share mode LPSECURITY_ATTRIBUTES lpSecurityAttributes, // pointer to security attributes DWORD dwCreationDistribution, // how to create DWORD dwFlagsAndAttributes, // file attributes HANDLE hTemplateFile // handle to file with attributes to copy );
في الحالات العادية معظم هذه البارامترات تحمل القيمة NULL حيث انها لا تهمنا, ما نحتاج الى اعداده بشكل صحيح هو بارامتر الاسم, بارامتر خصائص الوصول, بارامتر خصائص الملف عند الانشاء...
سنقوم باستخدام هذه الوظيفة في برنامجنا بالشكل التالي:
invoke CreateFile, addr lpFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL
ما يتغير من هذه البارامترات هو البارامتر lpFileName والذي يشير الى اسم الملف المراد الوصول اليه.
البارامتر dwDesiredAccess والذي يحمل احدى قيمتين: GENERIC_READ/WRITE
البارامتر dwShareMode ويحمل احدى اربع قيم لكننا في معظم الاوقات كما يظهر الكود السابق نحتاج الى الوصول الى الملف بغرض القراءة والكتابة في نفس الوقت لذا فاننا نقوم بتحديد اكثر من خيار بفصلهم بالمحرف "|".. في radasm سنقوم باستخدام المعامل "and" بدلا من هذا المحرف حيث لا يمكن استخدام "|".
البارامتر dwCreationDistribution تملك اكثر من خيار اكثر ما نستخدمه هو CREATE_NEW او OPEN_EXISTING
البارامتر dwFlagsAndAttributes يحدد خصائص الملف المراد انشاؤه كما ورد في مثالنا الخاص بـ 16بت.
فيما يخص البارامترات الاخرى فيذكر الملف win32api.hlp انها تستخدم في حالات خاصة وبالتالي في غير هذه الحالات فانها تحمل القيمة NULL.
فيما يخص هذه الدالة فقد نستخدمها في فتح ملف موجود سلف او للتحقق من وجود ملف حسب القيمة المرتجعة في EAX حيث اما ان ترتجع قيمة مقبض الملف او القيمة FFFFFFFF وبالتالي يمكن الاعتماد عليها في تحديد وجود ملف من عدمه.
اذن كما راينا فان استخدام هذه الدالة ليس بالامر الصعب, لكن كيف نقوم بالعادة باستدعاء اي دالة في MASM32 ؟
هذا السؤل يحمل اجابتين, في masm32 الاصلي فانك ملزم بالشكل (push param3 - push param2 - push param1 - call FunctionName) بينما في masm32 المعدل من قبل _Huntch فقد تم كتابة ماكروات تسمح باستخدام الشكل (Invoke FunctionName, param1, param2, param3) -- هل تذكر صيغة C و باسكال؟
سنقوم نحن باستخدام النسخة المعدلة للتسهيل والتي تملك امكانية استخدام الشكلين بالاضافة الى اننا سنقوم باستخدام RadAsm كبيئة لكتابة تطبيق بواسطة MASM32 وتجميعه بواسطته.
اذن بفرض انك قمت باجراء التعديلات الصحيحة ليعمل masm مع radasm نقوم بفتح ملف جديد في radasm عن طريق Ctrl+N او File -> New File.. لن اعرض الان البنية الكاملة للملف التنفيذي لان هناك بعض الدوال التي سنتعرف عليها بعد قليل والتي سنحتاجها في البداية.
لاحظ كيف ان RadAsm يوفر امكانية التحديد التلقائي للدالة التي تقوم بكتابة اسمها وكيف انه يقوم بعرض ايضا الشكل العام لاستدعاء هذه الدالة او اي دالة اخرى.. مثلا, FindFirstFile.


لاحظ كيف اننا نقوم بالاشارة الى اسماء label البيانات التي قمنا بحجزها في .DATA عن طريق استخدام ADDR والتي تشير الى عنوان الـ RVA (Relative Virtual Address) الخاص بهذه البيانات.
هذا هو الشكل العام الذي سنقوم باستخدامه لتعريف واستدعاء وظائف API, ولا تختلف اي دالة عن الاخرى في الاستدعاء بل في كيفية تعريف بعض البارامترات وخصوصا ما يتعلق بدوال الشبكات وخلافه.
الان ننتقل الى الوظيفة الثانية..
دالة ReadFile:
غالبا ما تستخدم هذه الدالة مع CreateFile حيث تنفذ في حال تم اعادة مقبض للملف ان وجد, تستخدم هذه الدالة لقراءة كمية من البيانات من الملف المحدد بمقبضه. تملك هذه الدالة الشكل العام التالي:
BOOL ReadFile( HANDLE hFile, // handle of file to read LPVOID lpBuffer, // address of buffer that receives data DWORD nNumberOfBytesToRead, // number of bytes to read LPDWORD lpNumberOfBytesRead, // address of number of bytes read LPOVERLAPPED lpOverlapped // address of structure for data );
يحدد البارامتر hFile مقبض الملف الذي قمنا بفتحه بواسطة CreateFile, يحدد البارامتر lpBuffer المخزن الذي سيتم تخزين البايتات المقروءة فيه, يحدد البارامتر nNumberOfBytesToRead عدد البايتات المراد قراءتها من الملف والتي يتم قراءتها ابتداء من العنوان المحدد بمؤشر الملف الذي يدل على بدايته, يحدد البارامتر lpNumberOfBytesRead عدد البايتات المقروءة فعليا من الملف, واخيرا, البارامتر الاخير يمكن ان يحمل القيمة NULL في حال لم يكن الملف قد تم الوصول الى مقبضه من خيار FILE_FLAG_OVERLAPPED فان كان الاخير هو المستخدم وجب تحديد buffer ليتم تخزين بيانات بنية التراكب فيها.
نستخدم الدالة السابقة في برنامجنا بالشكل التالي:
invoke ReadFile, addr hfile, addr fbuffer, 10, addr bnum, NULL
اود الاشارة هنا الى البارامتر الثالث, كما نرى فاننا نقوم بتحديد عدد البايتات التي نريد قراءتها اما بشكل مباشر بقيمة عشرية مثلا, او يمكننا طبعا استخدام متحول يحمل هذه القيمة ونشير اليه بواسطة addr bytes_2_read مثلا, ايضا فيما يخص البارامتر الرابع, يجب ان نحدد بارامتر يتم فيه تخزين عدد البايتات التي تم قراءتها من قبل ReadFile.
دالة CloseHandle:
نستخدم هذه الدالة في اغلاق مقبض الملف الذي قمنا بانشاءه في البداية (او فتحه) للوصول اليه.. لا نستخدم هذه الدالة الا بعد الانتهاء من التعامل مع الملف.
الشكل العام لها بسيط جدا:
BOOL CloseHandle( HANDLE hObject // handle to object to close );
نستدعي الدالة بعض ان نقوم بتحديد قيمة مقبض الملف الذي نريد اغلاقه.. طبعا هذه الدالة تستخدم لاغلاق المقابض للعديد من العناصر وليس فقط الملفات لكن استخدامها في برنامجنا هو المذكور.
invoke CloseHandle,addr hfile
دالة MessageBox:
هذه الدالة من اكثر الدوال شيوعا في الاستخدام في البرمجة, اعتقد ان استخدامها واضح من الاسم وهو اظهار رسالة ما على الشاشة, سنستخدمها في برنامجنا لنقوم باظهار عدد من المحارف التي قمنا بقراءتها من الملف على الشاشة, استخدامها العام هو التالي:
int MessageBox( HWND hWnd, // handle of owner window LPCTSTR lpText, // address of text in message box LPCTSTR lpCaption, // address of title of message box UINT uType // style of message box );
يحدد في البارامتر الاول مقبض النافذة الاب التي تعود الرسالة اليها, في حال تحديد القيمة NULL لهذا البارامتر فان الرسالة تعود الى النافذة النشطة حاليا, البارامتر الثاني يشير الى النص المراد اظهاره على الشاشة, البارامتر الثالث يشير الى عنوان لهذه الرسالة, اخيرا, البارامتر الاخير يشير الى شكل هذه الرسالة اي ما سيظهر فيها من ازرار, OK فقط, YES / NO, Retry / Ignore / Abort .. الخ, بشكل شائع يستخدم الشكل OK فقط لهذه الرسائل..
invoke MessageBox, NULL, addr lpText, addr lpCaption, MB_OK
لربما قد ترتبك فيما يخص اسماء المتحولات التي قمت باستخدامها وبين الشكل العام للاستخدام.. ان اسم المتحول lpText مثلا لا يشير الى انك ملزم بهذا الاسم, يمكنك استخدام ما شئت من اسماء للاشارة الى النص الذي قمت بوضعه في .DATA لكني فضلت استخدام هذا الشكل فقط لتوضيح كيفية الاستخدام لا اكثر.
ماذا نحتاج للقيام به كبداية لانشاء ملف EXE؟
ان فتح ملف EXE بشكل صحيح واغلاقه يختلف عما كنا نقوم به فيما يخص تطبيقات 16بت حيث ان تطبيقات 32بت لها شكل معين في الاستدعاء.. عندما نقوم بانشاء ملف EXE وفتحه فان اول دالة يتم تنفيذها هي دالة GetModuleHandleA -- هذه الدالة غير ضرورية دوما, قد لا تريد استخدامها ويمكنك ذلك لكن الغاية منها توفير بعض المعلومات الضرورية من اجل استدعاء وظائف اخرى في البرنامج. بعد ان يتم تنفيذ هذه الدالة يتم الانتقال في التنفيذ الى الدالة التي تليها وهي تكون الدالة التي تريد منها اظهار رسالة او قراءة ملف او عمل اخر تحدده انت.. وفي اخر البرنامج يتم الانهاء بواسطة دالة تسمى ExitProcess -- لناخذ الان نظرة اقرب على هاتين الدالتين..
دالة GetModuleHandle:
تستخدم هذه الوظيفة في ارجاع مقبض ملف تم تخطيطه في الذاكرة -- الاستخدام العام لهذه الدالة هو:
HMODULE GetModuleHandle( LPCTSTR lpModuleName // address of module name to return handle for );
اي ان البارامتر lpModuleName يشير الى اسم الملف المراد ارجاع مقبض له شريطة ان يكون قم تم تخطيطه في الذاكرة, ان اردت استخدام هذه الدالة مع برنامجك فانت لست بحاجة لتحديد اسم ومسار البرنامج, استخدم القيمة NULL للبارامتر وسيتم ارجاع مقبض البرنامج الحالي.
invoke GetModuleHandle, NULL
دالة ExitProcess:
تستخدم هذه الوظيفة في انهاء واغلاق البرنامج وازالته من الذاكرة - تملك هذه الدالة بارامتر واحد وستلاحظ ان اغلب التطبيقات ان لم تكن جميعها تقوم باستخدام القيمة "0" لهذا البارامتر والذي يقوم باستخدام القيمة الافتراضية لانهاء التطبيق - بينما كان من المفترض (وليس شرطا) ان يتم استخدام الدالة GetExitCodeProcess والتي تعيد القيمة اللازمة لانهاء التطبيق.
تنظيم البرنامج وكتابته:
سنقوم كما ذكرت في بداية المقال باستخدام Masm32 v8.2 مع RadAsm لذا قم بفتح مشروع جديد في radasm (او ملف asm جديد فقط ان اردت) وستكون الخيارات هي التالية:
1- بعد ان نختار File -> New Project نقوم بتحديد المجمع المطلوب وهو masm ونحدد خيار Win32 App (no res)
2- نقوم بتحديد اسم للمشروع المطلوب واسم لمجلد المشروع المطلوب وليكن "MyFirst_Win32Asm_Project"
3- لن نقوم باختيار اي بنية خاصة اوتوماتيكية للملف المطلوب لذا يمكن تجاوز نافذة Template باختيار none وباي حال فان (no res) تلغي وجود اي template للبرنامج :P
4- فيما يخص الملفات التي نريد انشاءها للمشروع سنبقي فقط على الخيارات Asm و Inc
5- في النافذة الاخيرة Make سنبقي على الخيارات كما هي ونختار Finish لانشاء الملفات المطلوبة.
ستلاحظ ظهور ملفات باسماء MyFirst_Win32Asm وبلاحقتي .asm و .inc في نافذة المشروع project الى اليمين.
فيما يخص برنامجنا فاننا نقوم بكتابته في الملف الذي يحمل اللاحقة .asm (طبعا [:) اما ملف .inc فنستخدمه لكتابة اسماء المكتبات التي نطلب منها وظائف فيه وللبيانات المستخدمة في البرنامج.. فقط للتنظيم :)
فيكون برنامجنا بالشكل التالي:
.386 .model flat, stdcall option casemap: none include MyFirst_Win32Asm.inc .code main proc far invoke GetModuleHandle, NULL; return the program handle mov handle, eax ; store the handle in 'handle' ; try to open an existing file, if it fails we'll simple show an error message that the file doesn't exist.. for the time being :) invoke CreateFileA, addr file_name, GENERIC_READ and GENERIC_WRITE, FILE_SHARE_READ and FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL cmp eax, -1 ; see if the file exists, '-1' = 'FFFFFFFFh' so we are checking if the file doesn't exist je @no_file ; if the JE instruction was ignored then the file exists and EAX holds the handle mov file_handle, eax ; so we store the handle in 'file_handle' invoke ReadFile, file_handle, addr input_buffer, 25, addr bnum, NULL test eax, eax ; see if failed to read the contents je @no_read invoke CloseHandle, file_handle; close the file we just opened to read invoke MessageBox, NULL, addr input_buffer, addr asm_is_gr8, MB_OK jmp _@ @no_file: invoke MessageBox, NULL, addr no_file, addr error, MB_OK jmp _@ @no_read: invoke MessageBox, NULL, addr no_read, addr error, MB_OK _@: invoke ExitProcess, 0 main endp end main
اما فيما يخص البيانات وخلافه فراجع الملفات المرفقة ايضا للاطلاع على كامل ملفات المشروع..
ماذا لو اردنا تنقيح البرنامج؟
لنفرض اني واجهت خطا ما عند تشغيل برنامجي, ولم ادر اين او ما السبب, فاردت تنقيحه بواسطة برنامج مثل olly.. في برنامجنا سيظهر التنقيح بالشكل التالي:

ما يميز تطبيقات الاسمبلي عن غيرها انها تعمل على مبدا what-you-write-is-what-you-get, دون زيادة او نقصان, لاحظ في الصورة السابقة كيف ان الكود الذي قمنا بكتابته تم عرضه كما هو الامر الذي يسهل على المبرمج تدارك اخطائه وفهم البرنامج بدون اي تعقيد..


اخبرني الان "Isn't assembly just gr8؟" :)
اسئلة؟


