أمثلة عامة:
نواصل درسنا الأخير في لغة التجميع، وهذه المرة مع الأمثلة الخاصة بأغلب وأهم التعليمات المستخدمة في هذه اللغة. لكن قبل ذلك يلزمنا البرنامج أو البرامج المناسبة لتطبيق ذلك، لهذا أنصح المهتم بهذه الدروس أن يقوم بتنزيل المصرّف TASM (أو MASM) وبرنامج TLINK (أو LINK من مايكروسفت).
فيم يخص التطبيقات المنجزة فلقد جربتها بـ TASM و LINK (أي أخلطت بين الإصدارات)، والسبب هو أن منتج بورلاند TLINK يرسل رسائل الخطأ والتي مفادها أن العمل يجري خارج الذاكرة، وهذا ما يستلزم تعديلات أخرى نحن في غنى عنها، لهذا سنقوم بعملنا مستعملين البرنامجين TASM و LINK والذين ستجدهما كتوفرين معا في ملف مضغوط واحد.
عند كتابة البرنامج (الذي فرضناه FirstPrg)، قم بتنفيذ الخطوات الآتية:
Tasm FirstPrg // عملية تصريف البرنامج
link FirstPrg // عملية ربط تحكمات البرنامج
FirstPrg // تنفيذ البرنامج
المثال الأول:
لفهم العملية جيدا، فإنه لدينا هذا البرنامج، والذي يقوم بطبع جملة Hello World على الشاشة:
;------------------------------------------------------------------
; The First Program ;
; بعد الفاصلة المنقوطة يمكننا إدراج تعليقات لتوضيح الفكرة
; هذه التعليقات سيهملها المصرف لاحقا
;------------------------------------------------------------------
; قطاع البيانات
data16 segment public ; التصريح بقطاع البيانات
Hello db 'Hello World!',13,10,'$' ; متغير
data16 ends
; ------------------------------------------------------------------
; قطاع الكود
code16 segment public ; التصريح بقطاع الكود
assume cs:code16, ds:data16, ss:stack16
start16:
; تحضير قطاع البيانات
mov ax, data16 ; ax = data16
mov ds, ax ; ds = ax
; طباعة نص على الشاشة
mov dx, offset Hello ; helloفرع الذاكرة الخاص بالجملة
mov ah, 09h ; تسمح بطباعة النص على الشاشة 09h
int 21h ; مقاطعة الدوس
; إنهاء البرنامج
mov al, 00h ; ولا مشكلة
mov ah, 4ch ; تسمح بمغادرة البرنامج 4ch
int 21h ; مقاطعة الدوس
code16 ends ; نهاية النص
; ------------------------------------------------------------------
; قطاع المكدس
stack16 segment stack
db 200h dup (?)
stack16 ends
end start16 ; start16 نهاية البرنامج
; ------------------------------------------------------------------
تحليل البرنامج:
تشكل لنا القطاعات (segments) هيكل البرنامج. عامة يوجد قطاع الكود، قطاع البيانات، وقطاع المكدس (stack). قطاع الكود يستعمل للتعليمات (instructions)، قطاع البيانات للمتغيرات، وقطاع المكدس لتعليمات المكدس نفسه كتعليمتي push و pop.
التصريح العام للقطاع:
“Name” segment [public|stack]
……… data instructions
“Name” ends
التعليمة assume (تكلّف) تخبر المصرف TASM بالاستعمال الحالي لسجل القطاع (segment register).
Code16 هو قطاع خاص بالكود، إذن قطاع الكود (CS) متكلّف بـ code16.
Data16 هو قطاع خاص بالبيانات، إذن قطاع البيانات (DS) متكلّف بـ Data16.
Stack16 هو قطاع خاص بالمكدس، إذن قطاع المكدس (SS) متكلّف بـ Stack16.
عند الإقلاع يقوم الدوس بتحضير (تهيئة) CS و SS أما DS فلا يتم تحضيره. إذن يجب عليك تحضير DS أو ستتعرض للمفاجئات.
Adapt the data register
Mov ax, data16 ; لأننا لا نستطيع الكتابةax نستعمل
Mov ds, ax ; محضّر ds حاليا
يعتبر قطاع المكدس (SS) وقطاع البيانات (DS) متخصصين بالمعطيات (البيانات). لكن قطاع المكدس هو منطقة ذاكرية مؤقتة للتعليمات pop و push، لهذا فإنه لا يوجد إلا فرع ذاكري واحد.
قطاع البيانات يمكنه احتواء متغيرات وثوابت. في هذا المثال، ‘hello’ تعتبر ثابت على شكل حزمة رموز (string). في المثال قمنا بوضع فرع الذاكرة (offset) الخاص بهذه الجملة في dx لأن مقاطعة الدوس تحتاج لفرع الذاكرة حتى يمكنها طباعة النص على الشاشة. 'hello' هي المتغير الأول والذي يملك فرعا ذاكريا (أقرب مرادف للـ offset) معدوما.
Declaration of data:
"name" db ? ; متغير من 8 بت: بايت
"name" dw ? ; متغير من 16 بت: كلمة
"name" dd ? ; متغير من 32 بت: كلمة_مضاعفة
"name" db ?,? ; 2 بايت
"name" dw ?,?,?,? ; 4 كلمات
"name" db 'Hello' ; سلسلة من البايتات
"name" dw 32 dup (?) ; 32 كلمة
جرب البرنامج ولاحظ النتيجة.
حاول تعديل بعض القيم أو تغييرها، لكن إحذر من الأعمال العشوائية.
إذا لم تفهم كل الذي قلناه، فلا تقلق، لأنه مع الوقت يكون بإمكانك تحقيق مرادك وبلغة التجميع.
المثال الثاني:
كما هو معلوم، فإن لغة التجميع لا تتوفر على التحكم "if"، لكن هناك تعليمات خاصة بالمعالج قد تحقق نفس المطلوب، هذه التعليمات هي القفز المشروط.
لنلاحظ هذا المثال البسيط:
cmp ax, 1
je Ok
mov bx, 3
Ok:
في المثال، إذا كان ax مساويا للواحد فإن المعالج يقفز إلى الوسم "OK"، وإلا فإنه لا يتم القفز. ax تتم مقارنته بالقيمة "1"، ونتيجة هذه المقارنة يتم وضعها في الرايات.
ومنه فإن القفز المشروط j[cas] يستخدم الرايات ليعرف ما إذا كان واجبا القفز أم لا.
المثال السابق يمكننا ترجمته إلى لغة السي على شاكلة:
if (ax == 1) goto Ok;
bx = 3;
Ok:
أو بطريقة أكثر سلاسة:
if (ax <> 1)
{
bx = 3
}
الآن سنعرض مثال كامل يمكننا تصريفه وتنفيذه:
;----------------------------------------------------------------------------;
; برنامج يحوي قفزا مشروطا
;----------------------------------------------------------------------------;
; من أجل تصريف البرنامج وتنفيذه
; tasm cond
; link cond
; cond
;----------------------------------------------------------------------------;
; ---------------------------------------------------------------------------;
; قطاع البيانات
data16 segment public
TimePrompt db 'Is it after noon (Y/N)?$'
GoodMorning db 13,10,'Good morning, World!',13,10,'$'
GoodAfternoon db 13,10,'Good afternoon, World!',13,10,'$'
Default db 13,10,'Greetings, World!',13,10,'$'
; "13,10" estle symbol dos تخص السطر الموالي
; أضف "13،10" ولاحظ النتيجة
data16 ends
; ---------------------------------------------------------------------------
; قطاع الكود
code16 segment public
assume cs:code16, ds:data16, ss:stack16
start16:
mov ax, data16
mov ds, ax
mov dx, offset TimePrompt
mov ah, 09h ; تسمح بطباعة النص على الشاشة 09h المقاطعة
int 21h ; مقاطعة الدوس
mov ah, 01h ; تسمح بكتابة رمز وحيد 01h المقاطعة
int 21h ; مقاطعة الدوس
or al, 20h ; ترغم الرمز أو الحرف على أن يظهر بأحرف صغيرة
cmp al, 'y' ; Yإذا ما تم كتابة الحرف
je IsAfternoon ; القفز إذا كان مساويا
cmp al, 'n' ; Nإذا ما تم كتابة الحرف
je IsMorning ; القفز إذا كان مساويا
mov dx, offset Default
jmp Display ; قفز غير مشروط
IsAfternoon:
mov dx, offset GoodAfternoon
jmp Display ; قفز غير مشروط
IsMorning:
mov dx, offset GoodMorning
Display:
mov ah, 09h ; تسمح بطباعة النص على الشاشة 09h المقاطعة
int 21h ; مقاطعة الدوس
mov al, 00h
mov ah, 4ch ; تسمح بإنهاء البنامج 4ch المقاطعة
int 1h ; مقاطعة الدوس
code16 ends ; نهاية الكود
; ---------------------------------------------------------------------------
; قطاع المكدس
stack16 segment stack
db 200h dup (?)
stack16 ends
end start16
تحليل البرنامج:
للقد أشرنا في تحليلنا الساب إلى أهم قطاعات الكود وخصائص كل قطاع، لهذا فإننا في هذا المثال لن نعيد ما قلناه، بل سنشير إلى الشيء الجديد فقط.
في قطاع البيانات (التصريح بالبيانات):
لو ترجع قليلا إلى الوراء فإنك ستلاحظ هذه الجملة:
"name" db 'Hello' ; سلسلة من البايتات
ومعناها أننا نقوم بالتصريح بحزمة رموز (string) تحمل الإسم name.
على ذات المنوال يمكننا ملاحظة التصريحات الواردة في البرنامج السابق:
TimePrompt db 'Is it after noon (Y/N)?$'
GoodMorning db 13,10,'Good morning, World!',13,10,'$'
GoodAfternoon db 13,10,'Good afternoon, World!',13,10,'$'
Default db 13,10,'Greetings, World!',13,10,'$'
لقد صرحنا بأربع حزم وهي TimePrompt، GoodMorning، GoodAfternoon و Default
وكل حزمة مهيأة (مستهلة) بجملة معينة تنتهي برمز الدولار. يعتبر هذا الرمز ضروريا في نهاية كل حزمة رموز، أما الرقمين 13,10 فتعني إضافة أسطر مثلما هو الحال مع الرمز (\n) في لغة السي.
نواصل درسنا الأخير في لغة التجميع، وهذه المرة مع الأمثلة الخاصة بأغلب وأهم التعليمات المستخدمة في هذه اللغة. لكن قبل ذلك يلزمنا البرنامج أو البرامج المناسبة لتطبيق ذلك، لهذا أنصح المهتم بهذه الدروس أن يقوم بتنزيل المصرّف TASM (أو MASM) وبرنامج TLINK (أو LINK من مايكروسفت).
فيم يخص التطبيقات المنجزة فلقد جربتها بـ TASM و LINK (أي أخلطت بين الإصدارات)، والسبب هو أن منتج بورلاند TLINK يرسل رسائل الخطأ والتي مفادها أن العمل يجري خارج الذاكرة، وهذا ما يستلزم تعديلات أخرى نحن في غنى عنها، لهذا سنقوم بعملنا مستعملين البرنامجين TASM و LINK والذين ستجدهما كتوفرين معا في ملف مضغوط واحد.
عند كتابة البرنامج (الذي فرضناه FirstPrg)، قم بتنفيذ الخطوات الآتية:
Tasm FirstPrg // عملية تصريف البرنامج
link FirstPrg // عملية ربط تحكمات البرنامج
FirstPrg // تنفيذ البرنامج
المثال الأول:
لفهم العملية جيدا، فإنه لدينا هذا البرنامج، والذي يقوم بطبع جملة Hello World على الشاشة:
;------------------------------------------------------------------
; The First Program ;
; بعد الفاصلة المنقوطة يمكننا إدراج تعليقات لتوضيح الفكرة
; هذه التعليقات سيهملها المصرف لاحقا
;------------------------------------------------------------------
; قطاع البيانات
data16 segment public ; التصريح بقطاع البيانات
Hello db 'Hello World!',13,10,'$' ; متغير
data16 ends
; ------------------------------------------------------------------
; قطاع الكود
code16 segment public ; التصريح بقطاع الكود
assume cs:code16, ds:data16, ss:stack16
start16:
; تحضير قطاع البيانات
mov ax, data16 ; ax = data16
mov ds, ax ; ds = ax
; طباعة نص على الشاشة
mov dx, offset Hello ; helloفرع الذاكرة الخاص بالجملة
mov ah, 09h ; تسمح بطباعة النص على الشاشة 09h
int 21h ; مقاطعة الدوس
; إنهاء البرنامج
mov al, 00h ; ولا مشكلة
mov ah, 4ch ; تسمح بمغادرة البرنامج 4ch
int 21h ; مقاطعة الدوس
code16 ends ; نهاية النص
; ------------------------------------------------------------------
; قطاع المكدس
stack16 segment stack
db 200h dup (?)
stack16 ends
end start16 ; start16 نهاية البرنامج
; ------------------------------------------------------------------
تحليل البرنامج:
تشكل لنا القطاعات (segments) هيكل البرنامج. عامة يوجد قطاع الكود، قطاع البيانات، وقطاع المكدس (stack). قطاع الكود يستعمل للتعليمات (instructions)، قطاع البيانات للمتغيرات، وقطاع المكدس لتعليمات المكدس نفسه كتعليمتي push و pop.
التصريح العام للقطاع:
“Name” segment [public|stack]
……… data instructions
“Name” ends
التعليمة assume (تكلّف) تخبر المصرف TASM بالاستعمال الحالي لسجل القطاع (segment register).
Code16 هو قطاع خاص بالكود، إذن قطاع الكود (CS) متكلّف بـ code16.
Data16 هو قطاع خاص بالبيانات، إذن قطاع البيانات (DS) متكلّف بـ Data16.
Stack16 هو قطاع خاص بالمكدس، إذن قطاع المكدس (SS) متكلّف بـ Stack16.
عند الإقلاع يقوم الدوس بتحضير (تهيئة) CS و SS أما DS فلا يتم تحضيره. إذن يجب عليك تحضير DS أو ستتعرض للمفاجئات.
Adapt the data register
Mov ax, data16 ; لأننا لا نستطيع الكتابةax نستعمل
Mov ds, ax ; محضّر ds حاليا
يعتبر قطاع المكدس (SS) وقطاع البيانات (DS) متخصصين بالمعطيات (البيانات). لكن قطاع المكدس هو منطقة ذاكرية مؤقتة للتعليمات pop و push، لهذا فإنه لا يوجد إلا فرع ذاكري واحد.
قطاع البيانات يمكنه احتواء متغيرات وثوابت. في هذا المثال، ‘hello’ تعتبر ثابت على شكل حزمة رموز (string). في المثال قمنا بوضع فرع الذاكرة (offset) الخاص بهذه الجملة في dx لأن مقاطعة الدوس تحتاج لفرع الذاكرة حتى يمكنها طباعة النص على الشاشة. 'hello' هي المتغير الأول والذي يملك فرعا ذاكريا (أقرب مرادف للـ offset) معدوما.
Declaration of data:
"name" db ? ; متغير من 8 بت: بايت
"name" dw ? ; متغير من 16 بت: كلمة
"name" dd ? ; متغير من 32 بت: كلمة_مضاعفة
"name" db ?,? ; 2 بايت
"name" dw ?,?,?,? ; 4 كلمات
"name" db 'Hello' ; سلسلة من البايتات
"name" dw 32 dup (?) ; 32 كلمة
جرب البرنامج ولاحظ النتيجة.
حاول تعديل بعض القيم أو تغييرها، لكن إحذر من الأعمال العشوائية.
إذا لم تفهم كل الذي قلناه، فلا تقلق، لأنه مع الوقت يكون بإمكانك تحقيق مرادك وبلغة التجميع.
المثال الثاني:
كما هو معلوم، فإن لغة التجميع لا تتوفر على التحكم "if"، لكن هناك تعليمات خاصة بالمعالج قد تحقق نفس المطلوب، هذه التعليمات هي القفز المشروط.
لنلاحظ هذا المثال البسيط:
cmp ax, 1
je Ok
mov bx, 3
Ok:
في المثال، إذا كان ax مساويا للواحد فإن المعالج يقفز إلى الوسم "OK"، وإلا فإنه لا يتم القفز. ax تتم مقارنته بالقيمة "1"، ونتيجة هذه المقارنة يتم وضعها في الرايات.
ومنه فإن القفز المشروط j[cas] يستخدم الرايات ليعرف ما إذا كان واجبا القفز أم لا.
المثال السابق يمكننا ترجمته إلى لغة السي على شاكلة:
if (ax == 1) goto Ok;
bx = 3;
Ok:
أو بطريقة أكثر سلاسة:
if (ax <> 1)
{
bx = 3
}
الآن سنعرض مثال كامل يمكننا تصريفه وتنفيذه:
;----------------------------------------------------------------------------;
; برنامج يحوي قفزا مشروطا
;----------------------------------------------------------------------------;
; من أجل تصريف البرنامج وتنفيذه
; tasm cond
; link cond
; cond
;----------------------------------------------------------------------------;
; ---------------------------------------------------------------------------;
; قطاع البيانات
data16 segment public
TimePrompt db 'Is it after noon (Y/N)?$'
GoodMorning db 13,10,'Good morning, World!',13,10,'$'
GoodAfternoon db 13,10,'Good afternoon, World!',13,10,'$'
Default db 13,10,'Greetings, World!',13,10,'$'
; "13,10" estle symbol dos تخص السطر الموالي
; أضف "13،10" ولاحظ النتيجة
data16 ends
; ---------------------------------------------------------------------------
; قطاع الكود
code16 segment public
assume cs:code16, ds:data16, ss:stack16
start16:
mov ax, data16
mov ds, ax
mov dx, offset TimePrompt
mov ah, 09h ; تسمح بطباعة النص على الشاشة 09h المقاطعة
int 21h ; مقاطعة الدوس
mov ah, 01h ; تسمح بكتابة رمز وحيد 01h المقاطعة
int 21h ; مقاطعة الدوس
or al, 20h ; ترغم الرمز أو الحرف على أن يظهر بأحرف صغيرة
cmp al, 'y' ; Yإذا ما تم كتابة الحرف
je IsAfternoon ; القفز إذا كان مساويا
cmp al, 'n' ; Nإذا ما تم كتابة الحرف
je IsMorning ; القفز إذا كان مساويا
mov dx, offset Default
jmp Display ; قفز غير مشروط
IsAfternoon:
mov dx, offset GoodAfternoon
jmp Display ; قفز غير مشروط
IsMorning:
mov dx, offset GoodMorning
Display:
mov ah, 09h ; تسمح بطباعة النص على الشاشة 09h المقاطعة
int 21h ; مقاطعة الدوس
mov al, 00h
mov ah, 4ch ; تسمح بإنهاء البنامج 4ch المقاطعة
int 1h ; مقاطعة الدوس
code16 ends ; نهاية الكود
; ---------------------------------------------------------------------------
; قطاع المكدس
stack16 segment stack
db 200h dup (?)
stack16 ends
end start16
تحليل البرنامج:
للقد أشرنا في تحليلنا الساب إلى أهم قطاعات الكود وخصائص كل قطاع، لهذا فإننا في هذا المثال لن نعيد ما قلناه، بل سنشير إلى الشيء الجديد فقط.
في قطاع البيانات (التصريح بالبيانات):
لو ترجع قليلا إلى الوراء فإنك ستلاحظ هذه الجملة:
"name" db 'Hello' ; سلسلة من البايتات
ومعناها أننا نقوم بالتصريح بحزمة رموز (string) تحمل الإسم name.
على ذات المنوال يمكننا ملاحظة التصريحات الواردة في البرنامج السابق:
TimePrompt db 'Is it after noon (Y/N)?$'
GoodMorning db 13,10,'Good morning, World!',13,10,'$'
GoodAfternoon db 13,10,'Good afternoon, World!',13,10,'$'
Default db 13,10,'Greetings, World!',13,10,'$'
لقد صرحنا بأربع حزم وهي TimePrompt، GoodMorning، GoodAfternoon و Default
وكل حزمة مهيأة (مستهلة) بجملة معينة تنتهي برمز الدولار. يعتبر هذا الرمز ضروريا في نهاية كل حزمة رموز، أما الرقمين 13,10 فتعني إضافة أسطر مثلما هو الحال مع الرمز (\n) في لغة السي.