پیوند و اجرای اسمبلی

saalek110

Well-Known Member
در 5 پست بالا از تاپيك مذكور مطالبي نقل شد به علت دقيق بودن نوشته ها.
ولي ما روال كار را روي همان سايت مي گذاريم. يعني در پستهاي بعدي صفحه 3 سايت را شروع به تمرين كدهايش مي كنيم.
.
 

saalek110

Well-Known Member
با سلام.
صفحه سوم سايت را مي خواهيم ادامه دهيم.
اول صفحه يك نكته راجع به همان راهنماها گفته كه بررسي مي كنيم.
مي دانيد كه من با tasm فقط كار مي كنم. شايد بعدا ديگر اسمبلرها را هم دانلود كردم و كار با آنها را شرح دادم ولي فعلا فقط tasm .
اما نكته اين صفحه:
بحث سر اين 3 خط است كه در برنامه قبلي داشت:

کد:
mov dx,OFFSET MyMessage 
mov ax,SEG MyMessage 
mov ds,ax

مي خواهد بگويد جور ديگر هم ميشه نوشت.
ميگه كه اول برنامه اين دو خط را اضافه كنيد:

کد:
mov ax,@data 
mov ds,ax

و بعد اين خط را ، :
کد:
mov dx,OFFSET MyMessage

خوب باز هم كه شد 3 خط كد!
من با tasm الان اين حالت را چك مي كنم ببينم درست كار مي كنه يا نه.
آره درست بود. كل برنامه را در زير مي آورم:

کد:
.model small
.stack 
.data 

Message db "Hello World!$" ; message to be display

.code 
start proc
mov ax,@data 
mov ds,ax
mov dx,OFFSET Message

mov ah,9 ; function 9 - display string 
int 21h ; call dos service 
mov ax,4c00h ; return to dos DOS 
int 21h 

start endp
     end start

فقط به جاي mymessage گذاشتم message تا به نام متغير من بخوره.
= = = == =
براي اسمبلر a86 گفته اين جوري بنويسيد:
کد:
mov ax,data

يعني اون علامت @ را قبل data نمي خواهد.
من اين a86 را الان ندارم و نمي توانم چك كنم ببينم درست ميگه يا نه.
كلا اسمبلرهاي مختلف اين راهنماهايشان كمي متفاوت است ولي در كدهاي اسمبلي فكر نكنم فرقي داشته باشند.

كلا راجع به اين راهنماها و طرز چيده شدن قطعه ها و ((طرز دستور دادن توسط راهنماها ، براي چيدن قطعات)) من زياد مسلط نيستم و نمي توانم الان توضيح زيادي بدهم. پس اجازه دهيد برويم جلو و با منابع مختلف اينها را بعدا تمرين كنيم.

خوب اين قسمت اول صفحه سوم تمام شد و حالا مي خواهيم كد نويسي را شروع كنيم.
كه در پست بعدي شروع مي كنم.
 

saalek110

Well-Known Member
ادامه صفحه سوم:
در اين صفحه هم مثل صفحه دوم يك برنامه بزرگ در آخر صفحه گذاشته و در طول صفحه كدهاي آن را شرح داده.

من الان برنامه انتهايي را اجرا كردم. و اجرا شد.
فقط يك نكته:
برنامه اي كه اون گذاشته اين است:
کد:
; a program to demonstrate program flow and input/output

.model tiny
.code 
org 100h
start:

mov dx,OFFSET Message ; display a message on the screen
mov ah,9 ; using function 09h
int 21h ; of interrupt 21h

mov dx,OFFSET Prompt ; display a message on the screen 
mov ah,9 ; using function 09h
int 21h ; of interrupt 21h

jmp First_Time
Prompt_Again:

mov dx,OFFSET Another ; display a message on the screen 
mov ah,9 ; using function 09h
int 21h ; of interrupt 21h 

First_Time:

mov dx,OFFSET Again ; display a message on the screen 
mov ah,9 ; using function 09h
int 21h ; of interrupt 21h
xor ah,ah ; function 00h of

int 16h ; interrupt 16h gets a character 
mov bl,al ; save to bl 
mov dl,al ; move al to dl
mov ah,02h ; function 02h - display character
int 21h ; call DOS service 

cmp bl,'Y' ; is al=Y?
je Prompt_Again ; if yes then display it again
cmp bl,'y' ; is al=y?
je Prompt_Again ; if yes then display it again

theEnd:

mov dx,OFFSET GoodBye ; print goodbye message
mov ah,9 ; using function 9
int 21h ; of interrupt 21h

mov ah,4Ch ; terminate program 
int 21h
 
.DATA 
CR equ 13 ; enter 
LF equ 10 ; line-feed

Message DB "A Simple Input/Output Program$" 
Prompt  DB CR,LF,"Here is your first prompt.$" 
Again   DB CR,LF,"Do you want to be prompted again? $"
Another DB CR,LF,"Here is another prompt!$" 
GoodBye DB CR,LF,"Goodbye then.$"

end start

در اين برنامه براي ساخت يك برنامه com تدارك ديده. و فكر كنم با tlink فايل com ساخته كه موقع لينك يك حرف t مي گذارد كه فايل com ساخته بشه ولي من tlink را نداشتم و link را داشتم كه پارامتر t را نمي شناخت.
پس اومدم برنامه را تغيير دادم تا مثل برنامه صفحه 2 يك فايل exe بسازه.
برنامه خودم را مي گذارم. اين برنامه با tasm و link بدون اشكال به exe تبديل شد و درست كار مي كرد:

کد:
; a program to demonstrate program flow and input/output

.model small
.stack 
.code 

start:
mov ax,@data 
mov ds,ax

mov dx,OFFSET Message ; display a message on the screen
mov ah,9 ; using function 09h
int 21h ; of interrupt 21h

mov dx,OFFSET Prompt ; display a message on the screen 
mov ah,9 ; using function 09h
int 21h ; of interrupt 21h

jmp First_Time
Prompt_Again:

mov dx,OFFSET Another ; display a message on the screen 
mov ah,9 ; using function 09h
int 21h ; of interrupt 21h 

First_Time:

mov dx,OFFSET Again ; display a message on the screen 
mov ah,9 ; using function 09h
int 21h ; of interrupt 21h
xor ah,ah ; function 00h of

int 16h ; interrupt 16h gets a character 
mov bl,al ; save to bl 
mov dl,al ; move al to dl
mov ah,02h ; function 02h - display character
int 21h ; call DOS service 

cmp bl,'Y' ; is al=Y?
je Prompt_Again ; if yes then display it again
cmp bl,'y' ; is al=y?
je Prompt_Again ; if yes then display it again

theEnd:

mov dx,OFFSET GoodBye ; print goodbye message
mov ah,9 ; using function 9
int 21h ; of interrupt 21h

mov ah,4Ch ; terminate program 
int 21h
 
.DATA 
CR equ 13 ; enter 
LF equ 10 ; line-feed

Message DB "A Simple Input/Output Program$" 
Prompt  DB CR,LF,"Here is your first prompt.$" 
Again   DB CR,LF,"Do you want to be prompted again? $"
Another DB CR,LF,"Here is another prompt!$" 
GoodBye DB CR,LF,"Goodbye then.$"

end start

حالا فرقهايشان را اول مي گويم و بعد كدها را خط به خط شرح مي دهيم.
در برنامه هاي com نيازي به تعريف پشته نيست و خودش پشته را در انتهاي قطعه خود به خود قرار مي دهد. ولي در برنامه هاي exe در كدمان بايد با كلمه stack پشته را ذكر كنيم و گفتيم كه اگر جلوي stack چيزي ننويسيم خودش 1024 را كه ديفالت است را تنظيم مي كند.
نكته ديگه اين كه در برنامه com با نوشتن org 100h باعث ميشه كه معلوم بشه فايل com است و يكسري تنظيماتي انجام ميشه با اين كد.
نكته ديگر اين كه بايد در كدهاي ما در فايل com نوع مدل tiny باشد.
دو خط:
mov ax,@data
mov ds,ax
را هم من به فايل خود اضافه كردم تا data ها در قطعه خودش پيدا كنه. اين data ها همان پيامهاي رشته اي است كه موقع اجرا چاپ ميشه و كاربر مي بينه.
براي امتحان من اين دو خط را برداشتم و exe ساختم كه برنامه قاطي مي كرد . يعني مي رفت جاي ديگر را مي خواند و حروف عجيب و غريب چاپ ميشد.

در برنامه سايت يك اشكال هم داشت كه من رفع كردم. و آن اين بود كه بعد Goodbye then. علامت دلار را نگذاشته بود و باعث ميشد خواندن پيام خاتمه پيدا نكند و ادامه بدهد به خواندن ram و باز حروف عجيب و غريب چاپ ميشد . كه با گذاشتن $ حل شد.

اما شرح برنامه:
اول چه نوع خروجي ئي مي دهد:
اول سه جمله چاپ مي شود به اين شكل:
A Simple Input/Output Program
Here is your first prompt
Do you want to be prompted again?

كه با y مي زنيم يا چيز ديگه. اگر y بزنيم اين جمله چاپ ميشه:
Here is another prompt!
و اگر چيز ديگه بزنيم ميگه:
Goodbye then.
يعني ((پس خداحافظ)) و برنامه خاتمه پيدا مي كند.

