يبدو ان موضوع فتح الملفات النصية والتعديل فيها وانشاؤها امر يطرح كثيرا في الكليات التي تدرس لغة الاسمبلي :)
لذا سنقوم في هذا الدرس بمناقشة كيفية كتابة تطبيق ببيئتي 16bit و 32bit يقوم بانشاء ملفات نصية / فتحها / التعديل فيها بالكتابة / الحفظ والاغلاق وارجو ان يغطي هذا الموضوع الحالية والتي لا بد وان تطرح في المستقبل حوله لجميع طلاب الكليات والمعاهد التقنية.
التطبيق ببيئة 16bit:
بما ان هذه البيئة تفرض بعض العوائق دعونا نستعرض اولا ماهية التعليمات والاوامر التي سنقوم باستخدامها وخصوصا المقاطعات وسنتناول في هذا الموضوع مقاطعة 21h -- (قد لا تعمل هذه المقاطعة بالشكل الصحيح على انظمة مثل XP -- النظام الذي اعمل عليه حاليا هو Windows 2003 Enterprise ويماثل العمل عليه العمل على 2k+XP اي ان المشاكل التي ستتعرضون اليها من المفترض ان اغطيها في المقال واي خلل خارجي ارجو طرحه لمناقشته).
ما هي المقاطعات؟
المقاطعات Interrupts يمكن النظر اليها على انها مجموعة من الوظائف والتي تجعل عملية البرمجة اسهل, فبدلا من ان نقوم بكتابة كود ليقوم بطباعة محرف ما على الشاشة يمكننا ان نقوم باستدعاء هذه المقاطعة لتنفيذ هذا الامر بدلا من منا!
تقسم المقاطعات الى قسمين, مقاطعات برمجية Software interrupts ومقاطعات صلبة Hardware interrupts فالاولى تختص بالتعامل مع القرص وبعض الاجهزة المعينة والاخيرة تختص بالتعامل مع اجهزة اخرى مثل البيوس وخلافه. ما يهمنا حاليا هو المقاطعات البرمجية وبالتحديد المقاطعة المشهورة 21h.
يتم استدعاء اي مقاطعة بتمرير عدد من البارامترات المعينة والتي تحدد الوظيفة المطلوبة من المقاطعة اجراؤها قبل استدعائها -- هذا النوع من تمرير البارامترات ملزم بشكل معين حيث يتم دفع هذه البارامترات وتخزينها في مسجلات الاغراض العامة مثل AX, BX, CX وقد يتم استخدام بعض المسجلات الاخرى مثل DX لتخزين قيم معينة فيها كالسلاسل النصية.. اخيرا يتم استدعاء المقاطعة بالشكل التالي:
int NUM
حيث ياخذ الـ NUM قيما بين 0 - 255 (اي بالتمثيل الست عشري 00h - 0FFh) -- يجدر الاشارة هنا الى ان استدعاء المقاطعة يتم باستخدام الرقم بالتمثيل العشري لها اي لا يمكن استخدام النظام العشري في هذه الحالة والسبب واضح في البرمجة بشكل عام!
قد يتبادر الى الذهن ان هناك فقط 256 وظيفة (مقاطعة) يمكن استخدامها لكن هذا خطا, فكل مقاطعة تملك عددا من الخيارات التي بامكانها القيام بتنفيذها sub-function ويتم تحديد هذه الوظيفة الفرعية من خلال تخزين قيمة (رقم) الوظيفة في المسجل AH (حصرا) قبل القيام باستدعاء الوظيفة..
تملك كل مقاطعة عددا من الوظائف الفرعية يصل عددها الى 256 ايضا وبالتالي نرى اننا نملك 256 * 256 = 65536 وظيفة يمكننا استخدامها لتنفيذ الاعمال المطلوبة منا! لكن هل نحن بحاجة الى معرفة كل هذه الوظائف واستخداماتها؟ اعتقد ان هذا الامر ضرب من المستحيل.. ناهيك عن الجنون :D
يمكن الحصول على قائمة بكامل المقاطعات المتوفرة مع خياراتها من خلال الموقع التالي حيث تعد قائمة Ralf Brown اكبر واشمل قائمة للمقاطعات المتوفرة مع شرح استخدامها بالتفصيل شبه الممل:
http://www.cs.cmu.edu/~ralf/files.html
ماذا نحن بحاجة لمعرفته عن مقاطعة 21h؟
توفر لنا المقاطعة المذكورة امكانية فتح وتحرير الملفات بصورة سهلة جدا حيث تملك عددا من الخيارات او ما اسميناه الوظائف الفرعية الخاصة بالتعامل مع الملفات وهذه الوظائف هي:
- انشاء الملف: 3Ch
- فتح الملف: 3Dh
- الكتابة في الملف: 40h
- اغلاق الملف: 3Eh
- حذف الملف: 41h
سنقوم الان باخذ صورة اوضح عن استخدامات هذه الوظائف الفرعية ومناقشتها كل على حدة.
مقاطعة انشاء الملفات 213Ch:
تقوم هذه المقاطعة بانشاء الملف المطلوب في المجلد الحالي للبرنامج وتعيد قيمة مقبض الملف file handle في المسجل AX في حال نجاح التنفيذ وتكون القيمة المرتجعة في مسجل راية الحمل Carry Flag او اختصارا CF مساوية الصفر - او تعيد قيمة 1 في مسجل CF دليل حدوث خطا ما ويتم ارجاع رقم الخطا في المسجل AX (ويكون اما 3, 4, 5 ولكل منهم دلالة سنطلع عليها فيما بعد) -- بالتالي نرى انه من المهم تحديد فيما ان وجد خطا ما للتعامل معه حسب الضرورة ويمكن تطبيق هذا باستخدام تعليمة مثل JC (Jump if Carry) والتي يتم تنفيذها في حال كانت القيمة المخزنة في المسجل CF مساوية الواحد.
عندما يتم تنفيذ هذه الوظيفة بنجاح فانه في حال وجود ملف بنفس الاسم في المجلد الحالي للبرنامج فان حجم الملف يتم ارجاعه للصفر والغاء جميع البيانات فيه كما لو انك قمت بحذفه واعادة انشائه لذا يجب الانتباه هنا الى التحقق من وجود الملف في نفس المجلد او اتخاذ اسم جديد للملف للابقاء على الملف السابق دون المساس به. سنقوم بمناقشة معظم هذه الحالات فيما بعد.
- كيف نقوم باستخدام هذه الوظيفة؟
يتم اولا طلب الوظيفة بدفع القيمة 3Ch الخاصة بالوظيفة الى المسجل AH (كما اوضحنا سابقا) ومن ثم يتم دفع قيمة الى المسجل CX تحدد هذه القيمة خصائص الملف المراد انشائه فاما ان يكون للقراءة فقط او عادي او ملف نظام او مخفي .. الخ
فيما بعد يتم دفع اسم الملف الى المسجل DX ويتم طلب المقاطعة 21h بشكل عادي.
فيما يلي شكل طلب تنفيذ هذه الوظيفة من المقاطعة 21h:
mov ah, 3Ch ;-------------------- ; Bitfields for file attributes: ; Bit(s) Description (Table 01401) ; 2-0 create mode ; 000 normal ; 001 read-only ; 010 hidden ; 100 system ;-------------------- mov cx, 0 ; 000000b lea dx, file_name int 21h
يقوم الكود السابق بانشاء الملف الذي اسمه تم تخزينه في الدليل file_name والذي يتم تحميله الى المسجل DX باستخدام التعليمة LEA والتي هي اختصار لـ Load Effective Address والتي تقوم بتحميل العنوان الخاص بدليل ما في احد المسجلات حيث تقاس الازاحة اوتوماتيكيا ويتم تخزين العنوان RVA الخاص بذلك الدليل في المسجل المطلوب -- لا باس ان لم تكن تعرف ما معنى RVA فليس مطلوبا منك كبداية ان تعرف ماهيته وان اردت الاطلاع اكثر راجع الدروس المتقدمة الخاصة بامن التطبيقات.
بالنسبة لخصائص الملف فكما يبدو من الشكل السابق فانه يتم التحكم بالخصائص وفقا لموضع البت الذي يحمل القيمة 1 والذي يحدد الخاصة المطلوبة -- فنحن قمنا بحفظ القيمة 0 في المسجل CX وبالتالي نكون قد طلبنا انشاء ملف عادي لا يحمل اي خاصية بينما لو اردنا مثلا انشاء ملف للقراءة فقط امكننا هذا بدفع القيمة 1b الى المسجل CX حيث نلاحظ اننا قمنا بوضع القيمة 1 في البت (الخانة 0 -الاولى-) -- القيمة السابقة تمثل فعليا بالشكل 0000001 وتم اهمال الاصفار على اليسار لانها لا تؤثر على التنفيذ -- ايضا يرجى الانتباه الى اننا قمنا بوضع الدليل "b" على يمين البت اشارة الى ان هذه القيمة تمثل بالنظام الثنائي وكان بامكاننا وضعها 1h او حتى 1 (عشريا) لكن كي يتم التوضيح ولانه سيكون هناك اختلاف فيما يخص انشاء ملف يحمل خاصية القراءة فقط و ملف نظام ومخفي حيث ستكون القيمة 111b او 7h او 7.
مقاطعة فتح الملف 213Dh:
تقوم هذه الوظيفة بفتح ملف عن طريق اسم الملف الذي يؤخذ من المسجل DX حيث نقوم اولا بدفع رقم الوظيفة المطلوبة الى المسجل AH ومن ثم نقوم بتحديد الوضع الذي نريد فتح الملف به (للقراءة, الكتابة, كلاهما) بدفع قيمة معينة للمسجل AL ونقوم بحفظ اسم الملف في DX كم نفذنا سابقا - عند تنفيذ الوظيفة بالشكل الصحيح فانه يتم ارجاع مقبض الملف في المسجل AX وتكون قيمة CF مساوية للصفر وفي حال العكس يتم ارجاع رقم الخطا في AX وتحمل CF القيمة 1 -- امر اخر لا ادري ان كان صحيحا او انه بسبب عدم التوافقية الكاملة مع النظام المستخدم, تقوم هذه الوظيفة عند تنفيذ الملف بانشاء الملف ان لم يكن موجودا اصلا اي انها تقوم بعمل مشابه لوظيفة 3Ch وبالتالي قد يكون بالامكان الاستغناء عن السابقة ان كان هذا الامر هو نفسه على جميع الانظمة.. يكون الشكل الاخير للاستدعاء:
mov ah, 3Dh ; Bitfields for access and sharing modes: ; Bit(s) Description (Table 01402) ; 2-0 access mode ; 000 read only ; 001 write only ; 010 read/write mov al, 10b ; can also be '2' or '2h' lea dx, file_name int 21h
الكتابة في الملفات باستخدام 2140h:
تقوم هذه الوظيفة بنقل عدد من البايتات (الاحرف) المحدد طولها في CX وتقوم بحفظها في ملف ما نقوم بتحديد المقبض الخاص به في المسجل BX بينما نقوم بتحديد السلسلة التي نريد كتابتها في الملف في المسجل DX واخيرا يتم تحديد رقم الوظيفة في AH - عند التنفيذ الصحيح: AX = عدد البايتات المكتوبة فعليا في الملف / CF = 0 -- عند حدوث اي خطا: AX = رقم الخطا (5, 6) / CF = 1
وفيما يلي شكل يمثل كيفية تنفيذ ذلك:
mov ah, 40h ; can also be "mov ax, 40h" mov bx, file_handle mov cx, string_len lea dx, buffer int 21h
اغلاق الملف المفتوح بواسطة 213Eh:
تقوم هذه الوظيفة باغلاق ملف ما عن طريق وضع رقم الوظيفة في AH - مقبض الملف في BX - واستدعائها بواسطة المقاطعة 21h -- يمثل ذلك بالصورة التالية:
mov ah, 3Eh mov bx, file_handle int 21h
حذف ملف ما بواسطة 2141h:
تقوم هذه الوظيفة بحذف ملف موجود في نفس المجلد الحالي للبرنامج عن طريق وضع رقم الوظيفة في AH - حفظ اسم الملف في DX ومن ثم تنفيذ 21h -- يكون شكل التنفيذ كما يلي:
mov ah, 41h lea dx, file_name int 21h
قائمة بانواع الاخطاء اثناء التنفيذ:
00h (0) no error 01h (1) function number invalid 02h (2) file not found 03h (3) path not found 04h (4) too many open files (no handles available) 05h (5) access denied 06h (6) invalid handle 07h (7) memory control block destroyed 08h (8) insufficient memory 09h (9) memory block address invalid 0Ah (10) environment invalid (usually >32K in length) 0Bh (11) format invalid 0Ch (12) access code invalid 0Dh (13) data invalid 0Eh (14) reserved 0Eh (14) (PTS-DOS 6.51+, S/DOS 1.0+) fixup overflow 0Fh (15) invalid drive 10h (16) attempted to remove current directory 11h (17) not same device 12h (18) no more files
طبعا هذه قائمة صغيرة بالاخطاء التي قد تحصل لكنها تغطي ما قم تستخدمه من مقاطعات في الوقت الراهن وان اردت الاطلاع على كامل القائمة ما عليك سوى الاطلاع على ملف interrup.g في قائمة Ralf تحت وظيفة 2159h.
توسيع دائرة عمل البرنامج بواسطة عدد من الوظائف الاخرى:
كما نرى فان البرنامج السابق يقوم بكتابة بيانات معينة في الملف الذي قمنا بانشاؤه -- لكن ماذا لو اردنا ان نقوم بكتابة بيانات بانفسنا في ذلك الملف اي ان البيانات غير ثابتة ويقوم المستخدم بادخالها؟
في هذه الحالة نحن بحاجة الى استخدام الوظيفة 0Ah التي تقوم بانتظار المستخدم لادخال بيانات ويتم انهاؤها عند النقر على زر Enter (انهاؤها يعني انتقال التنفيذ الى الوظيفة التالية).
- كيف نقوم اذن باستخدام هذه الوظيفة؟
تستخدم وظيفة 0Ah لكتابة بيانات ادخلها المستخدم عن طريق وضع رقم الوظيفة في AH - تحديد عنوان الذاكرة او المخزن الذي سيتم الكتابة فيه بواسطة DX ومن ثم استدعاء الوظيفة بالشكل التالي:
mov ah, 0Ah lea dx, buffer int 21h
امر اخر -- البرنامج السابق يقوم بالكتابة في ملف قمنا بتحديد اسمه سابقا لكن ماذا لو اردنا القراءة من ذلك الملف, او تحديد فيما اذا كان موجودا سابقا لتجنب استبداله بملف اخر فارغ, كيف يكون السبيل الى ذلك؟
سنقوم لتنفيذ هذا الامر باستخدام 3 وظائف هي 4Eh التي تقوم بالبحث عن ملف ما على القرص ووظيفة 3Fh للقراءة من الملف ان وجد وعدم استبداله بملف اخر واخيرا سنتعرف على وظيفة 56h التي تقوم بتغيير اسم الملف.
- استخدام الوظيفة 4Eh للبحث عن الملف:
يتم استخدام هذه الوظيفة بوضع رقمها في المسجل AH - تحديد خصائص الملف المراد البحث عنه في CX - تحميل DX باسم الملف المراد البحث عنه ومن ثم استدعاؤها بالشكل التالي:
mov ah, 4Eh mov cx, 0 lea dx, file_name int 21h
عند التنفيذ الصحيح للوظيفة تقوم بملئ منطقة DTA الخالية في FCB عند الازاحة 80h ببعض المعلومات حول الملف مثل مواصفاته, وقت الانشاء, تاريخ الانشاء, حجم الملف, اسم الملف.. لا باس ان لم تكن تعرف ما هي DTA و FCB لانه سيكون موضوع المقال القادم.
طبعا كما سبق فان التنفيذ الصحيح يعني ملئ CF بالقيمة 0 والعكس صحيح.
- استخدام الوظيفة 3F للقراءة من الملف:
يتم استخدام الوظيفة بوضع رقمها في AH - تحميل مقبض الملف في BX - عدد البايتات المراد قراءتها في CX - وعنوان المنطقة (المخزن) الذي سيتم فيه تخزين البيانات المقروءة في DX ومن ثم استدعاؤها بالشكل:
mov ah, 3Fh mov bx, file_handle mov cx, 512 lea dx, buffer int 21h
في حال نجاح التنفيذ يتم تحميل عدد البايتات المقروءة في AX و صفر في CF وفي حال فشله.. :P
- استخدام الوظيفة 56h لتغيير اسم الملف:
تقوم هذه الوظيفة بتغيير اسم الملف عن طريق وضع رقم الوظيفة في AH - تحميل DX بالاسم الحالي للملف - تحميل DI بالاسم الجديد للملف وتنفيذها بالشكل:
mov ah, 56h lea dx, old_name lea di, new_name int 21h
الشكل الكامل لكود برنامج التعامل مع الملفات:
; Copyrighted (c) 2005 ArabTeam2000 - All rights reVersed :D ; w h a t a r e y o u g o i n g t o d o a b o u t i t ;----------------------------------------------------------- ; This lame program does the following: ; 1- Takes the name of file to open/create ; 2- Searchs current directory for the file: ; a) found: Asks the user to choose wheather to ; rename, delete & create another, read & display the contents on screen. ; b) not found: creates the file and open it for write access ;----------------------------------------------------------- ; I live on your money so be kind and send me some }:] ;----------------------------------------------------------- .model small .code org 100h start: jmp main ; this program uses FCB techniques - explaination later on :p name_skelton label byte max_len db 55 ; avoids buffer overflow & arbitrary code execute - Qnix;) act_len dw ? file_name db 50 dup (?) new_name_skel label byte new_max_len db 55 new_act_len dw ? new_file_name db 50 dup (?) ext db '.txt', 0 ask db '(1) rename (2) delete & create (3) read & display', 13, 10, '$' handle dw ? in_buff db 250 dup (?) file_buffer label byte maxl db 255 actl dw ? o_buff db 250 dup (?) op_err db 'Error openning file', 0 re_err db 'Error reading file', 0 ren_err db 'Error renaming file', 0 de_err db 'Error deleting file', 0 cr_err db 'Error creating file', 0 wr_err db 'Error writing file', 0 cl_err db 'Error closing file', 0 main proc near call get_file ; calls sub-routine get_file call loc_file ; calls sub-routine loc_file call take_action ; calls sub-routine take_action ret main endp get_file proc near ; waits the user to enter the file name and stores it in file_name mov ah, 0Ah lea dx, name_skelton int 21h ; now because the file name needs extension we append the ext here ; using the act_len and principle of OFFSET mov ax, act_len ; al holds the length of entered name lea bx, file_name ; loads addr of 'file_name' block in bx add bx, ax ; locates the end of the entered name mov si, word ptr [ext] ; moves two bytes from 'ext' to si mov [bx], si ; appends 1st two bytes '.t' mov si, word ptr [ext]+2 ; moves last bytes from 'ext' to si mov [bx]+2, si ; appends last two bytes 'xt' ; by the end of this procedure we have 'file_name.txt' stored in memory :) ret ; return to main proc get_file endp loc_file proc near ; search for the file in current directory mov ah, 4Eh mov cx, 0 lea dx, file_name int 21h jc nFound mov ax, 0 ret nFound: mov ax, 1 ret ; by the end of this proc we have either 0 or 1 in ax to determine the results loc_file endp take_action proc near ; option explicit :]] cmp ax, 1 je file_not_found file_found: mov ah, 09h ; service 09 (display characters) lea dx, ask int 21 mov ah, 0 ; service 00 (read a character) int 16h ; execute 16h interrupt cmp al, 31h je rename cmp al, 32h je delete open: mov ax, 3D00h ; service 3D (open file for read-only) lea dx, file_name int 21h jc open_err mov handle, ax read: mov ah, 3Fh mov bx, handle mov cx, 250 lea dx, o_buff int 21h jc read_err display: mov ah, 09h lea dx, o_buff int 21h jmp close rename: mov ah, 0Ah lea dx, new_name_skel int 21h mov ax, new_act_len lea bx, new_file_name add bx, ax mov si, word ptr [ext] mov [bx], si mov si, word ptr [ext]+2 mov [bx]+2, si mov ah, 56h lea dx, file_name lea di, new_file_name int 21h jc rename_err jmp exit delete: mov ah, 41h lea dx, file_name int 21h jc delete_err create: mov ah, 3Ch mov cx, 0 lea dx, file_name int 21h jc create_err mov handle, ax recieve: mov ah, 0Ah lea dx, in_buff int 21 write: mov ah, 40h mov bx, handle mov cx, actl lea dx, o_buff int 21h jc write_err close: mov ah, 3Eh mov bx, handle int 21h jc close_err jmp exit open_err: mov ah, 09h lea dx, op_err int 21h jmp exit read_err: mov ah, 09h lea dx, re_err int 21h jmp exit rename_err: mov ah, 09h lea dx, ren_err int 21h jmp exit delete_err: mov ah, 09h lea dx, de_err int 21h jmp exit create_err: mov ah, 09h lea dx, cr_err int 21h jmp exit write_err: mov ah, 09h lea dx, wr_err int 21h jmp exit close_err: mov ah, 09h lea dx, cl_err int 21h jmp exit file_not_found: jmp create exit: ret take_action endp end start
رغم ان الكو السابق من المفترض ان يكون كاملا وان يعمل دون اخطاء لكن الخطا وارد خصوصا اني غير قادر على تشغيل الملف التنفيذي واختباره لاني اعمل على نظام لا يدعم جميع المقاطعات, اعتقد ان الخطا سيكون في حجم الملف ومحدودية احجام ملفات com والله اعلم -- لكن المهم هو فهم اليات الاستخدام والعمل على تطوير الكود السابق لتغطية كافة الاخطاء وادخال اجرائيات فحص جديدة او اجرائيات تزيد من فعالية البرنامج او حتى التخفيف من التعليمات المستخدمة بوضع الوظائف المتكررة في اجرائيات مستقلة واستدعاؤها عند الحاجة -- لا احتاج للقول اني توقفت عن كتابة تطبيقات 16بت منذ زمن طويل لذا يعتبر كتابة تطبيق (وان احتوى اخطاء :P) يستخدم مسجلات 16بت ومقاطعة 21h دون ان افقد صوابي انجاز كبير لي p;
- ملاحظة اخيرة بخصوص اختبار الكود على بقية الانظمة مثل 9x - يفضل ان تقوم بتدريب نفسك على كتابة البرنامج بعد فهم الدرس ومحاولة ادخال افكار جديدة ومن ثم اختباره -- للاسف مستخدمي Emu8086 احمل اخبار سيئة لكم وهي انه لا يدعم كافة المقاطعات الواردة في هذا المقال وبالتالي "انسوه :P" -- يمكن استخدام masm16 لكن الملف الناتج في النهاية .exe وسيكون بحجم اكبر من المطلوب بسبب الـ 512bit زيادة :P بالتالي سنقوم بتحويله الى ملف .com باستخدام برنامج EXE2BIN ويمكن ايجاده في المنتدى او من خلال هذا الموقع -- استخدامه سهل للغاية حتى اخي سامي الصغير بيعرف كيف يشتغل على masm16 و exe2bin :P
http://www.ibiblio.org/pub/micro/pc-stuff/...bin/exe2bin.exe
ماذا بشان التطبيق ببيئة 32bit؟
بعد ان انتهيت من كتابة المقال وجدت بانه سيجعل القارئ يهرب من الصفحة الاولى :P لذا سنؤجل نقاشنا حول كتابة التطبيق السابق ببيئة 32بت الى الدرس القادم - يليه فهم كتلة الـ FCB - ومن ثم ننتقل الى التعامل مع الشاشة والتوسع في استقبال المحارف من الكيبورد... متمنيا ان انتهي من 16بت بسرعة D;
اسئلة؟