= = = = = = = = = = =
اما آموزش اين صفحه همان طور كه معلومه راجع به ورودي گرفتن از كاربر است.
در برنامه صفحه دوم اين سايت فقط يك hello world چاپ ميشد ولي در برنامه صفحه سوم ، برنامه با كاربر ارتباط برقرار مي كنه و از او نظر مي خواهد كه چه كار كنم. در اينجا شما مي توانيد با زدن y هاي متوالي مدام برنامه را وادار به تكرار كنيد يا با زدن حرف ديگر باعث اختتام برنامه شويد.

اين توضيحات را پست مي كنم و شرح جزئي كدها را در پست بعدي مي زنم.
 
آخرین ویرایش:

saalek110

Well-Known Member
ممنون دوست عزيز. لطف داريد.
== = == = == = = = = = =
من براي اينكه بتوانيم فايل com هم بسازيم رفتم سرچ كردم و برنامه اي به نام tlink16 پيدا كردم كه توانست به درستي برنامه پست قبلي(برنامه اول پست قبلي ) را به فايل com تبديل كند.
آدرس دانلود:
http://www.cs.umsl.edu/~schulte/cs2710/

ولي فقط tlink16 را دانلود نكنيد چون بعدش ميگه كه rtm.exe را هم مي خواهم.
پس اين دو تا را دانلود كنيد.
==================
سينتكس تبديل:
کد:
tlink16 filename.obj/t

اگر اين t را نگذاريد مي رود فايل exe مي سازد به جاي com و اخطاري هم tlink16 مي دهد كه پشته چرا نداريد. و مهم تر از همه فايل درست كار نمي كند چون قطعه هايش تنظيم نيست.
البته همه اينها را براي برنامه اول پست قبل مي گيم نه برنامه دومي. برنامه دوم پست قبل را همين الان با tlink16 (بدون نوشتن حرف t ) تبديل كردم و بخوبي كار مي كرد. يعني همون كاري كه با link قبلا كرده بودم.
=======================
پس توانستيم فايل com هم بسازيم. و فرق برنامه هاي com و exe را هم كمي شرح داديم.
شرح كدهاي برنامه بالا را در پست بعدي پي مي گيريم.
 
آخرین ویرایش:

saalek110

Well-Known Member
اولا بايد بگويم كه در كد برنامه هاي com و exe بالا ، كدهاي اسمبلي يكي است.
به جز دو خط :
mov ax,@data
mov ds,ax
كه در exe داريم و در com نداريم كه محل داده ها را مشخص مي كنه ولي در فايل com چون همه اجزا در يك قطعه اند نيازي به مشخص سازي قطعه نيست.

حالا بقيه كدها:

چنين كدي:
mov dx,OFFSET Message
mov ah,9
int 21h
چند بار تكرار شده.كه كارش اينه كه با خط اولش روي يك رشته اول تنظيم ميشه و بعد با خط دومش سرويس وقفه تنظيم ميشه و با خط سومش وقفه احضار ميشه.
يعني در كل اين قطعه هاي سه خطي كارشان نمايش يك جمله به كاربر است.

مي رسيم به :
xor ah,ah

int 16h

خط اول باز تنظيم سرويس است اما اين بار با يك روش جديد. قرار بوده در ah صفر قرار داده بشه و براي صفر كردن آن ، آمده ah رابا خودش xor كرده. شما and و or را مي شناسيد. اولي وقتي 1 ميده كه هر دو يك باشند و دومي وقتي يك ميده كه يكي از آنها يك باشد. و اينجا xor وقتي كه هر دو يك نوع باشند صفر ميدهد و معلومه كه ah يا خودش هميشه يكي است پس xor آن با خودش صفر مي دهد بيرون. يعني يا يك است كه ((يك با يك)) صفر بيرون ميده و يا صفر است كه ((صفر با صفر)) باز صفر بيرون مي دهد.

حالا خط دوم كد بالا، وقفه 16 كارش اين است كه از كاربر كليدي را دريافت مي كند. از صفحه كليد. و انتظار هم مي كشد براي گرفتن كليد. وقفه ديگري براي گرفتن كليد هست كه انتظار نمي كشد و اگر كليدي ندهيم رد مي شود به كد بعدي.
و وقتي اين وقفه(سرويس صفر آن) كليد را دريافت كرد كدهاي اسكي و اسكن آن را در ah و al قرار مي دهد.

كد بعدي:
mov bl,al ; save to bl
همان طور كه در كامنت جلوي آن نوشته براي اين كه al ممكنه بعدا دستخوش تغيير بشه ، محتواي آن را در bl ذخيره مي كنه. به نوعي داره از al بك آپ مي گيره.

كد بعدي:
mov dl,al ; move al to dl
حالا al در dl قرار داده مي شود. چرا؟ چون كد زير:
mov ah,02h ; function 02h - display character
int 21h ; call DOS service
كه سرويس 2 وقفه 21 است كارش اينه كه نگاه مي كنه به dl و عدد داخلش را كد اسكي يك كاراكتر تفسير مي كند و آن را چاپ مي كند. براي تمرين به dl اعداد مختلفي بدهيد و با دو خط كد بالا چاپش كنيد.

كد بعدي:
cmp bl,'Y' ; is al=Y?
je Prompt_Again ; if yes then display it again
اينجا bl كه قبلا بك آپ al درونش ريخته شده بود با كد اسكي Y مقايسه مي شود. Cmp نوعي تفريقي است كه مثل a منهاي b كه بعد تفريق a و b تغييري نمي كنند. در حقيقت فقط مقايسه اين دو است. ولي فلاگها تفريق در نظر مي گيرند قضيه را.
فلاگها را نگفتيم و كم كم بايد آشنا بشويم با آنها.
هر فلاگ يك حافظه يك بيتي(نه بايت) است كه يا روشن است و يا خاموش. فرض كنيد يك چراغ جلوي دفتر كار شما نصب باشه كه وقتي وارد اتاق كار خود مي شويد روشن مي كنيد و وقتي خارج مي شويد خاموش مي كنيد. فلاگها چنين چراغهايي هستند براي راهنمايي.
پرش ها در اسمبلي دو نوعند. بي شرط و با شرط. پرشهاي با شرط به فلاگها نگاه مي كنند. در دو خط كد بالا je وقتي پرش مي كند كه نتيجه تفريق صفر باشد. يعني وقتي كه در bl كد اسكي Y باشد. كه bl هم از al گرفته بود محتواي خود را و al را هم وقفه 16 پر كرده بود. وقفه 16 هم كاراكتري را كه كاربر زده بود را در al ريخته. پس اينجا اين دو خط كد يعني كه اگر كاربر Y را زده بود پرش كن به ليبل Prompt_Again .

بحث ليبل:
ليبل همان طور كه در كدها مي بينيد يك اسم است كه هر چي دوست داشتيد مي توانيد قرار دهيد و جلوي آن دو نقطه بايد بگذاريد.
و اين جوري ليبل ميشه يك نقطه مشخص كه ميشه به آنجا دستور پرش داد. و پرشهاي شرطي و غير شرطي مي توانند بگويند كه به فلان ليبل بپر.
در كد زير:
cmp bl,'y' ; is al=y?
je Prompt_Again ; if yes then display it again
هم كه معلومه ديگه كه داره y را بررسي مي كنه . چون ممكنه كپس لاك كاربر روشن بوده باشه يا مورد ديگر كه او حرف ايگرگ را بزرگ يا كوچك بزند. و مي دانيد كه كد اسكي اين دو با هم متفاوت است.

كد بعدي:
mov ah,4Ch ; terminate program
int 21h
اين هم كه كد خاتمه دهنده برنامه است.

كد بعدي:
CR equ 13 ; enter
LF equ 10 ; line-feed
عبارت equ همان اكوئال يعني مساوي است . يعني CR ميشه مشابه 13 . كه بعدا استفاده ميشه.
بهتر زياد از اعداد استفاده نكنيد در برنامه خود چون بعدا وقتي يك 13 مي بينيد نمي دانيد چيست. به اين گونه اعداد ، ((اعداد جادويي)) مي گويند. البته فكر كنم خوب معني نشده و بايد اسمش را اعداد گنگ يا اعداد گيج كننده بگذاريم. چون موقعيكه خودمان آنرا در برنامه مي نويسيم مي دانيم چيه ولي چند روز بعد نمي دانيم اين 13 چيه. موضوع وقتي پيچيده ميشه كه يك 13 ديگه هم در برنامه باشه. و اگر روزي بخواهيم CR را از برنامه برداريد ممكنه آن 13 نوع دوم را هم برداريد و برنامه كلا قاطي بشه.
پس بهتره كه به اعداد خود اسم هاي مناسب بدهيد و بعد استفاده كنيد. اما كار اين 13 و در خط دوم كد بالا ، آن 10 چيه ؟ كدهاي بعدي را با هم مي بينيم:

Message DB "A Simple Input/Output Program$"
Prompt DB CR,LF,"Here is your first prompt.$"
Again DB CR,LF,"Do you want to be prompted again? $"
Another DB CR,LF,"Here is another prompt!$"
GoodBye DB CR,LF,"Goodbye then.$"

مي بينيد كه CR و LF در رشته هاي ما استفاده شده اند. مي دانيد كه وقتي DB را بكار برده ايم داريم بايت پر مي كنيم داخل ram . پس 13 و 10 هم كه حالا با اسم CR و LF شناخته مي شوند مي نشينند در ram . البته به شكل همان 13 و 10 و مي شوند اول رشته هاي ما. حالا كارشان چيه؟ اينه كه اينتر مي كنند و لاين فيد(نرفتم امتحان كنم ببينم چيه) مي كنند. پس جملات پشت سر هم چاپ نمي شوند و بعد اينتر در خط بعدي چاپ مي شوند و ظاهر برنامه شكيل تر ميشه.
علامت دلار را هم در انتهاي هر رشته فراموش نكنيد چون وقفه مربوطه تا آن را نبيند همين طور ادامه مي دهد. شما كه نمي خواهيد هزارها بايت كاراكتر بي معني چاپ بشه روي صفحه. پس با گذاشتن علامت دلار در انتهاي رشته كه مي رود در ram مي نشيند وقفه مربوطه را هدايت كنيد.

سعي كنيد قطعات اين برنامه را خارج كنيد و برنامه هاي ساده باهاش بسازيد و اجرا كنيد تا مسلط بشويد بر اين كدها.
 

saalek110

Well-Known Member
تكميل نكات صفحه سوم:

سرويس صفر وقفه 16 كه با آن كار كرديم كد اسكن را در ah و كد اسكي را در al قرار مي دهد.
توضيح اينكه بعضي كليدهاي صفحه كليد كد اسكن هم دارند.
مي توانيد براي اينكه اين قضيه را تمرين كنيد برنامه اي بسازيد و كليدهاي مختلف را بزنيد و ah و al را بررسي كنيد. در زبانهاي ديگر مثل سي هم مي توانيد اين تمرين را بكنيد. كليدهاي f1 تا f12 و كليدهاي ديلت و arrow ها(بالا – پايين - ..) و اين نوع كليد ها را بررسي كنيد.
البته در جداول استاندارد هم اينها هست.

در برنامه ما چون كليد مورد نظر ما y بود كاري با اين بحث ها نداشتيم. چون كد اسكي را كافي بود بررسي كنيم.

= = = = = = = = = = = = = = = =
وقتي در رجيستري مقداري هست و بايد حفظ شود و همان لحظه به آن رجيستر نياز مي شود دو راه براي حفظ آن مقدار هست. يكي اين كه در رجيستر ديگري بريزيم و ديگر اين كه push كنيم به پشته و هر وقت لازم داشتيم pop كنيم.

در برنامه بالا ديديد كه وقفه 16 كه ورودي از صفحه كليد مي گرفت آن را در ah و al مي ريخت. حالا اگر در اين رجيسترها چيزي بود كه بايد از نابودي آن جلوگيري ميشد مي توانستيم با يكي از اين دو راه آن را حفظ كنيم.
در برنامه بالا ديديم كه al را به bl ريختيم. من راجع به اين كه اين كار چه نفعي داشته فكر نكردم چون بنا به اعتمادي كه كردم به نويسنده اين آموزش گفتم احتمالا al در معرض خطر بوده. شما خودتان بررسي كنيد كه آيا ريختن al به bl لازم بوده يا نه. بايد بدانيد كه ابدا لازم نيست براي حل چنين مسئله اي شما همين راه را برويد . بلكه مي توانيد سليقه اي از رجيسترها و پشته استفاده كنيد. ولي با اين وجود از رجيسترها به طور خاصي استفاده مي شود و نوعي استاندارد در اين مورد ايجاد شده. ولي شما اگر نخواستيد اين استاندارد ها را رعايت كنيد حداقل سعي كنيد كه استانداردهاي شخصي براي خود داشته باشيد تا در مرور برنامه هاي خود دچار سختي نشويد.

در برنامه بالا ديديد كه رجيستر ah و al كارهاي خاصي به عهده اش بود. در اول كار تنظيم سرويس با ah بود و بعد اجراي وقفه هم كد اسكن داخل ah ريخته ميشد.
پس مي بينيد كه ax تا dx هم وظايف خاصي گاهي دارند. كار خاص dl را هم ديديد كه در سرويس 2 وقفه 21 نگه دارنده كاراكتر بود. در صفحه هاي بعدي اين سايت آموزشي كه ما تابحال تا صفحه 3 آمده ايم مي بينيم كه cx هم به عنوان كانتر يعني شمارنده استفاده مي شود يعني وقتي حلقه اي مي سازيم اين رجيستر مي شود شمارش گر تعداد چرخش. حرف c هم اول نام counter است. و حرف aاز ax به معني آكومولاتور به معني انباره يا جمع كننده.

منظور من از نوشتن اين توضيحات اين بود كه محيط كار خود را بشناسيد . در اسمبلي قسمت مهمي از محيط كار ما همين رجيسترهاست . قسمتي پشته و كلا قطعه ها و چيزهاي ديگر.

== = = = = = = =
كد زير هم نمايش ساده اي از پرش غير شرطي و ليبل هدف آن است:
کد:
jmp ALabel
.
.
. 
ALabel:
دستور jmp دستور پرش بي شرط است. بقيه دستورات شرطي با نگاه به فلاگها پرش مشروط مي كنند. در برنامه پست قبل je وقتي bl مساوي y بود پرش مي كرد. چون اجراي دستور cmp باعث تغيير فلاگي شده بود كه تغيير آن فلاگ به je دستور پرش را دستور مي دهد.
ليست بعضي پرشهاي شرطي را در زير مي آوريم:

2j0nhhl.gif



نكته:
Note: the jump can only be a maximum of 127 bytes in either direction.
پرش به هر سو بايد زير 127 بايت باشد.
هم در مورد پرش شرطي و هم پرش غير شرطي اين قضيه را امتحان كنيد. بايد warning بدهد كامپايلر شما.
= = = = = = == == =
سينتكس كامل تر cmp :
کد:
CMP register or variable, value

پس مي بينيد كه فقط رجيستر در طرف چپ قرار نمي گيرد بلكه متغير هم مي تواند قرار گيرد.
مثال cmp :
کد:
cmp ax,3; is AX = 3?
je correct; yes

کد:
cmp al,'Y' ; compare the value in al with Y
je ItsYES ; if it is equal then jump to ItsYES

البته دقيقا همان حالت داخل برنامه است اينها . فقط مي خواستم كامنت زيباي آن را بخوانيد. كامنت نويسي خودش يك هنر است . چون هر چه مختصر تر و روشن كننده تر باشد بهتر است.
 

saalek110

Well-Known Member
صفحه چهارم:

در اين صفحه يك سري دستورات مورد نياز رايج گفته مي شود.

اولي دستور جمع است:
کد:
ADD operand1,operand2

باعث جمع دو تا اپرند ميشه و حاصل در اولي گذاشته ميشه.

تفريق:
کد:
SUB operand1,operand2

دومي از اولي كم ميشه.

ضرب:
کد:
MUL register or variable

چيزي كه جلوي اين دستور باشه در al يا ax ضرب ميشه و در همان جا قرار مي گيره . اگر بزرگتر از اندازه آن مكان باشه ، اگر al باشه به كل ax ميريزه و اگر ax باشه بقيه اش به dx ريخته ميشه.

دستور ضرب بالا براي بي علامت يا unsigned است.
براي علامت دارها دستور زير استفاده ميشه:
کد:
IMUL register or variable

تقسيم: (براي علامت دار و بي علامت )
کد:
DIV register or variable
IDIV register or variable

شبيه ضرب است . آنجا اضافه مي رفت به ah و dx و در اينجا باقيمانده مي رود اينجاها.
= = = == = = == = = = = =

زير برنامه:

مقداري كد است كه در زيربرنامه قرار مي دهيم و نگران طرز كارش ديگه نيستيم. در اكثر زبانها زيربرنامه را داره.

کد:
PROC AProcedure
.
. ; some code to do something 
.
ret; if this is not here then your computer will crash 
ENDP Aprocedure

ساختارش را مي بينيد كه با proc شروع ميشه ، بعدش اسم زيربرنامه.
بعد كدها را مي نويسيم.
آخرش يك ret داره و
بعدش هم يك endp و باز اسم زيربرنامه.

طرز صدا كردن(فراخواني ) :
کد:
call Aprocedure

مثال زير همان برنامه اول اين سايت آموزشي است كه اين بار با زيربرنامه ساخته شده:
کد:
.model tiny 
.code 
org 100h

Start: 

call Display_Hi ; Call the procedure
mov ax,4C00h ; return to DOS
int 21h 

Display_Hi PROC 

mov dx,OFFSET HI 
mov ah,9 
int 21h 

ret
Display_Hi ENDP 

HI DB "Hello World!$" ; define a message

end Start

شرح برنامه:
اولا ساختار برنامه com است پس موقع لينك با tlink16 و پارامتر t تبديل كنيد.
دوما تنها كاري كه كرده آمده كل برنامه را در يك زيربرنامه قرار داده و در برنامه اصلي زيربرنامه را صدا زده و بعد صدا زدن برنامه هم دستورات اختتام را گذاشته. همين.
 

saalek110

Well-Known Member
ادامه صفحه چهار:

در پست قبلي زيربرنامه را ياد گرفتيم ولي مي دانيد كه ارسال مقدار به زيربرنامه باعث تسهيلات فوق العاده اي براي ما مي شود.

در اسمبلي ارسال مقادير به 3 روش انجام مي شود:
يك از طريق رجيستر
دو از طريق پشته
سه از طريق حافظه

= = = = == = =
حالت اول : ارسال از طريق رجيستر
اين حالت ساده و سريع است و فقط كافي است قبل صدا كردن زيربرنامه مقدار مورد نظر را در رجيستر قرار دهيد.

نكته اي كه بايستي در مورد زيربرنامه ها گفته بشود اين است كه زير برنامه بايد تمام رجيسترهايي را كه دستكاري مي خواهد بكند را قبل هر كاري به پشته بفرستند و در آخر زيربرنامه همه را از پشته بازيابي كند. اين كار براي اين است كه ما بي هيچ نگراني زيربرنامه ها را بكار بريم.
ترتيب push و pop را هم يادتان نرود. اوني كه اول مي فرستيد را در آخر بازيابي بايد بكنيد.

مثالي كه براي ارسال از طريق رجيستر اينجا زده شده يك مثال طولاني است و به نظر من براي آموزش اصلا خوب نيست ولي من نقل مي كنم.
کد:
.model tiny 
.code
org 100h

Start: 

mov dh,4 ; row to print character on
mov dl,5 ; column to print character on
mov al,254 ; ascii value of block to display
mov bl,4 ; colour to display character

call PrintChar ; print our character
mov ax,4C00h ; terminate program 
int 21h

PrintChar PROC NEAR

push bx ; save registers to be destroyed
push cx

xor bh,bh ; clear bh - video page 0
mov ah,2 ; function 2 - move cursor
int 10h ; row and col are already in dx

pop bx ; restore bx

xor bh,bh ; display page - 0
mov ah,9 ; function 09h write char & attrib
mov cx,1 ; display it once
int 10h ; call bios service

pop cx ; restore registers

ret ; return to where it was called
PrintChar ENDP

end Start

در اين مثال قبل call زير برنامه رجيسترها را تنظيم كرده. اين برنامه از وقفه ها استفاده مي كند براي يك كار گرافيكي . من اجراش كردم . يك مستطيل چشمك زن در قسمتي از صفحه داشت. خودتان كامنت ها را بخوانيد تا متوجه وظيفه هر كد بشويد. ولي نياز نيست واقعا روي اين كد طولاني كار كنيد. مهم همان ماهيت كار بود كه توضيح داده شد. يعني تنظيم رجيسترها قبل call زيربرنامه.

= = == = = =
روش دوم ارسال از طريق حافظه:
اين روش ساده است ولي برنامه را بزرگ مي كند و كند.

نفس كار اين است كه چيزهايي كه قراره ارسال بشه در يك متغير ذخيره ميشه و دقيقا مثل حالتي كه با رجيستر كار ميشد با اين متغير كار ميشه . ولي كار با رجيستر مسلما سريعتره.(سرعت اجراي برنامه منظوره)

کد:
; this a procedure to print a block on the screen using memory
; to pass parameters (cursor position of where to print it and
; colour).

.model tiny 
.code
org 100h

Start: 

mov Row,4 ; row to print character
mov Col,5 ; column to print character on
mov Char,254 ; ascii value of block to display
mov Colour,4 ; colour to display character

call PrintChar ; print our character
mov ax,4C00h ; terminate program 
int 21h

PrintChar PROC NEAR

push ax cx bx ; save registers to be destroyed

xor bh,bh ; clear bh - video page 0
mov ah,2 ; function 2 - move cursor
mov dh,Row
mov dl,Col
int 10h ; call Bios service 

mov al,Char 
mov bl,Colour
xor bh,bh ; display page - 0
mov ah,9 ; function 09h write char & attrib 
mov cx,1 ; display it once
int 10h ; call bios service

pop bx cx ax ; restore registers

ret ; return to where it was called
PrintChar ENDP

; variables to store data

Row db ? 
Col db ?
Colour db ?
Char db ?

end Start

برنامه بالا را هم من تست كردم اجرا شد. همان برنامه قبلي است فقط با متغير كار شده.
شرح برنامه: مي بينيد كه به جاي اينكه رجيسترها را قبل call پر كند ، متغيرها را پر كرده. متغيرها هم در آخر كدها مي بينيد كه بدون مقدار اوليه دادن تعريف شده اند.


== = = = == = = == = = =
روش سوم: ارسال مقادير از طريق پشته:
اين روش قويتر و قابل انعطاف تر است ولي پيچيده است.
يك نكته از خودم بگم اينجا كه آدرس بازگشت از زيربرنامه خودش در پشته نگهداري ميشه. حواستان به اين باشه.

حالا مثال:
کد:
; this a procedure to print a block on the screen using the 
; stack to pass parameters (cursor position of where to print it
; and colour).

.model tiny 
.code
org 100h

Start: 

mov dh,4 ; row to print string on
mov dl,5 ; column to print string on
mov al,254 ; ascii value of block to display
mov bl,4 ; colour to display character
push dx ax bx ; put parameters onto the stack 

call PrintString ; print our string

pop bx ax dx ;restore registers
mov ax,4C00h ;terminate program 
int 21h

PrintString PROC NEAR

push bp ; save bp
mov bp,sp ; put sp into bp
push cx ; save registers to be destroyed

xor bh,bh ; clear bh - video page 0
mov ah,2 ; function 2 - move cursor
mov dx,[bp+8] ; restore dx
int 10h ; call bios service

mov ax,[bp+6] ; character
mov bx,[bp+4] ; attribute
xor bh,bh ; display page - 0
mov ah,9 ; function 09h write char & attrib 
mov cx,1 ; display it once
int 10h ; call bios service

pop cx ; restore registers
pop bp 

ret ; return to where it was called
PrintString ENDP

end Start

برنامه بالا را امتحان كردم. يك مربع قرمز چاپ شد. حالا نمي دانم اين نشانه سلامت است يا خطا . بگذريم.
شرح: براي بهره برداري ازپشته تنها چيزي كه بايد بدانيم اين است كه آن چيز در كجاي پشته است.
مي دانيد كه پشته به سمت پايين رشد مي كند. يعني اگر ما سه چيز را به طور متوالي بياندازيم داخل پشته ، آدرس اولي بزرگتر از دومي و آدرس دومي بزرگتر از سومي است.
دقيق تر بگوييم آدرس اولي تا سومي عبارت است از bp+6 و bp+4 و bp+2 .
بايد بدانيد كه شما مي توانيد ax را به پشته بفرستيد ولي نمي توانيد ah را به پشته بفرستيد. پس مستاجرين پشته همه 2 بايتي هستند. براي همين اين اعداد 2 و 4 و 6 توليد شده است.
اما برنامه پيچيده بالا باز هم نياز به توضيح دارد كه بايد مثل معمايي حل شود. مي سپارم به شما حلش را. خودم هم روي آن فكر مي كنم اگر معما را توانستم حل كنم در همين پست مي نويسم. از عكس زير هم كه براي فهم اين قضيه در همين صفحه آمده كمك بگيريد. مشكل من اينه كه تفاوت sp و Bp را خوب نمي دانم.


2v19feo.gif

 

saalek110

Well-Known Member
ادامه صفحه چهار:

مدلهاي حافظه:
Memory Models

سينتكس:
کد:
.MODEL MemoryModel
مثال:
کد:
.model tiny

انواع:
SMALL, COMPACT, MEDIUM, LARGE, HUGE, TINY or FLAT.

= = = == = = = =
مدل tiny :
در اين مدل ، يك قطعه واحد براي كد و data داريم.
اين نوع برنامه مي تواند به فايل com تبديل شود.

مدل small :
كد در يك قطعه و data در يك قطعه ديگر.
و اين به اين معناست كه تمام زيربرنامه ها و متغيرها با آدرس دهي near و فقط توسط آفست ، آدرس دهي مي شوند.

مدل compact :
كد در يك قطعه و data در چند قطعه. پس اولي near ميشه و دومي far . يعني در دومي هم آدرس قطعه لازم دارد و هم آفست. ولي اولي فقط آفست لازم دارد.

مدل medium :
برعكس compact است. يعني data ميشه near و code ميشه far .

مدل large :
هر دو far .

مدل flat :
اين مدل نياز به DOS extender دارد.
 
آخرین ویرایش:

saalek110

Well-Known Member
ادامه صفحه چهار:

ماكرو ها:
Macros
سينتكس هاي زير براي توربو اسمبلر مناسب است.

ماكرو شبيه زيربرنامه مقداري كد است ولي يك فرق مهم دارد با آن.
فرقش اينه كه موقع كامپايل يك نسخه از زيربرنامه در ميان كدها وجود دارد و هر قسمت از برنامه كه بهش نياز داشته باشه كنترل را روي زير برنامه مي اندازد تا كدهاي زيربرنامه اجرا شود و بعد كنترل به برنامه صدا زننده برمي گردد ولي در مورد ماكرو اصل مطلب اين است كه هر جا كه ما ماكرو را صدا مي زنيم موقع كامپايل يك نسخه از ماكرو كدهايش آنجا نوشته ميشه.

سينتكس :
کد:
Name_of_macro macro 
;
;a sequence of instructions 
;
endm

يك ماكروي مفيد:
کد:
SaveRegs macro

push ax
push bx
push cx
push dx

endm
و عكس آن:
کد:
RestoreRegs macro

pop dx
pop cx
pop bx
pop ax

endm

گفتيم كه خوبه كه مواقعي كه نياز است همه رجيسترها را براي حفظ آنها push و بعد انجام كارمان pop كنيم. با صدا زدن ماكروهاي بالا اين كار خيلي ساده انجام ميشه.

طرز صدا كردن دو ماكروي بالا را در زير مي بينيم:
کد:
SaveRegs

; some other instructions

RestoreRegs

ماكرو دو مشكل مي تواند ايجاد كند. يكي اين كه چون مدام يك نسخه ازش به برنامه داره اضافه ميشه بايد حواسمان به حجم برنامه باشه و ديگه اين كه ليبل ها اگر به كار روند داراي ليبل هاي هم نام مي شويم. پس براي حل آن از دستور local استفاده مي كنيم.

سينتكس:
کد:
LOCAL name
كه name عبارت است از نام متغير يا ليبل لوكال(منطقه اي) ما.
يك ماكرو براي مثال در زير آورده مي شود. اين ماكرو به راحتي يك پيام چاپ مي كند.
کد:
OutMsg macro SomeText
local PrintMe,SkipData

jmp SkipData

PrintMe db SomeText,'$'

SkipData:

push ax dx cs 

mov dx,OFFSET cs:PrintMe
mov ah,9
int 21h

pop cs dx ax

endm

ماكرو همچنين مي تواند پارامتر بگيرد.
مثال:
کد:
AddMacro macro num1,num2,result

push ax ; save ax from being destroyed
mov ax,num1 ; put num1 into ax
add ax,num2 ; add num2 to it
mov result,ax ; move answer into result
pop ax ; restore ax

endm
براي استفاده از اين ماكرو بايد مثالي پيدا كرد كه بعدا اين كار را مي كنيم.
صفحه چهار هم تمام شد. البته نياز به تمرينات و توضيحات بيشتري داشت اين صفحه ولي براي اينكه سريع اين سايت تمام بشه وقت نشد اين كار را بكنيم. بعدا از منابع ديگر تمرين مي كنيم.
 

saalek110

Well-Known Member
مي رسيم به صفحه 5 .
صفحه 5 كار با فايل است كه چون هم يك بحث تخصصي و هم يك بحث خسته كننده است ،اين صفحه را فعلا از قلم مي اندازيم.

صفحه 6 :
اما صفحه 6 كدهايي داره كه هم كاربرهاي خوبي داره و هم به درك اسمبلي كمك مي كند.

به اين كدها نگاه كنيد:
کد:
movsb ; move byte
movsw ; move word
movsd ; move double word

براي انتقال بايت و كلمه و ((دو كلمه)) بكار مي رود ولي از كجا به كجا؟
جواب : از DS:SI به ES:DI .
خيلي جالبه به نظر من اين بحث. چون اولا نقش مهم si و di را نشان ميدهد و دوما تصور كنيد كه ما داريم قسمتي از ram را به جاي ديگر منتقل مي كنيم.

اينجا es و ds هم كه قطعات data و اكسترا را مشخص مي كنند دارند در نشانه روي قطعه كمك مي كنند.

دستورات بعدي:
کد:
cmpsb ; compare byte
cmpsw ; compare word
cmpsd ; compare double word

براي مقايسه بايت و كلمه و ((دو كلمه)). باز از DS:SI to ES:DI استفاده ميشه.
داخل كد مي گذارم تا جابجايي احتمالي پيدا نكنه:
کد:
DS:SI to ES:DI

با دستور rep هم اين دستورات قابل استفاده است.

سرچ. كدها:
کد:
scasb ; search for AL
scasw ; search for AX
scasd ; search for EAX

دنبال محتواي al و ax و eax در ES:DI مي گردند.
با repz و repnz استفاده مي شوند.

طرز استفاده از rep :
دستور rep يك Prefix(پيشوند) است كه باعث تكرار به ميزان cx بار ميشه.

Stos :
کد:
stosb ; move AL into ES:DI
stosw ; move AX into ES:DI
stosd ; move EAX into ES:DI

باعث انتقال al و ax و eax به ES:DI ميشوند.

Lods :
کد:
lodsb ; move ES:DI into AL
lodsw ; move ES:DI into AX
lodsd ; move ES:DI into EAX

= = = = = = = = = == = = =
مثال كدهاي بالا:
کد:
.model small
.stack
.code 

mov ax,@data ; ax points to of data segment
mov ds,ax ; put it into ds
mov es,ax ; put it in es too
mov ah,9 ; function 9 - display string
mov dx,OFFSET Message1 ; ds:dx points to message
int 21h ; call dos function

cld ; clear direction flag
mov si,OFFSET String1 ; make ds:si point to String1
mov di,OFFSET String2 ; make es:di point to String2
mov cx,18 ; length of strings
rep movsb ; copy string1 into string2

mov ah,9 ; function 9 - display string
mov dx,OFFSET Message2 ; ds:dx points to message
int 21h ; call dos function

mov dx,OFFSET String1 ; display String1
int 21h ; call DOS service

mov dx,OFFSET Message3 ; ds:dx points to message
int 21h ; call dos function

mov dx,OFFSET String2 ; display String2
int 21h ; call DOS service

mov si,OFFSET Diff1 ; make ds:si point to Diff1 
mov di,OFFSET Diff2 ; make es:di point to Diff2 
mov cx,39 ; length of strings
repz cmpsb ; compare strings
jnz Not_Equal ; jump if they are not the same

mov ah,9 ; function 9 - display string
mov dx,OFFSET Message4 ; ds:dx points to message
int 21h ; call dos function

jmp Next_Operation

Not_Equal:
mov ah,9 ; function 9 - display string
mov dx,OFFSET Message5  ; ds:dx points to message
int 21h ; call dos function

Next_Operation:
mov di,OFFSET SearchString ; make es:di point to string
mov cx,36 ; length of string
mov al,'H' ; character to search for
repne scasb ; find first match
jnz Not_Found

mov ah,9 ; function 9 - display string
mov dx,OFFSET Message6 ; ds:dx points to message
int 21h ; call dos function
jmp Lodsb_Example

Not_Found:
mov ah,9 ; function 9 - display string
mov dx,OFFSET Message7 ; ds:dx points to message
int 21h ; call dos function

Lodsb_Example:
mov ah,9 ; function 9 - display string
mov dx,OFFSET NewLine ; ds:dx points to message
int 21h ; call dos function

mov cx,17 ; length of string
mov si,OFFSET Message ; DS:SI - address of string
xor bh,bh ; video page - 0
mov ah,0Eh ; function 0Eh - write character

NextChar:
lodsb ; AL = next character in string
int 10h ; call BIOS service
loop NextChar

mov ax,4C00h ; return to DOS
int 21h 

.data
CR equ 13
LF equ 10
NewLine db CR,LF,"$"

String1  db "This is a string!$"
String2  db 18 dup(0)
Diff1    db "This string is nearly the same as Diff2$"
Diff2    db "This string is nearly the same as Diff1$"
Equal1   db "The strings are equal$"
Equal2   db "The strings are not equal$"
Message  db "This is a message"
SearchString db "1293ijdkfjiu938uHello983fjkfjsi98934$"

Message1 db "Using String instructions example program.$"
Message2 db CR,LF,"String1 is now: $"
Message3 db CR,LF,"String2 is now: $"
Message4 db CR,LF,"Strings are equal!$"
Message5 db CR,LF,"Strings are not equal!$"
Message6 db CR,LF,"Character was found.$"
Message7 db CR,LF,"Character was not found.$"

end

= == == = = = = = =
روش پيدا كردن ورژن DOS:
کد:
mov ah,30h ; function 30h - get MS-DOS version
int 21h ; call DOS function

اين وقفه عدد بزرگ ورژن را در al و عدد كوچك را در ah برمي گرداند.
مثلا ورژن 4 مميز صفر يك اگر باشد ، 4 داخل al و صفر يك داخل ah قرار خواهد گرفت. در داس 5 و بالاتر ممكنه مشكل ايجاد بشه. براي اين منظور از كد زير استفاده كنيد:

کد:
mov ah,33h ; function 33h - actual DOS version
mov al,06h ; subfunction 06h
int 21h ; call interrupt 21h

كه ماژور را در Bl و مينور را در bh قرار مي دهد.
ولي اين فقط ورژن 5 و بالاتر را نشان مي دهد پس براي ورژن پايين بايد از كد قبلي استفاده كنيد.

= = = = == == = =
Multiple Pushes and Pops :

در tasm و a86 مي توانيم به شكل زير:
کد:
push ax bx cx dx ; save registers
pop dx cx bx ax ; restore registers

همه رجيسترها را در يك خط push كنيم و pop .
البته موقع كامپايل به شكل جدا نوشته ميشه و اين فقط براي راحتي و افزايش خوانايي است.
در a86 بين رجيسترها كاما بگذاريد. ويرگول.

= = = = = == = = ==
The PUSHA/PUSHAD and POPA/POPAD Instructions :

دستور PUSHA مشابه دستورات زير عمل مي كند:
کد:
temp = SP
push ax
push cx
push dx
push bx
push temp
push bp
push si
push di

و اين باعث راحتي است. هم در تايپ صرفه جويي ميشه و هم سريع تر از اين دستورات اجرا ميشه.

دستور POPA هم عكسش عمل مي كند . يعني push شده ها را دوباره pop مي كند.
دو دستور PUSHAD and POPAD هم مشابه اينها هستند ولي رجيسترهاي 32 بيتي بكار مي روند. شامل: ESP, EAX, ECX, EDX, EBX, EBP, ESI and EDI.

ادامه در پست بعد.
.
 
آخرین ویرایش:

saalek110

Well-Known Member
ادامه صفحه 6 :

Using Shifts for faster Multiplication and Division :
استفاده از دستور shift براي اجراي سريعتر ضرب و تقسيم:

دستورات ضرب و تقسيم دستورات بسيار كندي هستند و بايد موقعي استفاده شوند كه سرعت مورد نياز نيست.

براي ضرب و تقسيم سريعتر شما مي توانيد از شيفت به چپ يا راست به تعداد يكي يا بيشتر استفاده كنيد.
هر شيفت برابر توان 2 است.
در زبان سي مشابه شيفت اپراتورهاي << و >> را داريم.

دستورات:
کد:
SHL Unsigned multiple by two 
SHR Unsigned divide by two 
SAR Signed divide by two 
SAL same as SHL
سينتكس:
کد:
SHL operand1,operand2

در اين سينتكس در 8086 ما فقط به opperand2 بايد 1 بدهيم.
و در 286/386 تا رقم 31 مي شود به operand2 مقدارداد.

= = = = = = = = = = == = = = =
LOOP :

براي ساخت حلقه بهتره از دستور loop استفاده كنيم نه jmp .
براي ساخت حلقه با loop به cx تعداد چرخش را مي دهيم . و با هر بار رسيدن كنترل به دستور loop به طور اتوماتيك يك واحد از cx كم مي شود.

نكته: دستور loop هم مثل پرشها بايد پرشش زير 127 بايت باشد.
دقيق ترش اينكه 128 به عقب و 127 به جلو.

سينتكس:
کد:
mov cx,100 ; 100 times to loop
Label:
.
. 
.
Loop Label: ; decrement CX and loop to Label
كد بالا مشابه كد زير است:
کد:
mov cx,100 ; 100 times to loop

Label:
dec cx ; CX = CX-1
jnz Label ; continue until done

شرح كد دوم: دستور dec باعث كاهش يك واحد از cx مي شود. دستور dec (decrease ) هم مثل تفريق بر فلاگها اثر مي گذارد و اين تاثير را jnz حس مي كند و با آن پرش خود را تنظيم مي كند. يعني اگر cx صفر بشود ديگر پرش نمي كند و حلقه خاتمه مي يابد.
پرشهاي شرطي هر كدام به فلاگ خاصي نگاه مي كنند. و جدول تقريبا پيچيده اي ايجاد مي شود. چون بعضي از پرشهاي شرطي به چند فلاگ نگاه مي كنند. براي فرار از اين پيچيدگي يك راه اين است كه به جدول تعريف پرشهاي شرطي مراجعه كنيم . چون تعريف پرش شرطي خيلي ساده تر است تا اينكه فكر كنيم به چه فلاگي نگاه مي كند. مثلا تعريف يك پرش شرطي اين است ((اگر بزرگتر باشد)). البته اين نظر من است و ممكن است اشتباه باشد.

برنامه دومي(يعني استفاده از پرش شرطي) در 486 و بالاتر از 486 سريعتر اجرا مي شود.

پرش شرطي jnz موقعي پرش مي كند كه فلاگ صفر ست نشده باشد. و وقتي cx به صفرمي رسد شرط ديگر برقرار نيست و پرش صورت نمي گيرد.

= = = == == = = == =
How to use a debugger :

در اين سايت توربو ديباگر توصيه شده. منظورش اين بوده كه بعد ساخت فايل خود آن را با توربو ديباگر نگاه كنيم. و بفهميم كه برنامه ما به چه شكلي درمي آيد.

اول بايد يك برنامه داشته باشيم كه برنامه زير را پيشنهاد كرده:
کد:
; example program to demonstrate how to use a debugger

.model tiny 
.code
org 100h
start:

push ax ; save value of ax
push bx ; save value of bx
push cx ; save value of cx

mov ax,10 ; first parameter is 10 
mov bx,20 ; second parameter is 20
mov cx,3 ; third parameter is 3

Call ChangeNumbers 

pop cx ; restore cx
pop bx ; restore bx
pop ax ; restore dx

mov ax,4C00h ; exit to dos
int 21h 

ChangeNumbers PROC 

add ax,bx ; adds number in bx to ax
mul cx ; multiply ax by cx
mov dx,ax ; return answer in dx
ret
ChangeNumbers ENDP 

end start

بعد با دستور زير در توربو ديباگر باز مي كنيم:
کد:
td name of file

تصوير زير نمايي ازبرنامه وي را نشان مي دهد:

34jburb.gif


دستورات اول را در ابتداي قطعه در اين عكس مي بينيد يعني اينها:
کد:
cs:0000 50 push ax
cs:0001 53 push bx
cs:0002 51 push cx
من با ديباگ ويندوز باز مي كنم:


2hpneqa.gif


همان كد بالا را فايل com ساختم و در ديباگ با دستوري كه در تصوير هست بازش كردم. و با u 100 ليست دستورات را گرفته ام. شما هم برنامه هايي را بسازيد و با ديباگ نگاه كنيد تا متوجه بشويد كه چطور زيربرنامه ها و پرش ها و حلقه ها كامپايل مي شوند.
در تصوير بالا به مشخصاتي از فايل com اشاره شده. اول اين كه همه قطعه ها شماره قطعه اشان يكي است يعنيبر هم منطبق هستند و ديگر اين كه نشانگر پشته به انتهاي قطعه fffe اشاره مي كند. و هر چه push كنيد اين رقم كوچك مي شود. يعني از ته قطعه به سمت پايين حركت مي كند.
يك فايل exe را هم در ديباگ ويندوز باز كنيد و مقايسه كنيد با فايل com .
.
در فايل اگزه دستورات از خط 100 شروع نمي شوند
بلكه از صفر شروع مي شوند.
.
 
آخرین ویرایش:

saalek110

Well-Known Member
صفحه هفتم (صفحه آخر):

برنامه زير براي انتقال cursor است و برنامه ساده اي است. از خواندن كامنت ها براحتي طرز كارش معلوم است.
از يك وقفه براي انتقال كرزر استفاده شده و قبل صدا كردن آن سرويس و ورودي هاي وقفه را تنظيم كرده.
بعد هم خواندن رشته با سرويس 9 وقفه 21 و چاپ آن. و در آخر هم دستورات خاتمه برنامه.
برنامه هم com است. بي مشكلي اجرا شد.
كد:
کد:
.model tiny 
.code
org 100h
start:

mov dh,12 ; cursor col 
mov dl,32 ; cursor row
mov ah,02h ; move cursor to the right place
xor bh,bh ; video page 0
int 10h ; call bios service

mov dx,OFFSET Text; DS:DX points to message
mov ah,9 ; function 9 - display string
int 21h ; all dos service

mov ax,4C00h  ; exit to dos
int 21h

Text DB "This is some text$" 

end start

برنامه زير هم براي نوشتن توسط سرويس 40 وقفه 21 به كار مي رود.
برنامه exe است.

کد:
.model small
.stack
.code

mov ax,@data ; set up ds as the segment for data
mov ds,ax 

mov ah,40h ; function 40h - write file
mov bx,1 ; handle = 1 (screen)
mov cx,17 ; length of string
mov dx,OFFSET Text ; DS:DX points to string
int 21h ; call DOS service

mov ax,4C00h ; terminate program
int 21h

.data

Text DB "This is some text"

End

برنامه بعدي استفاده از سرويس 13 از وقفه 10 است. ميشه رنگي و هر جاي صفحه چاپ كرد ولي تنظيم آن كمي مشكل است. فايل exe است . بي اشكال اجرا شد. نتيجه اجرا هم چاپ در وسط صفحه و رنگي.
كد:
کد:
.model small
.stack
.code

mov ax,@data ; set up ds as the segment for data
mov es,ax ; put this in es

mov bp,OFFSET Text ; ES:BP points to message
mov ah,13h ; function 13 - write string
mov al,01h ; attrib in bl,move cursor
xor bh,bh ; video page 0
mov bl,5 ; attribute - magenta
mov cx,17 ; length of string
mov dh,5 ; row to put string
mov dl,5 ; column to put string
int 10h ; call BIOS service

mov ax,4C00h ; return to DOS
int 21h

.data

Text DB "This is some text"

end

برنامه زير نشان مي دهد كه چگونه با استفاده از rep stows ميشه در حافظه تصوير نوشت.
فايل exe است . بي نقص اجرا شد. براي اجرا در داس پرومپت اجرا كنيد.
کد:
.model small
.stack
.code

mov ax,0B800h ; segment of video buffer
mov es,ax ; put this into es
xor di,di ; clear di, ES:DI points to video memory
mov ah,4 ; attribute - red
mov al,"G" ; character to put there
mov cx,4000 ; amount of times to put it there 
cld ; direction - forwards
rep stosw ; output character at ES:[DI]

mov ax,4C00h ; return to DOS
int 21h

end

شرح برنامه بالا:

حافظه تصوير در 0B800 قرار دارد. ابتدا اين عدد را در ax و سپس در es گذاشته. چون مستقيما نميشه به es مقدار داد.
بعد di را صفر كرده. كلا ما داريم ES:DI را براي نشانه روي روي حافظه تصوير تنظيم مي كنيم.
در ah رنگ را تنظيم كرده. و در al كاراكتر را تنظيم كرده. (حرف g ).
مي دانيد كه دستور rep كارش تكرار است به تعداد cx ، پس cx را ابتدا تنظيم كرده. 4000 تا تنظيم كرده. مواظب باشيد شما بيشتر از حد لازم ندهيد. چون از حافظه تصوير رد مي شويد و مي رويد به قسمتهاي ديگر ram كه ممكن است باعث شود مجبور شويد ريست كنيد.
بعد با cld جهت را تعيين كرده كه فوروارد (رو به جلو) است.
در آخر هم كه دستور stows باعث انتقال به ram است.

پس حالا با اين برنامه شما در مي يابيد كه در حافظه تصوير براي هر كاراكتر دو بايت در نظر گرفته شده. كه ما اين دو بايت را در ah و al محيا كرده بوديم. با تغيير اعداد در ah و al ببينيد چه نوع جلوه هايي مي توانيد به وجود آوريد.

= = = = = == = = =
برنامه بعدي نشان مي دهد كه چطور مي شود يك رشته را در حافظه تصوير نوشت. فايل exe . اجرا بي اشكال.
کد:
; write a string direct to video memory 
.model small
.stack
.code

mov ax,@data
mov ds,ax

mov ax,0B800h ; segment of video buffer
mov es,ax ; put this into es
mov ah,3 ; attribute - cyan
mov cx,17 ; length of string to print
mov si,OFFSET Text ; DX:SI points to string
xor di,di

Wr_Char:

lodsb ; put next character into al 
mov es:[di],al ; output character to video memory
inc di ; move along to next column
mov es:[di],ah ; output attribute to video memory
inc di

loop Wr_Char ; loop until done

mov ax,4C00h ; return to DOS
int 21h

.data

Text DB "This is some text" 

end

شرح برنامه بالا:
اين برنامه از برنامه قبلي كمي جالب تر و كمي مفصل تر است.
در برنامه قبلي ما از يك رجيستر در حافظه تصوير نوشتيم ولي در اين برنامه از يك رشته به حافظه تصوير مي نويسيم.
مي دانيد كه رشته هاي ما در قطعه داده نگه داري مي شوند. و حافظه تصوير درسته كه در ram است ولي آنچنان ارتباطي با قطعه هاي برنامه ما ندارد.

شرح كدها:
در اين برنامه از lodsb براي انتقال از ram به al استفاده شده. و قبلش بايد براي lodsb تنظيماتش را انجام داد.
بعد كه با اين دستور al را پر كرده با دستور :
کد:
mov es:[di],al

به حافظه تصوير منتقل كرده. بعد يا همين نوع mov آمده ah كه صفت رنگ كاراكتر است و براي همه كاراكترها در اين برنامه ثابت است را به حافظه تصوير منتقل كرده.
قشنگي اين برنامه اين است كه از دو راه با ram ارتباط برقرار كرده يكي با lodsb و ديگري با mov .
البته مي بينيد كه دستور mov هم برايش بايد es و di را تنظيم كرد. براي lodsb هم همين طور. پس تنظيم قطعه و آفست براي اين دو را بايد در نظر داشت كه همه كدهاي اول برنامه مشغول همين كار بوده اند.
حلقه اي كه با loop ساختيم هم مي دانيد كه قبلش بايد برايش cx تنظيم بشه. و داخل حلقه هم di داره يكي يكي لفزايش مي يابد تا خانه هاي حافظه تصوير به نوبت با حلقه پر شوند.
 
آخرین ویرایش:

saalek110

Well-Known Member
ادامه صفحه هفتم:

Mode 13h :
اين mode فقط در كارتهاي VGA, MCGA و بالاتر ساپورت ميشه.
با اين mode استفاده آسان گرافيك ميسر است.
براي چك اين كه اين mode را كامپيوتر شما مي تواند ساپورت كند برنامه exe زير را اجرا كنيد:
کد:
.model small
.stack
.data

NoSupport db "Mode 13h is not supported on this computer."
  db ,"You need either a MCGA or VGA video" 
  db ,"card/monitor.$"

Supported db "Mode 13h is supported on this computer.$"

.code

mov ax,@data ; set up DS to point to data segment
mov ds,ax ; use ax 

call Check_Mode_13h ; check if mode 13h is possible

jc Error ; if cf=1 there is an error

mov ah,9 ; function 9 - display string
mov dx,OFFSET Supported ; DS:DX points to message
int 21h ; call DOS service

jmp To_DOS ; exit to DOS

Error:
mov ah,9 ; function 9 - display string
mov dx,OFFSET NoSupport ; DS:DX points to message
int 21h ; call DOS service

To_DOS:
mov ax,4C00h ; exit to DOS 
int 21h

Check_Mode_13h PROC ; Returns: CF = 1 Mode 13h not possible

mov ax,1A00h ; Request video info for VGA
int 10h ; Get Display Combination Code
cmp al,1Ah ; Is VGA or MCGA present?
je Mode_13h_OK ; mode 13h is supported
stc ; mode 13h isn't supported CF=1

Mode_13h_OK:

ret 
Check_Mode_13h ENDP

end

ديگه اين روزها همه كامپيوترها پيشرفته اند و فكر نمي كنم كسي باشه كه جواب رد از اين برنامه بشنود. براي من هم گفت كه ساپورت مي كند.

حالا استفاده:
برنامه زير(نوع com ) در mode گرافيكي تعدادي پيكسل بر صفحه رسم مي كند:
کد:
; example of plotting pixels in mode 13 using bios services - 
; INT 10h

.model tiny
.code
org 100h
start:

mov ax,13 ; mode = 13h 
int 10h ; call bios service

mov ah,0Ch ; function 0Ch
mov al,4 ; color 4 - red
mov cx,160 ; x position = 160
mov dx,100 ; y position = 100
int 10h ; call BIOS service

inc dx ; plot pixel downwards
int 10h ; call BIOS service
inc cx ; plot pixel to right
int 10h ; call BIOS service
dec dx ; plot pixel up
int 10h ; call BIOS service

xor ax,ax ; function 00h - get a key
int 16h ; call BIOS service

mov ax,3 ; mode = 3
int 10h ; call BIOS service

mov ax,4C00h ; exit to DOS
int 21h

end start
 

saalek110

Well-Known Member
براي شرح برنامه نمايش پيكسل پست قبلي من سري زدم به اين سايت:
وقفه 10 را مي توانيد شرح سرويس هايش را در اينجا ببينيد:

http://www.bookcase.com/library/dos/ints/int10.html

===========================
با توجه به اين صفحه در برنامه دوم پست قبلي ، كد زير:

mov ax,13 ; mode = 13h
int 10h ; call bios service

درست و دقيقش اينه كه ما ah را صفر كرديم و al را برابر 13 قرار داديم تا mode را به حالت گرافيكي ببريم.

در قطعه بعدي برنامه ، يعني قطعه زير:

mov ah,0Ch ; function 0Ch
mov al,4 ; color 4 - red
mov cx,160 ; x position = 160
mov dx,100 ; y position = 100
int 10h ; call BIOS service

داريم set pixel مي كنيم. نقل از سايت بالا:
کد:
Set Pixel

Call 
AH = 0Ch 
AL = Pixel Value 
If bit 7 is set, then value is XOR-ed (not in 256 color mode) 
BH = Page Number 
CX = Pixel Column 
DX = Pixel Row 
Return 
N/A

در سينتكس بالامي بينيد كه چه تنظيماتي براي set pixel مورد نياز است. در برنامه ما bh مقدار دهي نشده. يعني نخواسته ايم page تعيين كنيم.

در قطعه بعدي برنامه ما ، يعني قطعه زير:

inc dx ; plot pixel downwards
int 10h ; call BIOS service
inc cx ; plot pixel to right
int 10h ; call BIOS service
dec dx ; plot pixel up
int 10h ; call BIOS service

كار خاصي انجام نشده. فقط بازي با مختصات پيكسل است و بعد تغيير مختصات احضار وقفه 10 . دستورهاي inc و dec ( اينكريز و دكريز-افزايش و كاهش) را كه آشنا هستيد.
توجه كنيد كه فقط مدام وقفه صدا شده و تنظيمات قطعه قبلي دارد اينجا استفاده مي شود.

قطعه بعدي برنامه ، يعني قطعه زير:

xor ax,ax ; function 00h - get a key
int 16h ; call BIOS service

كارش ايجاد انتظار براي گرفتن كليدي از كاربر است. و با زدن يك كليد از صفحه كليد اين قسمت اجازه عبور مي دهد و به قطعه بعدي ، يعني قطعه زير مي رويم:

mov ax,3 ; mode = 3
int 10h ; call BIOS service

اين جا باز به mode شماره 3 برمي گرديم تا براي خروج آماده شويم.

و اين هم قطعه خروج :

mov ax,4C00h ; exit to DOS
int 21h
 
آخرین ویرایش:

saalek110

Well-Known Member
ادامه صفحه هفتم:

يك نكته فقط از اين صفحه مانده . اين را بگوييم و برويم سراغ منابع بعدي.

در برنامه قبلي ( نمايش پيكسل) سرعت بالا نيست و سرعت بالاتر را با نوشتن مستقيم در حافظه تصوير مي توانيم بدست آوريم.

The VGA segment is 0A000h.
قبلا ديديم كه وقتي مي خواستيم كاراكتري را روي صفحه چاپ كنيم يك راه اين بود كه مستقيما داخل حافظه تصوير بنويسيم و آدرسش را در پستهاي قبلي گفتيم و چند برنامه هم بنا بر آن حافظه نوشتيم.

و حالا آدرس حافظه گرافيكي را به شما مي دهيم. و روش نوشتن در حافظه مثل همان مثالهاي حافظه كاراكتري است و ديگه مثال نمي زنيم.
The VGA segment is 0A000h.

پس پيكسل ها را مستقيما ايجاد مي كنيم.
از فرمول زير هم براي محاسبه آفست استفاده كنيد.
کد:
Offset = X + ( Y * 320 )

شما بايد عددي را در اين حافظه ها قرار دهيد. كه رنگ پيكسل است.

روشهاي قرار دادن در حافظه:

راه اول: با stosb (بايت منتقل مي كند از al ) به آدرس: ES:DI

راه دوم: با استفاده از mov با كد زير:
کد:
mov es:[di], color

مقايسه اين دو روش:
از لحاظ سرعت در پنتيوم براي stosb رقم 3 و براي mov رقم 1 گذاشته شده. حالا من نفهميدم كه اين اعداد 1 و 3 سرعت است يا زمان طول كشيده شده. بعدا از منابع بعدي تكميل مي كنيم.

در مورد mov هم مي دانيد كه بايد بعد هر عمل بايد di را افزايش دهيد.

اگر برنامه اي داريد كه يك سري اسپريت ( شكل هايي كه مثلا در يك بازي نقش شخصيت ها را دارد.) داريد كه بايستي مداوما پاك و رسم شود ، ممكن است دچار فليكر شويد(چشمك زدن ها) .
براي رفع اين قضيه بهتره آن اسپريت را در جاي ديگري در حافظه بسازيد و كپي بگيريد موقع نياز در حافظه نمايشي.

= = = = = = = = = = = = = = = = = = =

اين مجموعه هفت صفحه اي به پايان رسيد. صفحه 5 را نگفتيم چون كار با فايل بود. علت اين كه نگفتم اين بود كه با تنظيم پارامترهاي وقفه ها كار مي كردند و ليست طولاني و كسل كننده اي از تنظيم ها داشت و هر كس هم كه به آنها نياز پيدا كند فكر كنم سريع بتواند راه بياندازد آنها را.

و ثانيا هدف من تمرين با برنامه هاي ساده تر و ساختن برنامه هايي در جهت درك محيط كار است.

اين 7 صفحه آموزش خيلي قسمتهايش كم كار شده بود و من اطلاعات كافي نداشتم تا تكميل كنم آنها را.
در قسمتهايي كه من از خودم توضيحاتي اضافه كرده ام با احتياط كامل بوده و يا با تست قضيه بوده و يا با اطمينان بالا از محفوظاتم بوده. و تلاش من اين بوده كه هيچ اشتباه در اين آموزش موجود نباشد. به همين خاطر ترجيح دادم قسمتهايي گنگ بماند براي خواننده تا اينكه بخواهم چيزهايي را بگويم كه غلط باشد. و بعدا با تمرين از منابع ديگر به شفافيت بحث ها خواهيم افزود.
 
آخرین ویرایش:

saalek110

Well-Known Member
سايت زير:
http://courses.ece.uiuc.edu/ece390/books/artofasm/artofasm.html
اسمبلي 32 بيتي است و كمي سنگين است مباحثش. ولي به نظرم اساسي كار كرده.
در 13 صفحه آموزش داده. البته سورس نداده. . فقط شكل دستوراته و بحث و بيشتر 32 بيتي است نه 16 بيتي. يعني كمي انرژي بيشتري مي برد تبديلش تا اون سايت 7 صفحه اي كه حاضر آماده بود.

بگذريم.

عكسهاي خيلي عالي ئي هم داره. مخصوصا 3 صفحه اول كه عكساش را يك نگاهي بكنيد خيلي خوبه. جداولش را هم ببينيد خيلي عاليه. مثلا يك جدول داره كه گفته از ابتدايي ترين كامپيوترها تا حالا ، هر كدام چه مقدار حافظه را پشتيباني مي كند. كه پنتيوم 4 گيگا بود. البته پنتيوم هاي جديد نبود شايد. پيشنهاد مي كنم 13 صفحه را باز كنيد و در هاردتان ذخيره كنيد و حداقل يك نگاهي به صفحات بكنيد. خيلي خوب دستورات را دسته بندي كرده.
كلا براي حرفه اي ها مي تواند عالي باشد.
ولي اگر فعلا وقت نداريد مهم نيست. سايتهاي بهتري هم هستند.
 

saalek110

Well-Known Member
چاپ رجيستر:

فرض كنيد دو دستور زير را داريم:
Mov al,3
Inc al
كه دستور اول به al مقدار مي دهد و دومي آن را يك واحد افزايش مي دهد.
حالا مي خواهيم al را ببينيم كه متوجه بشويم كه آيا يك واحد افزايش پيدا كرده يا نه.

اينجاست كه چاپ رجيستر معني پيدا مي كند.
در شكل زير من دو دستور را در ديباگ نوشتم و مي بينيد كه درست كار مي كنه . ولي من مي خواهم خودمان al را چاپ كنيم.


2zjaus6.gif

 

saalek110

Well-Known Member
براي مقدمه بايد بگويم كه چه رجيسترها كه حافظه هايي داخل cpu هستند و چه حافظه هاي موجود در ram همه از تعدادي بيت تشكيل شده اند.

واحد خريدن حافظه هم بيت است. البته بايت است كه 8 بيت است و به طور روزمره به مگا بايت و گيگا بايت نام برده مي شود كه ميليون و ميليارد بايت است.

ولي منظور اين كه بيت اساس سخت افزار است.

بيت يا صفر است يا يك. و خارج از اين دو حالت ندارد. البته همه اينها را مي دانند فقط خواستم ذهنمان را آماده كنيم براي كارهاي بعدي.

حالا al را در نظر بگيريد. يك بايت است. با ah كه آن هم يك بايت است مي شود دو بايت. كه دو بايت را يك word يا كلمه مي گويند.

اين al كه يك بايت است هيچ فرقي با بايتهاي درون ram ندارد. البته از لحاظ جنسش مي گويم. وگرنه حافظه هاي cpu به علت ساختار cpu خيلي مهم هستند.

ما قبلا دستوراتي را آموزش داديم كه به راحتي يك رجيستر را به ram و بالعكس منتقل مي كرد.
و دستوراتي داريم كه به راحتي مي تواند رجيسترها را به هم ديگر انتقال دهد.

يعني الان اگر راجع به چاپ al صحبت مي كنيم ، يعني در حقيقت داريم راجع به چاپ يك بايت صحبت مي كنيم كه اين بايت هر جايي از حافظه مي تواند باشد تا به al منتقل شود و چاپ شود.

حالا بحثمان ميشه چاپ بايت:
بايت 8 بيت است.
در مبناي 16 هر 4 بيت را مي شود اين طوري خواند. 0123456789abcdef .
شد 16 حالت. از صفر تا f ميشه 16 حالت. پس al را ميشه 4 بيت ، 4 بيت خواند تا بشه اين طوري a2 يا cc يا 23 يا ff و .....

اما سئوال مهم اينجاست كه ما مي خواهيم al را اين طوري چاپ كنيم 22 يا اين طوري 0010 0010 . اين حالت صفر و يكي هم همان است ولي در مبناي 2 است.
حقيقتش اينه كه اين حالت صفر و يكي همان حالت حقيقي حافظه است چون بيت ها همين طور پشت سر هم به حالت ((صفر و يك)) (روشن و خاموش) داخل ram (يا جاي ديگر) نشسته اند.

ولي كلا خواندن صفر و يك ها مشكل است پس بهتره كه به همان حالت 22 بخوانيم. ولي اين 22 در مبناي 10 نيست ها. در مبناي 16 است. در مبناي 10 اگر مي خواهيد بايد تبديل كنيد.

اين عدد 00100010 را به مبناي 10 تبديل كنيد:
ميشه 2 بعلاوه 32 . چرا؟ جواب: از سمت راست اين طوري بخوانيد يك – دو – چهار – هشت – شانزده – سي و دو – شصت و چهار. و هر جا يك داشتيم اين اعداد را موجود دانسته و با هم جمع بزنيد.

حالا عدد 22h را به مبناي 10 تبديل كنيد. ميشه 2 بعلاوه 2 ضرب در 16 .(عدد سمت راست بعلاوه عدد سمت چپ ضرب در 16 )

نتيجه هر دو يكي است. ولي دومي راحت تر بود.

من دو برنامه آماده دارم كه يكي به شكل صفر و يك چاپ مي كند و يكي در مبناي 16 . ولي مي خواهم دومي را بگذارم. چون به نظر من كاربردش بيشتره.
.
 
آخرین ویرایش:

جدیدترین ارسال ها

بالا