PDA

نسخه کامل مشاهده نسخه کامل : آموزش کامل عالی اسمبلی



Nesta
31-03-2005, 20:38
اسمبلي ياد بگيريم - 1

براي ياد گرفتن اسمبلي بايد با مبناهاي عدد نويسي ، ساختمان داخلي كامپيوتر
و برنامه نويسي آشنا باشيم .
ما برنامه هايمان را مستقيما با اسمبلر Macro Assembler خواهيم نوشت و گاها از Debug
استفاده خواهيم كرد . بعلاوه چون برنامه هاي حجيم نخواهيم نوشت قالب اكثر
رنامه هاي ما COM. خواهد بود .
براي شروع ابتدا نگاهي به حافظه ميكنيم :

حافظه و آدرس دهي

هر كامپيوتر مبتني بر 8086 داراي حداقل 640 كيلوبايت حافظه است . اين 640
كيلوبايت به قطعات 64 كيلوبايتي تقسيم شده و ما اين قطعات را "قطعه " يا Segment
ميناميم . هر سگمنت هم به خانه هاي تك بايتي ديگري تقسيم شده است .

براي بدست آوردن مقدار يك بايت مشخص از حافظه ما بايد عد مربوط به سگمنت و
همچنين شماره آن بايت در سگمنت ( كه آفست Offset ناميده ميشود ) را بدانيم .
مثلا اگر مقدار مورد نظر در قطعه 0030h(h( يعني عدد در مبناي 16 است ) و آفست 13C4h
باشد ما بايد قطعه اي كه شماره آن 0030h است را بيابيم و بعد در همان قطعه
مقدار باين شماره 13C4 را بخوانيم .
براي نمايش اين حالت بين عدد سگمنت و آفست علامت (:) قرار ميدهيم . يعني
ابتدا عدد مربوط به قطعه را نوشته و سپس عدد آفست را مي آوريم :
Segment:Offset

مثال : 4D2F:َ9000 **
هميشه در آدرس دهي ها از اعداد مبناي 16 استفاده ميكنيم .

| | |

| CConvertional | 1 Segment=64K | | | | | Memory

| | | | | |
| | | |
| | | |




ثباتها Registers

رجيسترها مكان هائي از CPU هستند كه براي نگهداري داده ها (DATA) و كنترل اجراي
برنامه بكار ميروند . ما ميتوانيم آنها را مقدار دهي كرده و يا بخوانيم و يا
باتغيير محتواي آنها CPU را مجبور به انجام يك پروسه (رويه يا Procedure) كنيم

دسته اي از رجيسترها كه ما انها را "ثباتهاي همه كاره يا همه منظوره " ميخوانيم
و شامل AX/BX/CX/DX هستند ، براي انتقال مقادير بين رجيستر ها و CPU بكار ميروند.
اين ثباتها را ميتوانيم به هر نحوي تغيير دهيم و مقاديري را به آنهاارسال كنيم .

ثباتهاي ديگري هم كه نام ميبريم كاربردهاي خاص خودشان را دارند و براي مقدار دهي
آنها بايد قواعد خاصي (كه توضيح خواهيم داد) را بكار بريم .

ميكند عدد كه در اين ثبات وجود دارد شماره يك قطعه است و CPU براي يافتن DS : مخفف Data Segment . محل نگهداري متغييرها و ثابتهاي برنامه را مشخص
مقادير لازم به آن قطعه مراجعه ميكند . CS

: مخفف Code Segment است و آدرس قطعه اي كه برنامه در آن قرار گرفته را
نشان ميدهد . ES

: اين يك ثبات كمكي است و معمولا در آدرس دهي ها شماره قطعه را نگهداري
ميكند . DI

DataIndex:Dبا DS/ESا مرتبط است و عدد آفست را نگهداري ميكند . IP

: اين رجيستر معلوم ميكند كه برنامه در حال اجرائي كه در CS قرار دارد از
كدام بايت قطقه (يعني كدام آفست ) شروع ميشود . به همين دليل هميشه اين دو
ثبات را با هم و بصورت CS:IP نشان ميدهند.
و ...

تمام رجيسترهاي فوق 16 بيتي (دوبايتي ) هستند و اعداد دوبايتي را نگهداري ميكنند.
ثباتهاي همه منظوره به دو نيم ثبات تك بايتي تقسيم ميشوند . بايت بالائي ب
نماد H و بايت پائيني با نماد L نشان داده ميشود . مثلا ثبات AX داراي دو نيم -
ثبات AH/AL است :
| AH - 8 Bit | AL -8 Bit |


تمرين :
براي ديدن رجيسترها در DOS، DEBUG، را اجرا كنيد و فرمان R را صادر كنيد :


D:\MASM>DEBUG
-R
AX=0000 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=17AA ES=17AA SS=17AA CS=17AA IP=0100 NV UP EI PL NZ NA PO NC
17AA:0100 0F

Nesta
31-03-2005, 20:39
در اين قسمت ميخواهيم با استفاده از مطالبي كه در بخشهاي قبلي ياد گرفتيم
برنامه اي بنويسيم كه كامل و قابل استفاده باشد . با اين برنامه ميتوانيم
فلاپي ديسكهاي خودمان را با سرعت كپي كنيم ! امروز برنامه را به شكلي مينويسيم كه
بتواند ديسكهاي 1.44 را بوسيله درايو A كپي كند . بيشتر نياز ما در كپي (تكثير)
ديسكها هم به همين شكل هست . با اينحال در قسمت بعدي نگارش (Version) جديدتري از
برنامه را مينويسيم و قابليت تشخيص نوع ديسك و قابليت مشخص كردن درايو را به آن
اضافه ميكنيم .
بهترين كاري كه ميتوانيم بكنيم اينست كه بتوانيم داده هاي خوانده شده از
ديسك را در حافظه EMS بنويسيم (در اين نسخه روي هاردديسك مينويسيم ) . وقتي كه
نحوه كار را حافظه گسترش يافته (Extended Memory) را هم ياد گرفتيم ، برنامه
خود را كامل كرده و از آن بعنوان اولين دستختمان در برنامه نويسي اسمبلي لذت
ميبريم .
ليست برنامه در زير قرار دارد و توضيحات برنامه را روي آن ميبينيم
قبل از آن ياد آوري ميكنم كه هر ديسك HD َ1.44 داراي دو طرف و در هر طرف 80 شيار
(Track) بوده و هر شيار هم به 18 بخش بنام قطاع (Sector) تقسيم ميشود . برنامه
ما بايد محتواي تمام اين قطاعها را خوانده و در فايلي روي ديسك سخت ذخيره كند.
سپس همين داده ها را از فايل خوانده و مجددا روي ديسك جديد بنويسد.



طول هر قطاع 512 بايت است EQU 512 SECTORSIZE
تعداد شيار ها 80 شيار (79- 0-) است EQU 79 MAXTRACK
هر ديسك دو طرف دارد EQU 2 NUMSIDES
تعداد سكتور در هر شيار 18 تا است EQU 118 SECTOR_PER_TRACK E
.MODEL SMALL
.CODE
ORG 100H
START:
JMP MAIN

بافر براي ذخيره (0)BUF DB SECTORSIZE*SECTOR_PER_TRACK DUP
داده ها . اندازه آن به اندازه بايتهاي يك شيار است
معرف رويه فعلي ديسك SIDE D DB 0
معرف تراك جاري TRACK DDB 0
هندل (مشخصه ) فايل HANDLE DW 0

اسم فايل براي دخيره موقت داده ها FILENAME DB 'C:TTEMP.$$$'/0

MSG1 DB 'ENTER A DISK INTO DRIVE A :THEN PRESS A KEY'/13/10/'$'
MSG2 DB 'ENTER A NEW DISK INTO DRIVE A :THEN PRESS A KEY'/13/10/'$'



رويه ReadTrack داده هاي يك شيار را بطور كامل ميخواند . براي خواندن يك شيار
كامل از Int 13h/Ah=02h استفاده كرده ايم . داده ها بعد از خوانده شدن در محلي
كه با ES:BX مشخص ميشود ذخيره ميشوند . (به مرجع اينتراپيتها مراجعه كنيد) قبلا
كار با اين وقفه را توضيح داده ايم (برنامه Boots.asm را ببينيد)


READTRACK PROC ;READ A TRACK
PUSH ES
MOV AX/DS
MOV ES/AX
LEA BX/BUF
MOV AH/2
MOV DL/0 ;DRIVE A:
MOV DH/SIDE
MOV CH/TRACK
MOV CL/1 ;THE 1st SECTOR
MOV AL/SECTOR_PER_TRACK
INT 13H
POP ES
RET
READTRACK ENDP



اين رويه داده هاي موجود در BUF را خوانده و در يك شيار كامل كه با متغير Track
مشخص ميشود مينويسد . براي اينكار از INT 13h/AH=03h استفاده شده است . آدرس
متغير BUF را بايد در ES:BX قرار بدهيم .


WRITETRACK PROC
LEA BX/BUF
PUSH ES
MOV AX/DS
MOV ES/AX


شماره تابع براي نوشتن MOV AH/03
تعداد سكتورها براي نوشتن MOV AL/SECTOR_PER_TRACK
شماره تراك MOV CH/TRACK
شماره سكتور شروع MOV CL/1
رويه ديسك (طرف ديسك ) MOV DH/SIDE
شماره درايو كه اينجا A است MOV DL/0 INT 13H
POP ES
RET
WRITETRACK ENDP


اين پروسيجر به اندازه يك تراك كامل از فايل خوانده و در متغير BUF قرار ميدهد
READFILE PROC
MOV BX/HANDLE

اندازه يك تراك MOV CX/SECTORSIZE*SECTOR_PER_TRACK
آدرس بافر براي ذخيره كه DS:DX است LEA DX/BUF MOV AH/3FH
INT 21H
RET
READFILE ENDP


اين پروسيجر كليه داده هاي داخل BUF كه به اندازه يك تراك كامل (18*512 بايت )
است را خوانده و در فايل مينويسد تا بعدا مجددا خوانده و روي ديسك جديد بنويسد


WRITEFILE PROC
MOV BX/HANDLE
MOV CX/SECTORSIZE*SECTOR_PER_TRACK
LEA DX/BUF
MOV AH/40H
INT 21H
RET
WRITEFILE ENDP



منتظر ميماند تا كليدي فشرده شود WAIT PPROC
تابع خواندن كليد MOV AH/0 INT 16H
RET
WAIT _ENDP



اين رويه فايل با هندل مشخص شده را ميبندد CLOSEFILE PROC MOV AH/3EH
MOV BX/HANDLE
INT 21H
RET
CLOSEFILE ENDP


شروع برنامه اصلي . MAIN:

در اين قسمت اعذم ميكنيم كه ديسكي را در درايو A قرار دهده و كليدي را
برنند . MOV AH/9
LEA DX/MSG1
INT 21H

مكث براي دريافت كليد _CALL WAIT

ساختن فايل براي ذخيره داده ها MOV AH/3CH
LEA DX/FILENAME
MOV CX/0
INT 21H

MOV SIDE/0
MOV HANDLE/AX
MOV TRACK/1

موتور ديسك خوان مدت زماني لازم دارد تا به سرعت كافي برسد . بنا براين بايد
يك يا دو بار قبل از خواندن ديسك ، تابع خواندن را اجرا كنيم تا موتور ديسك در
حالت مناسب قرار بگيرد.
CALL READTRACK ; START UP THE CASSETTE-MOTOR
COPY:
MOV TRACK/0
COPYTRACK:

خواندن شيار CALL READTRACK
نوشتن داده هاي خوانده شده در ديسك CALL WRITEFILE
شيار بعدي INC TRACK
آيا شيار80 هستيم / CMP TRACK/80
نه ، شيار بعدي TRACKS َ; COPY 80 JNZ COPYTRACK

طرف بعدي ديسك INC SIDE
آيا طرف دوم ديسك هستيم ? CMP SIDE/1
نه ، پس ادامه بده JZ COPY
وگر نه فايل را ببند CALL CLOSEFILE

حالا اعلام ميكنيم كه ديسك جديد را در درايو A قرار دهد و كليدي را بزند MOV AH/09H
LEA DX/MSG2
INT 21H
CALL WAIT_

MOV SIDE/0


همان فايل را براي خواندن باز ميكنيم . وقتي كه فايلي را ميسازيم تنها ميتوانيم
در آن فايل بنويسيم . بنا براين براي خواندن از فايل ، بايد آن را بسته و مجددا
براي خواندن باز كنيم . LEA DX/FILENAME
MOV AH/3DH
MOV AL/0
INT 21H

مشخصه فايل در Handle قرار ميگيرد MOV HANDLE/AX

MOV TRACK/1
MOV SIDE/0

اجراي تابع نوشتن براي راه اندازي موتور ديسك CALL WRITETRACK
WRITE:
MOV TRACK/0
WRITE_ON_TRACK:

داده هارا از فايل بخوان CALL READFILE
داده ها را روي شيار بنويس CALL WRITETRACK
شيار بعدي INC TRACK
آيا شيار 80 هستيم ? CMP TRACK/80
نه ، پس ادامه بده JNZ WRITE_ON_TRACK
بله ، طرف بعدي ديسك INC SIDE
آيا الان طرف دوم را هم خوانده ايم ? CMP SIDE/1
نه ، پس شيار بعدي را بنويس JZ WRITE
بله ، فايل را ببند CALL CLOSEFILE

فايلي كه ساخته بوديم فضائي از ديسك سخت را اشغال كرده ، بنا براين بهتر است
آن را با استفاده از وقفه 21h و تابع 3Ah حذف كنيم . LEA DX/FILENAME
MOV AH/3AH
INT 21H ;ERASE THE TEMPORARY FILE
INT 20H
END START

تمام (:

Nesta
31-03-2005, 20:40
خوب ، رجيسترها را ديديم و آشنائي كلي با آنها پيدا كرديم .
حالا ميخواهيم به رجيتسرها مقدار بدهيم و آنها را بخوانيم و ... . ما معمولا در
ےزبانهاي ديگر از علامت =(يا =ا:) براي مقدار دهي استفاده ميكنيم ولي در زبان
ےاسمبلي اين كار ممكن نيست . در عوض از دستورالعمل MOV كمك ميگيريم . با MOV
ميتوانيم داده ها را بين رجيسترها يا خانه هاي حافظه انتقال بدهيم . به اين صورت
MOV in_it/Value


در اينجا In_it به معناي يك رجيستر، نام يك متغير، يا آدرس يك مكان از حافظه
است و Value هم يك مقدار عددي يا حرفي ، نام يك رجيستر و ... ميباشد .
ےمانند MOV AX/200 كه عدد 200 دسيمال را به رجيستر AX منتقل ميكند . (هميشه از
سمت راست به چپ ) .

در زبان اسمبلي ما ميتوانيم با مبناهاي 2وَ10وَ16 كار كنيم . اعداد به طور پيش
فرض مبناي 10 هستند . براي نشان دادن عدد هگزا (مبناي 16) در انتهاي عدد يك
حرف H ( يا h ) و در انتهاي اعداد باينري علامت (b) قرار ميدهيم . مثلا براي نشان
دادن عدد AC1 مبناي 16 بايد حتما آن را بصورت AC1h بنويسيم . به همين ترتيب عدد110b
همان عدد 6 خودمان است .

با اين تفاسير براي دادن مقدار 4Ch به رجيستر AX از دستور زير استفاده ميكنيم :
mov ax/4Ch


به همين شكل ميتوانيم به نيم ثباتها هم مقدار بدهيم . مثلا ميتوانيم براي مقدار
دهي AH بنويسيم : mov ah/20h . در اين حالت مقدار نيم ثبات AL ثابت بوده و
محتواي AH برابر 20h ميشود . چون نيم ثباتها تك بايتي هستند ما نميتوانيم عدد
خارج از محدوده 0 تا 255 يا 128- تا 127 به آنها ارسال كنيم . در مورد اعداد منفي
هم بايد از طريق تبديل به مكمل دو عمل كنيم كه به زودي آن روش را توضيح خواهيم
اد .
مثلا ما نميتوانيم mov ah/100h را انجام دهيم چون 100h برابر 256 بوده و از محدوده
تعريف شده خارج است .
به همين شكل ميتوانيم محتواي ثباتها را هم منتقل كنيم . مثلا براي كپي كردن محتواي
ثبات CXبه DX ميتوانيم بنويسيم : mov dx/cx ، يعني مقدار داخل Cx را به Dx كپي
كن .
ےباز هم بايد به يك يا دوبايتي بودن ثباتها توجه كنيم . به عبارت ديگر ما
ےنميتوانيم مقدار يك ثبات تك بايتي را به يك ثبات كامل دوبايتي منتقل كنيم .
مثلا عبارت mov DX/AL قابل قبول نيست چون AL يك بايتي بوده و DX دوبايتي است .
به عبارت ساده و كامل تر دو طرف عملوند MOV بايد از نظر اندازه برابر باشند.
بنابر اين :
MOV DL/AL
و MOV CL/BHوM درست ولي MOV DS/AH نادرست است .

به علاوه ما فقط ميتوانيم ثباتهاي همه منظوره AXتا DX را به اين صورت مقدار دهي
ےكنيم . در صورتي كه بخواهيم ثباتهائي مثل ..DS/ES/ را مقدار دهي كنيم بايد از
رجيستر AX به عنوان واسطه استفاده كرده و مقدار را از طريق آن انتقال دهيم .
مثلا:
نميتوانيم بنويسيم mov ds/20h
ولي ميتوانيم داشته باشيم :
mov ax/20h
mov ds/ax


ےبه اين ترتيب مقدار 20hبه DS انتقال پيدا ميكند ( گرچه تغيير دادن DS ايده خوبي
نيست !)

ےحالا مطالب گفته شده را تمرين ميكنيم . ما ميتوانيم با DEBUG اسمبلي بنويسيم و
حتي برنامه هاي COM. درست كنيم . بنا براين در DOS، DEBUG، را اجرا كنيد .
D:\LNG\ASM> DEBUG


ےيك خط تيره به صورت - ظاهر ميشود . اين خط تيره اعلان DEUBG براي وارد كردن
دستورات است .
حرف A (به معني شروع وارد كردن دستورات اسمبلي ) را وارد كرده و Enter را بزنيد .
ےعددي بصورت xxxx:0100 ظاهر ميشود . اين عدد براي شما (فعلا) مهم نيست ، پس به
آن توجه نكنيد .
حالا ميتوانيد دستورات زير را وارد كنيد :


MOV AX/100
MOV BX/AX
MOV ES/AX



بعد از وارد كردن خط آخر يكبار ديگر كليد Enter را بزنيد تا اعلان (-) دوباره ظاهر
شود .
در سطر اول ما عدد 100h ( پيش فرض اعداد در Debug هگزا است ) را به AX منتقل
كرديم . بعد مقدار AXبه BX و سپس مقدار AXبه ES منتقل شده . به اين ترتيب همه
ثباتهاي AX/BX/ES بايد در نهايت برابر 100h باشند .
براي ديدن صحت اين مطلب دستور T ( به معناي Trace) را وارد كنيد .
با هر بار اجراي اين دستور يكي از سطرهاي برنامه اجرا ميشود . بعلاوه شما ميتوانيد
محتواي رجيسترها را هم ببينيد .
با اولين فرمان T ، سطر اول اجرا ميشود . بازهم فرمان T را وارد كنيد . الان مقدار100h
به BX داده شد و اگر به محتواي رجيستر AX توجه كنيد خواهيد ديد كه مقدار آن
(همانطور كه انتظار داشتيم ) برابر 100h است . دوبار ديگر هم فرمان T را صادر
كنيد و در نهايت مقدار ثباتهاي AX/BX/ES را ببينيد . هر سه ثبات (حالا) برابر 100h
هستند .
براي خارج شدن از Debug هم فرمان Q به معني Quit را وارد كنيد .
******


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

Nesta
31-03-2005, 20:40
تا اينجا ياد گرفتيم كه چطور مقادير را بين ثباتها منتقل كنيم : با فرمان MOV.
با همين دستور ميتوانيم مقادير را از محلهاي حافظه خوانده يا در آنجا بنويسيم .
براي كار با حافظه دوحالت ممكن است وجود داشته باشد : 1
- آدرس مورد نظر در سگمنت جاري باشد . در برنامه هاي COM. كل برنامه (غالبا)
از يك سگمنت تشكيل ميشود . 2
- آدرس مورد نظر خارج از سگمنت جاري باشد .

ثبات DS هميشه به قطعه اي اشاره ميكند كه داده هاي مورد نياز برنامه در آن
هستند . اين قطعه در برنامه هاي EXE. يك قطعه مستقل است ولي در برنامه هاي COM
. ، قطعه داده هاي و قطعه كد برنامه در يك سگمنت هستند . بنا براين مقدار
ثبات DS در يك برنامه COM. ثابت است .
در حالت كلي آدرس يك محل از حافظه بصورت DS:address مشخص ميشود. DS حاوي
آدرس سگمنت داده ها بوده و address آفست را مشخص ميكند .
چون همانطور كه گفتيم DS در برنامه هاي COM. ثابت است ، پس در صورتي كه آدرس
مورد نظر در همين قطعه باشد از نوشتن DS صرفنظر ميكنيم .
به عنوان مثال اگر قطعه داده هاي برنامه ما 9000h باشد و ما بخواهيم آفست 24h
ام در همين قطعه را بدست بياوريم ، ميتوانيم از يكي از دو شكل زير استفاده
كنيم :
DS:24h
or
24h


البته چون اسمبلر منظور ما از نوشتن عدد 24h را نخواهد فهميد شكل دوم يك خطاي
هنگام ترجمه توليد خواهد كرد ولي ما روش صحيح را هم خواهيم گفت .
ما آدرس ها (يا اشاره گرها) را براي اين ميخواهيم كه بتوانيم به يك خانه از
حافظه دسترسي پيدا كنيم . براي اينكه نشان بدهيم منظور ما از عدد مشخص شده ،
آدرس است نه خود عدد (مثل 24h در مثال قبلي ) آن عدد را داخل [] قرار ميدهيم .
بنا براين :
mov ah/24h عدد 24h را به AX منتقل ميكند ولي ....
mov ah/[24h] محتواي آفست 24h را به AX منتقل ميكند .

در شكل دوم هر مقداري كه در آفست 24h ام سگمنت جاري موجود باشد به ثبات Ah
منتقل ميگردد.
به همين صورت ميتوانيم يك مقدار را به يك خانه از حافظه منتقل كنيم : mov [24h]/ah
: محتواي ثبات AH را به آفست 24h ام منتقل ميكند .
ے اگر آدرس مورد نظر خارج از محدوده سگمنت جاري بوده و در قطعه اي جدا قرار داشته
باشد ، ميتوانيم از DSيا ESا (ترجيحا) براي دستيابي به حافظه استفاده كرد:
مثال : mov ax/9000h
mov ds/ax
mov ah/ds:[89h]


به اين ترتيب ما به آفست 89h از سگمنت 9000h دسترسي پيدا ميكنيم .
البته دستورات فوق مارا به مقصودمان ميرسانند ولي ما نميتوانيم به دلخواه خودمان DS
را تغيير دهيم چون همانطور كه گفتيم DS به قطعه داده هاي برنامه اشاره ميكند و
برنامه ، داده ها و مقادير متغير ها را از سگمنتي كه با DS مشخص شده ميخواند .
بنا براين ما نبايد مقدار DS را تغيير بدهيم مگر اينكه آن را دوباره به حالت اول
برگردانيم . براي ذخيره و بازيابي محتواي رجيسترها، يك روش ساده و عمومي وجود
دارد كه به زودي خواهيم گفت ولي در اين مثال ما ميتوانستيم مقدار قبلي DS را در
يك رجيستر ديگر مثل CX نگهداريم :

انتقال محتواي dsبه AX mov ax/ds
انتقال محتواي AXبه CX mov cx/ax
دادن مقدار9000hبه AX mov ax/9000h
انتقال محتواي AXبه DS mov ds/ax
خواندن آدرس mov ah/ds:[89h]
بازيابي مقدار DS mov ax/cx mov ds/ax


اگر بخواهيم آفست آدرس را با يك رجيستر مشخص كنيم بايد به نكات زير توجه
كنيم : 1
- اگر آدرس سگمنت با DS مشخص شده ، يا آدرس در سگمنت جاري باشد ، بايد
مقدار آفست را در ثبات BX قرار دهيم . مثلا mov cx/[BX]يا mov cx/ds:[bx]ا .
2
- اگر از ES به عنوان مقدار سگمنت استفاده ميشود بايد از DI به عنوان آفست
استفاده كنيم مثل mov cx/es:[di] .

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

Nesta
31-03-2005, 20:41
وقتي كه ما به روش گفته شده مقداري را از حافظه ميخوانيم ، يك داده تك بايتي
از حافظه گرفته ميشود . اما ممكن است بخواهيم كه يك كلمه يا كلمه
مضاعف ( 4بايتي ) را بخوانيم يا بنويسيم . در اين صورت ميتوانيم از
پيشوند هاي زير استفاده كنيم :
Byte Ptr
: براي دست يابي به يك بايت Word Ptr
: براي دستيابي به يك كلمه (2بايت ) Dword Ptr
: براي دست يابي به يك مقدار 4 بايتي

اين پيشوند ها را بايد قبل از آدرس مورد نظر قرار دهيم . به عنوان مثال براي
خواندن يك بايت از آفست 10h ميتوانيم بنويسيم : mov al/byte ptr ds:[10h]

و براي خواندن دو بايت بصورت : mov ax/byte ptr ds:[10h] .
ميتوانيم از همين روش استفاده كرده و مقداري را به حافظه انتقال دهيم . مثلا
ميخواهيم يك كلمه دوبايتي را به آفست 34h (در سگمنت برنامه ) منتقل كنيم . كافي
است بنويسيم :

mov word ptr [34h]/1FCAh .
مثال :
mov bx/34h
mov ax/ds
mov cx/ax
mov ax/00h
mov ds/ax
mov ax/word ptr ds:[bx]
mov ax/cx
mov ds/ax


جمع و تفريق


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

دستورالعمل ADD به ميزان خواسته شده به محتواي يك رجيستر يا متغير اضافه ميكند .
ےمثلا ADD AH/20 عدد 20 را به AH اضافه كرده و مجددا در AH قرار ميدهد . اگر مقدار
فعلي AH برابر 30 باشد بعد از اجراي آن دستور برابر 50 ميشود .
بايد توجه كنيم كه حاصل بدست آمده از محدوده مجاز تجاوز نكند . در اين مثال اگر
حاصل جمع عدد 20 با محتواي AH بزرگتر از 255 باشد ، خطاي سرريز (Over Flow) رخ
ميدهد .
مثال : اين دستورات را در ديباگ وارد كنيد : mov ax/5
add ax/4
int 20


(به معني سطر آخر توجه نكنيد) . حالا يكبار ديگر Enter را بزنيد تا خط اعلان Debug
ظاهر شود . حرف G را بزنيد تا برنامه شما اجرا شود . حالا فرمان آشناي R را براي
ديدن محتواي رجيسترها وارد كنيد و مقدار AX را ببينيد .

دستورالعمل SUB برعكس ADD بوده و به مقدار خواسته شده از محتواي يك ثبات يا
متغير كم ميكند . مثلا SUB AX/100h به اندازه 256 (100h) از AX كم كرده و نتيجه را
دوباره در AX قرار ميدهد .

مثال : mov bbx/100h SUB bx/50


در اين مثال حاصل bx را از 100 به 50 كاهش داده ايم .
فرمان INC يك حالت خاص از ADD بوده و تنها يكواحد به محتواي ثبات اضافه ميكند
مثلا inc cx يعني يك واحد به cx اضافه كن .
و برعكس اين ، دستور dec يكواحد از محتواي ثبات كم ميكند . مانند : dec cx .
ے بايد توجه كنيم كه اين دستورات تنها روي ثباتهاي همه منظوره DX.AX.D قابل
استفاده هستند .

پس امروز مطالب مربوط به اينها رو ياد گرفتيم :
byte ptr / word ptr / dword ptr
add / sub / inc / dec

Nesta
31-03-2005, 20:41
وقفه ها (Interrupts) CPU
براي اينكه بتواند كارهاي مختلفي را انجام دهد،از وقفه ها استفاده ميكند . يك
ےوقفه درخواستي از CPU است كه در طي آن زير برنامه اي اجرا ميشود. وقتي كه وقفه
فراخواني ميشود، CPU اعمال ديگر را متوقف كرده و آن اينتراپت را پردازش ميكند
به طور كلي وقفه ها به دودسته تقسيم ميشوند:
ےَ1- وقفه هاي سخت افزاري (Hardware Interrupts) . وقفه هائي هستند كه از سوي
ے ادوات سخت افزاري كامپيوتر مانند كيبورد و ... اجرا ميشوند. مثلا با فشرده يارها
شدن هر كليد ، يكبار وقفه شماره 9 فراخواني ميشود. 2
- وقفه هاي سخت افزاري (SoftWare Interrupts). اين وقفه ها در بايوس (BIOS)
كامپيوتر قرار دارند. بايوس كامپيوتر يك تراشه (IC) قابل برنامه ريزي است كه
بنا بر نوع پردازنده بر روي برد اصلي كامپيوتر قرار ميگيرد . بعلاوه خود DOS
نيز وقفه اي (وقفه 21h) را اداره ميكند كه به وقفه DOS معروف است . اين توابع
توسط MSDOS.SYS تعريف ميشوند ولي در نهايت به بايوس مراجعه ميكنند.
هر وقفه داراي يك شماره خاص خود است و از صفر شروع ميشود . وقفه 21h (سرويس DOS
) نيز داراي 255 سرويس ديگر است .
براي اينكه بتوانيم يك برنامه خوب و مفيد بنويسيم بايد بتوانيم از اينتراپتها
به نحو صحيح استفاده كنيم . پس هر برنامه نويس اسمبلي بايد يك مرجع كامل
اينتراپت در اختيار داشته باشد.
وقتي ميخواهيم يك وقفه را فراخواني كنيم ، ابتدا (درصورت لزوم ) ثباتهاي خاصي را
مقدار دهي ميكنيم . معمولا نيم ثبات AH ، از اين جهت كه اكثر اينتراپتها
داراي چند سرويس مختلف هستند ، شماره تابع را مشخص ميكند . بهمين صورت ، و
اگر لازم باشد ، ثباتهاي ديگر را هم مقدار دهي ميكنيم . مثلا فرض كنيد ميخواهيم
كليدي را از صفحه كليد بخوانيم . تابع شماره 0 از وقفه 16h ميتواند اين كار را
انجام دهد . وقتي ميگوئيم تابع شماره 0 ، يعني بايد به AH مقدار 0 بدهيم و بعد
اينتراپت 16h را فراخواني كنيم .
فراخواني اينتراپت به سادگي و با دستورالعمل INT انجام ميشود. به صورت :
INT int_no

كه int_no شماره اينتراپت ميباشد . در مورد اين مثال بايد دستورات زير را انجام
دهيم : mov ah/0
int 16h

وقتي يك وقفه فراخواني ميشود ، ممكن است روي ثباتها تاثير گذاشته و مقدار آنها
را عوض كند. به اين وسيله ما ميتوانيم وضعيت اجراي وقفه را بدست بياوريم . در
مورد اين مثال ، پس از خوانده شدن كليد ، كد اسكي (ASCII) كليد در ثبات AL قرار
ميگيرد . مثلا اگر حرف A تايپ شود ، مقدار AL برابر 65 خواهد بود.
حالا اگر عدد AH را قبل از فراخواني وقفه بجاي 1 برابر Eh قرار دهيم و وقفه 10hرا
اجرا كنيم ، بجاي خواندن كليد، يك كاراكتر را چاپ ميكند . به اين صورت كه كد
اسكي كاراكتر در ثبات AL و عدد Eh در ثبات AH قرار گرفته و وقفه 10h فراخواني
ميشود . mov AX/0E07h
in 10h

به سطر اول توجه كنيد !. وقتي ما يك عدد دوبايتي (Hex) را به AX ارسال ميكنيم ،
دوبايت بالا در AH و دوبايت پائين در AL قرار ميگيرد . پس در اين مثال كاراكتر
شماره 7 بايد چاپ شود و چون اين كد مربوط به كاراكتر Bell است ، صداي بيپ
شنيده خواهد شد.

خاتمه دادن به برنامه :
وقتي كه يك برنامه به انتها رسيد يا اگر خواستيم اجراي برنامه را متوقف
كنيم ، ميتوانيم از اينتراپت 20h استفاده كنيم . DOS هميشه و بمحض اجراي اين
وقفه ، اجراي برنامه را متوقه ميكند.
اينراپت 20h فقط با برنامه هاي COM. درست كار ميكند و در مورد برنامه هاي EXE.
درست جواب نميدهد . در عوض سرويس 4Ch از اينتراپت 21h در هر دونوع برنامه
بخوبي كار ميكند .
خوب ، حالا با مطالبي كه ياد گرفتيم يك برنامه اسمبلي نوشته و فايل COM. آن را
ميسازيم .
بنابر اين در محيط DOS، DEBUG، را اجرا كنيد .
D:\MASM>DEBUG

سپس دستورد A را به معني شروع دستورات اسمبلي وارد كنيد : - A
xxxx:0100

به عدد آدرسي كه ديده ميشود توجه نكرده و دستورات زير را تايپ كنيد . mov ah/2
mov al/7
int 16
int 20

بعد از تايپ آخرين سطر، يكبار ديگر هم كليد Enter را بزنيد تا اعلان debug مجددا
ظاهر شود. حالا دستور N را براي نامگذاري برنامه بكار ببريد: - N BELL.COM

بعد از آن بايد طول برنامه را ، برحسب بايت ، مشخص كنيم . طول برنامه در ثبات CX
نگهداري ميشود پس از فرمان RCX براي مقدار دهي استفاده ميكنيم . (طول برنامه 8
بايت است ) . - RCX
8

و در نهايت فرمان w براي نوشتن روي ديسك و Q براي خروج . حالا ما يك فايل COM.
داريم كه به محض اجرا يك صداي Beep توليد ميكند .

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

Nesta
31-03-2005, 20:42
در اين قسمت طرز استفاده از ماكرواسمبلر را ياد ميگيريم و برنامه هايمان را بدون
استفاده از Debug مينويسيم .
براي استفاده از اسمبلر بايد يك اديتور اسكي مثل EDITيا PE2ا داشته باشيد تا
بتوانيد برنامه هايتان را توسط آن تايپ كنيد . هر برنامه اسمبلي داراي يك فايل
منبع (Source) حاوي دستورالعملهاي اسمبلي است . ما اين فايل را با يك ويرايشگر
تايپ كرده و به ماكرواسمبلر MASM.EXE ميدهيم تا فايل مفعولي (OBJ.) آن را بسازد
. اين فايل هم بايد با برنامه Link.exe به فرم EXE. تبديل شود . چون ما ميخواهيم
برنامه هاي COM. بتويسيم بايد فايل exe. توليد شده را با EXE2BIN.COMيا EXE2COMا
به فرم com. تبديل كنيم .
فرض كنيد در محيط ويرايشگر(مثلا EDIT ) هستيم و ميخواهيم يك برنامه اسمبلي
بنويسيم .
هر برنامه از 3 قطعه (سگمنت ) تشكيل ميشود : 1
-قطعه داده ها يا DATA SEGMENT . متغيرهاي برنامه و ساير داده هاي مورد نياز در
اين سگمن قرار ميگيرند . 2
- قطعه كد يا Code Segment . كدها و دستورات اسمبلي در اين قسمت هستند . 3
- بخش انباره يا Stack Segment . اين قطعه زير برنامه ها و مقادير موقتي را
نگهداري ميكند . ما حتي ميتوانيم محتواي ثباتها را به پشته (Stack) منتقل كرده و
بعد دوباره از آن خارج كنيم .
در يك برنامه COM. قطعه داده ها و قطعه مد در يك سگمنت قرار دارند بنا براين
ما قطعه داده ها را تعريف نميكنيم . بعلاوه قطعه سگمنت هم براي يك فايل COM.
وجود ندارد بلكه خود DOS اين محيط را فراهم ميكند . به همين دلايل است كه نوشتن
برنامه هاي COM. آسانتر است . با اين حال ما با محدوديتي مواجه هستيم و آن
اينست كه سايز يك برنامه COM. نميتواند بيش از 64 كيلو بايت باشد .
فرض كنيد ميخواهيم همان برنامه اي كه صداي Beepتوليد ميكرد را با اسمبلر بنويسيم
پس يك فايل (مثلا bell.asm) ميسازيم : EDIT BELL.ASM
حالا ما در محيط ويرايشگر هستيم . برنامه ما به اين شكل خواهد بود :

. MODEL SMALL
. CODE
MOV AH/0EH
MOV AL/7
INT 10H
INT 20H
END


در سطر اول ، جمله model small. يك رهنمود مترجم است . رهنمودهاي مترجم
كداجرائي نيستند ولي اسمبلر را در ترجمه برنامه راهنمائي ميكنند . MODEL SMALL.
به اسمبلر ميگويد كه ما ميخواهيم برنامه com. بنويسيم و قطعه داده ها و كدها
مشترك است . اين جمله بايد هميشه وجود داشته باشد. CODE
. ميگويد كه قسمت كدهاي اجرائي شروع ميشود . ما بايد هميشه دستوراتمان را
بعد از يك CODE. شروع كنيم و در انتها نيز جمله END را به معني اتمام برنامه
بنويسيم .
بعد از اتمام اين مراحل از ويرايشگر خارج شده و با MASM.EXE فايل برنامه را ترجمه
ميكنيم : MASM BELL.ASM
در پرسشهاي masm كليد enter را بزنيد . اگر برنامه را صحيح تايپ كرده باشيد بايد
اين پيغامها را دريافت كنيد :

Microsoft( R )Macro Assembler Version 5.10
Copyright( C )Microsoft Corp 1981/ 1988 .All rights reserved.
50084 + 396073 Bytes symbol space free
0 Warning Errors
0 Severe Errors


حالا فايل BELL.OBJ ساخته شده و بايد آن را لينك كنيم : LINK BELL.OBJ

و نتيجه اين خواهد بود:

Microsoft( R )Overlay Linker Version 3.69
Copyright( C )Microsoft Corp 1983-1988 .All rights reserved.

:Run File [ASM6.EXE]
فقط Enter بزنيد | :List File [NUL.MAP]
:Libraries [.LIB] LINK : warning L4021 :no stack segment


سطر آخر يك پيغام خطا است ولي دقيقا همان چيزي است كه انتظار داريم . يعني
وجود نداشتن قطعه پشته (Stack) . به همين دليل برنامه EXE. توليد شده توسط Link
قابل اجرا نيست . پس با EXE2COM آن را به يك فايل COM. تبديل ميكنيم . EXE2COM BELL.EXE

و داريم :

EXE2COM Version 1.0( - c )Computer Magazine
ASM6.EXE converted to ASM6.COM( 8 Bytes )
Warning :Program begins at Offset 0( Entry point .)
ASM6.COM cannot be called directly!


الان فايل COM. هم توليد شد ولي EXE2COM ميگويد كه ما نميتوانيم برنامه را
فراخواني و اجرا كنيم . چرا!?
اگر بياد داشته باشيد وقتي ميخواستيم در DEBUG اسمبلي بنويسيم ، دستوراتمان هميشه
از آدرس xxxx:0100h شروع ميشد. دليل آن اينست كه DOS هميشه يك فضاي 256 بايتي
بنام PSP در ابتداي برنامه ايجاد كرده و اطلاعات فوق العاده مهمي را در آن
نگهداري ميكند . بنا براين برنامه ما بايد حتما از آدرس 100h شروع شود . اين
قانون اسمبلر براي نوشتن برنامه هاي COM. است . پس كد برنامه را به شكل زير
اصلاح كنيد :

. MODEL SMALL
. CODE

دستورالعمل جديد ORG 100H MOV AH/0EH
MOV AL/7
INT 10H
INT 20H
END


راهنماي Org 100hبه DOS ميگويد كه برنامه بايد از آدرس 100h شروع شود . ما اين
كد را اجبارا در همه برنامه ها قرار خواهيم داد . حالا برنامه را با تغييرات اعمل
شده ذخيره كرده و با انجام مراحل قبلي دوباره ترجمه كنيد . پس از ترجمه فايل BELL.COM
را اجرا كرده و نتيجه را مشاهده كنيد %

امروز برنامه اي با اسمبلر نوشيتم . از اين پس نيز تمام برنامه هاي را با
اسمبلر مينويسيم و از توانائيهاي آن استفاده ميكنيم .

Nesta
31-03-2005, 20:42
پرشهاي غير شرطي

ے اگر با زبانهائي مثل Basicيا Pascalا برنامه نويسي كرده باشيد حتما از دستور Goto
ے هم استفاده كرده ايد . بوسيله اين فرمان ، ما ميتوانستيم روال اجراي برنامه را به
يك نقطه مشخص انتقال بدهيم بدون اينكه نياز به برقراري شرط خاصي باشد .
در زبان اسمبلي هم چنين دستوري داريم : دستورالعمل JMP (مخفف JUMP) .
دستور JMP به اين شكل استفاده ميشود:
برچسب JMP
ے منظور از برچسب مكاني از برنامه است . در اسمبلي براي اينكه يك نقطه از برنامه
ے را علامت بزنيم ، نام برچسب مورد نظر را مينويسيم و براي اينكه اسمبلر آن را با
ے يك دستورالعمل اجرائي اشتباه نكند، كاراكتر (:) را در مقابل آن قرار ميدهيم
مانند: :Start
سپس ميتوانيم با دستور JMP به آنحا پرش كنيم : Start :
:
:
Jmp Start


دقت كنيد كه بعد از Start ي كه در مقابل JMP نوشتيم علامت : قرار نداده ايم .
ے اين JMP ها از نوع JUMP NERA هستند و نوعي ديگر بنام JUMP FAR هم داريم كه بزودي
آن را هم ياد ميگيريم .
ے مثال : برنامه اي كه در مثالهاي قبل نوشتيم را در نظر بگيريد . اگر ما از روي
دستورالعملهاي برنامه با JMP پرشي انجام دهيم هيچكدام از آن كدها اجرا نخواهندشد:


1] JMP Quit _
2] mov ax/0E07h
3] int 10h
4] Quit :_
5] int 20h


برنامه از روي سطرهاي 2وَ3 پرش خواهد كرد .

ثبات پرچم (Flags)


ے ثبات پرچم يك ثبات 16 بيتي است كه 1يا 0ا بودن بيتهاي آن نشانه درست يا
ے نادرست بودن يك شرط است . مثلا اگر با دستورالعمل خاصي (ميخوانيم ) تست كنيم
كه آيا ثبات BX مقدار 0 را دارد ، در اين صورت بيت 6 برابر 0 ميشود و ... .
از اين 16 بيت فقط 9 بيت استفاده ميشود كه به شرح زير هستند :
16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
* * * * * O D I T S Z * A * P * C

علامت * به معناي بي استفاده بودن است .

ے 1- پرچم نقلي يا (CF) . بيت 0 در نتيجه اجراي وقفه ها يا بعضي اعمال حسابي تغيير
ميكند .
ے 2-پرچم توازن (ZF) . بر اساس يك عمل مقايسه اي يا حسابي تغيير ميكند . اگر
نتيجه يك عبارت 0 باشد مقدار 1 و اگر نتيجه 1 باشد مقدار 0 ميگيرد.
ے 3-پرچم وقفه (IF) . اگر 0 باشد هيچ وقفه اي نميتواند اجرا شود و اگر 1 باشد
ميتوان وقفه ها را فراخواني كرد .

ے و ... . 6 پرچم ديگر را فعلا لازم نداريم بنا براين توضيحي براي آنها ارائه
نميكنيم .

دستور مقايسه اي CMP


ے براي مقايسه مقاديراز دستور CMP (مخفف CoMPare) استفاده ميكينم . اين دستور
ے مقدار داخل يك ثبات يا متغير را با مقداري ديگر مقايسه كره و روي ثبات هاي
ے CFو ZFو تاثير ميگذارد . بعد از مقايسه ميتوانيم بر حسب وضعيت پرچمها پرش
لازم را انجام دهيم .

ے مثلا CMP BX/0 تست ميكند كه آيا مقدار BX برابر 0 است يا نه . در صورتي كه برابر
0
باشد ،پرچم ZF برابر 1 ميشود .
با همين دستور CMP ميتوانيم كوچكتر،بزرگتر و .... را هم تست كنيم .

پرشهاي شرطي


براي پرشهاي شرطي از دستورهاي زير درست مثل JMP استفاده ميكنيم .
ے JE/JZ : اگر محتواي ZF صفر باشد جهش ميكند . اگر دو مقداري كه مقايسه كرده ايم
برابر باشيند پرش انجام ميشود.
ے JNE/JNZ : برعكس JZو JEو هستند و اگر ZF يك باشد (بعبارتي دو مقداري كه مقايسه
كرديم برابر نباشند) جهش انجام ميشود.
ے JA/JNBE . اگر محتواي ثبات يا متغيري كه مقايسه كرده ايم بزرگتر از عدد مورد نظر
باشد پرش انجام ميدهد . مثلا :


mov bh/1
cmp bh/10
ja Dest



ے مقدار BH برابر 1 است و در سطر دوم تست آن را با 10 مقايسه ميكنيم . در سطر سوم
چون BH بزرگتر از 1 نيست ، پس پرش JA Dest انجام نميشود .

JAE/JNB . اگر بزرگتر يا مساوي باشد ، پرش انجام ميشود.
JB/JNAE: در صورتي كه كوچكتر باشد پرش انجام ميشود.
JBE : در صورتي كه كوچكتر يا مساوي باشد پرش انجام ميشود .

مثال :
ے ميخواهيم برنامه اي بنويسيم كه تمام كاراكترهاي بين 128 تا 255 را
چاپ كند.


. MODEL SMALL
. CODE
ORG 100H
START :

كاراكتر 128 براي شروع ; MOV CH/128 CHARS :

كداسكي را درAL قرار ميدهيم تا چاپ شود ; MOV AL/CH
سرويس 0Eh براي چاپ كاراكتر ; MOV AH/0EH
اينتراپت 10h ; INT 10H
يكواحد به CH اضافه كن ; INC CH
مقايسه CH با 255 ; CMP CH/255
اگر مساوي نباشد به CHARS پرش ميكند ; JNZ CHARS
پايان ; INT 20H END START



ے تمام برنامه ساده و روشن است ولي در سطر آخر نكته جديدي وجود دارد . بعد از
ے END نام برچسب Start را آورده ايم . نكته اي كه در نوشتن برنامه هاي اسمبلي بايد
ے مراعات كنيم اينست كه : اگر از برچسبي در برنامه استفاده ميكنيم ، اسمبلر بايد
ے يك برچسب را به عنوان نقطه آغاز كدبرنامه ببيند . به همين خاطر علاوه بر برچسب
ے CHARS يك برچسب بنام Start هم در ابتداي برنامه تعريف كرده و براي اينكه
ے اسمبلر بداند مما كدام برچسب را براي انيكار اينخاب كرده ايم ، نام آن را در
مقابل END مي آوريم ، يعني END START .

ے نكته ديگر اينكه ، در اسمبلر هر چيزي كه بعد از كاراكتر (;) باشد ، توضيح
ے (Comment) فرض شده و اصلا ترجمه نميشود . (مثل REM در بيسيك و .. ) . هر comment
ے بايد در يك سطر جاي داده شود و اگر از اين مقدار بيشتر بود ميتوانيم در سطر بعد
هم يك كاراكتر (;) درج كرده و ادامه توضيحات را بعد از آن بياوريم .


پايان يخش هفتم

Nesta
31-03-2005, 20:43
برنامه اي
براي تعريف رنگهاي جديد!
اگر داراي كارت ويدئوي VGA و طبعا VGA BIOS باشيد ، ميتوانيد از تابع 10H مربوط
به اينتراپت 10h (كه مربوط به سرويسهاي تصويري است ) براي تعريف پالت هاي جديد
استفاده كنيد . تعداد رنگهائي كه ميتوان آنها را تغيير داد به نوع كارت گرافيك
و VGA BIOS مربوط است و در حالت عادي رنگهاي شماره 0تا 7ا قابل تعريف هستند .
پالت رنگ را به اين صورت بايد تعريف كنيم : AH=10H
AL=10H

شماره رنگ از 0تا BX= 7ا
عدد رنگ سبز CH=
عدد رنگ آبي CL=
عددرنگ قرمز DH=

رنگهائي كه ديده ميشود ، تركيبي از سه رنگ اصلي قرمز،سبز و آبي (RGB) هستند .
براي تعريف يك رنگ جديد نيز بايد مقدار هر رنگ اصلي در پالت مورد نظر را در
نيم ثباتهاي CH/CLو DHوC قرار دهيم . اين مقادير 6 بيتي و در محدوده 1 تا 63
هستند .
پس از مقداردهي ثباتها اينتراپت 10h را فراخواني ميكنيم . در مثال زير ما رنگ
شماره 1 كه آبي ميباشد را تغيير داده ايم .

تمرين : برنامه را براي رنگهاي شماره 2تا 7ا نيز با مقادير دلخواه تكميل كنيد


. MODEL SMALL
. CODE
ORG 100H
START :
MOV AH/010H
MOV AL/010H
MOV BX/1 ; COLOR NUMBER
MOV CH/12 ; GREEN VALUE
MOV CL/24 ; BLUE VALUE - THE 16-BIT NUMBER
MOV DH/14 ; RED VALUE
INT 10H ; VIDEO BIOS INT .
INT 20H ; TERMINATE PROGRAM
END START



ما در اينجا وجود VGA BIOS را تست نكرده ايم و اگر اين برنامه روي كامپيوتري
با كارت گرافيك EGA و ... اجراشود نتايج غيرقابل پيش بيني بدست خواهد آمد.
يك راه ساده براي تست وجود كارت VGA وجود دارد . به اينصورت كه مقدار ثباتهاي AH
و ALو را به ترتيب برابر 1Ah و 00h قرار داده و اينتراپت 10h را اجرا ميكنيم
اگر بعد از فراخواني وقفه ، AL برابر 1Ah بود يعني كارت VGA فعال است .
پس در برنامه اي كه نوشتيم ميتوانيم با يك دستور CMP ساده از بوجود آمدن خطاي
نبود VGA BIOS جلوگيري كنيم .
بنا براين برنامه را به اين صورت تكميل ميكنيم :


. MODEL SMALL
. CODE
ORG 100H ; BEGINING OFFSET : 100H
START :



MOV AH/1AH
اين قسمت را | MOV AL/00
اضافه كرده ايم | INT 10H CMP AL/1AH ; VGA BIOS EXIST? |

; NO UJUMP TO THE END JNZ NOVGA
MOV AH/010H
MOV AL/010H
MOV BX/1 ; COLOR NUMBER
MOV CH/12 ; GREEN VALUE
MOV CL/24 ; BLUE VALUE - THE 16-BIT NUMBER
MOV DH/14 ; RED VALUE
INT 10H ; VIDEO BIOS INT.
NOVGA:
INT 20H ; TERMINATE PROGRAM
END START



در برنامه بالا اگر بعد از اجراي وقفه 10h مقدار AL برابر 1Ah نباشد، نميتوانيم
از سرويس تعريف رنگ استفاده كرده و مجبوريم برنامه را با پرش به NOVGA خاتمه
دهيم .


در اين قسمت با نوشتن يك برنامه ، دو تابع مفيد از وقفه 10h را ياد گرفتيم و
ديديم كه نوشتن يك برنامه اسمبلي برخلاف آنچه تا بحال تصور ميكرديم چقدر ساده
و جالب است .

Nesta
31-03-2005, 20:44
دستورالعمل LOOP

تا اينجا هروقت كه ميخواستيم يك حلقه ايجاد كنيم از دستورالعمل CMP و پرشهاي
شرطي استفاده ميكرديم . راه ساده تري براي اجراي مكرر دستورالملها وجود دارد و آن
استفاده از LOOP است . دستور LOOP به تعداد دفعاتي كه با ثبات CX مشخص ميكنيم
حلقه اي را ايجاد ميكند .
براي ايجاد چنين حالتي ابتدا مقدار لازم را در ثبات CX قرار ميدهيم . دستور Loop
هميشه از مقدار CX يك واحد،يك واحد كم ميكند تا به 0 برسد . وقتي كه مقدار CX
برابر 0 شد ، از حلقه خارج ميشود . بنا براين براي ايجاد حلقه اي كه 100 بار
تكرار شود، CX را برابر 100 قرار ميدهيم . MOV CX/100


حلقه مورد نظر بين دستور loop و يك برچسب انجام ميشود . برچسب ، در ابتداي حلقه
و دستور loop در انتهاي آن قرار ميگيرد.


MOV CX/100
LPCNT :
:
:
:
LOOP LPCNT



در بالا با mov cx/100 ميخواهيم حلقه اي داشته باشيم كه 100 بار انجام بشود. وقتي
به loop lpcnt ميرسيم ، يكواحد از مقدار CX كاسته شده و مجددا به lpcnt پرش
ميكنيم .
به همين سادگي ! .
تمرين : برنامه اي بنويسيد كه كاراكتر هاي با كد اسكي 32 تا 255 را نمايش دهد.

راهنمائي :
چون هميشه CX در حال كاهش است ، براي اينكه بتوانيم از 32 تا 255 برويم ، بايد
عدد داخل CX را برابر 31=224-َ255 قرار بدهيم تا تعداد 254 حرف چاپ بشود. سپس
مقدار CX را از 255 كم كرده و داخل AL قرار ميدهيم تا با تابع 0Eh ار وقفه 10h
چاپ شود . اين وقفه را در برنامه ALLCHR.ASM توضيح داديم .

پشته (Stack) ، ذخيره و بازيابي ثباتها

ما تعدادي ثبات براي نگهداري و انتقال اعداد و مقادير داريم ولي كافي نيستند .
بخصوص در DEBUG كه نميتوانيم متغيرتعريف كنيم . يا در برنامه پيش مي آيد
بخواهيم براي يك كار خاص و بطور موقت مقدار ثبات را تغيير دهيم ،در اين مواقع
مقدار ثبات را در پشته ذخيره كرده و بعدا مجددا بازيابي ميكنيم .
در برنامه هاي EXE. پشته يا Stack يك سگمنت مستقل است و آدرس آن در ثبات SS
(Stack Segment) قرار دارد . در برنامه هاي COM. پشته به آنصورت وجود ندارد و
خود DOS فضاي لازم را براي برنامه فراهم ميكند . در هر صورت ما به اينكه پشته در
كجاست كاري نداريم و به يك شكل مقادير را به پشته فرستاده (PUSH) يا از آن
خارج ميكنيم (POP) .
خاصيت مهمي كه در PUSHو POPو كردن مقادير به پشته وجود دارد اينست كه هميشه
اولين مقداري كه به پشته فرستاده ميشود، آخرين مقداري است كه از پشته خوانده
ميشود . مثلا فرض كنيد كه ابتدا AX و بعد CX را به پشته ميفرستيم . حال براي خارج
كردن درست اين مقادير، ابتدا CX و بعد AX را خارج ميكنيم .
اين قانون اسمبلي است و به (FILO=First In Last Out) معروف است .

براي فرستادن مقدار يك ثبات به پشته از دستور PUSH استفاده ميكنيم .
مثلا براي قرار دادن AX مينويسيم : PUSH AX . PUSH
نميتواند مقدار يك نيم ثبات را در پشته قرار دهد و حتما بايد يك ثبات
كامل دوبايتي باشد .
وقتي ثباتي را PUSH كرديم ، مقدار آن در Stack نگهداري ميشود و ميتوانيم مقدار آن
را تغيير دهيم .
پس از آن ، از دستور POP براي خارج كردن ثبات از پشته استفاده ميكنيم مانند . POP AX


مثال :


1] MOV AX/0AH ; ax = 0Ah
2] MOV BX/0BH ; bx = 0Bh
3] PUSH AX ; push ax to stack
4] PUSH BX ; hold bx in stack
5] MOV AX/5 ; now ax=5
6] MOV BX/2AH ; and bx=2Ah
7] : ; other commands and
8] : ; statements ...
9] POP BX ; POP bx from stack
10] POP AX ; read ax from stack



در سطر 3وَ4 مقادير axو bxو را به پشته ميفرستيم . در سطر 5و 6و مقادير جديد به ax
bx/ ميدهيم و بعد از آن يكسري دستورات ديگر هستند ... . در سطر 10 مقدار BX
از داخل Stack بيرون كشيده ميشود . توجه داشته باشيد كه bx را بعد از ax در پشته
قرار داده ايم ولي در هنگام خارج كردن به ترتيب عكس عمل ميكنيم .

بعلاوه دستور PUSHF به معني PUSH FLAGS ، ثبات پرچم را در پشته قرار ميدهد . نحوه
كار با آن هم مثل PUSH معمولي است ولي آرگومان ندارد .
مثال : PUSHF


در قسمت بعد، اين مطالب را تمرين ميكنيم و چند برنامه نمونه ميبينيم ، حتي
يك برنامه گرافيكي با 256 رنگ مينويسيم و در آن از دستورات LOOPو PUSHو
استفاده ميكنيم .

Nesta
31-03-2005, 20:44
در اين قسمت يك تمرين ديگر با هم انجام ميدهيم و برنامه اي مينويسيم كه تعداد 200
رنگ از 256 رنگ موجود در حالت 320x200 گرافيكي را نمايش دهد .
تابع شماره 00h از وقفه 10h مربوط به تعيين حالت نمايش است . كد مربوط به
حالت صفحه نمايش در ثبات AL قرار گرفته و وقفه فراخواني ميشود:

شماره تابع برابر AH=00h
حالت صفحه نمايش با استفاده از جدول AL= INT 10h




حالت صفحه نمايش در AL از جدول مخصوص موجود در كتابهاي اسمبلي بدست مي آيد.
كد مربوط به حالت 256C َ320x200 برابر 13h است بنا براين AL را برابر 13h قرار
ميدهيم .
براي نمايش و روشن كردن يك نقطه (Pixel) در حالت گرافيكي از تابع 0Ch همين
وقفه استفاده ميكنيم . يعني شماره ستون را در CX ، سماره سطر را در DX و شماره
رنگ را در AL قرار داده و وقفه را اجرا ميكنيم . براي اينكه از ستون 199 تا ستون
شماره صفر نقطه روشن كنيم ، CX را برابر 319 قرار داده و با دستور LOOP نقاط را
در يك سطر روشن ميكنيم .


MOV CX/319 ; COLUMN 319 = START COLUMN
COL:
INT 10H ; CALL INTERRUPT 10H
LOOP COL
سپس DX يا همان شماره سطر را يكواحد افزايش ميدهيم و مقدار آن را با 199 مقايسه
ميكنيم (چون از 0 تا 199 سطر داريم ) و اگر برابر نبود دوباره عمليات بالا را
انجام ميدهيم .

بعد از اينكه اين عمليات انجام شد، تابع 00h از INT 16h را فراخواني ميكنيم تا
منتظر دريافت يك كليد از صفحه كليد شود . به اين ترتيب ميتوانيم نتيجه برنامه
را مشاهده كنيم و كليدي را براي اتمام برنامه بزنيم .
در نهايت بايد حالت صفحه نمايش را به مود متني برگردانيم .
براي اينكار از همان تابع تعيين مود نمايشي استفاده ميكنيم و حالت صفحه نمايش
كه با AL مشخص ميشود را برابر 3 قرار ميدهيم .
برنامه را با روش گفته شده تايپ و كامپايل كنيد . اگر از توربو اسمبلر استفاده
ميكنيد با فرمان ASM/T.َTASM VGA256 آن را به فرم COM. ترجمه كنيد.


. MODEL SMALL
. CODE
ORG 100H
START :
MOV AH/00H
MOV AL/13H
MOV BX/00H ; PAGE NUMBER
INT 10H ; SET TO 320x200 256 COLORS

MOV AH/0CH ; PUTPIXEL FUNCTION
MOV AL/25 ; COLOR #25
MOV DX/0 ; ROW 0
ROW :
MOV CX/319 ; COLUMN 319 = START COLUMN
COL :
INT 10H ; CALL INTERRUPT 10H
LOOP COL ; DOWN TO CX=0
INC DX ; DX=DX+1
INC AL ; AL=AL+1( COLOR NUMBER )
CMP DX/199 ; IF DX=199
JNZ ROW ; ELSE JUMP TO ROW

MOV AH/00H
INT 16H

MOV AH/00H ; VIDEO MODE SET
MOV AL/03H ; 80x25 16 COLORS
INT 10H ; CALL INT .10H
INT 20H ; TERMINATE PROGRAM
END START

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

Nesta
31-03-2005, 20:45
حتما با ثابتها در زبانهائي مثل پاسكال آشنائي داريد . بعنوان مثال با جمله
ے Const MaxLen=1024; ، ثابتي بنام MaxLen تعريف شده و مقدار آن برابر 1024 قرار
ے قرار ميگيرد . پس از آن كامپايلر در هرجا كه MaxLen را مشاهده كند عدد 1024 را
بجاي آن قرار ميدهد .
در زبان اسمبلي براي تعريف يك ثابت از معرفه EQU به شكل زير استفاده ميكنيم

مقدار EQU نام ثابت
مثلا : MaxLen EQU 1024
ے به اين ترتيب اسمبلر هميشه بجاي MaxLen عدد 1024 را قرار ميدهد . بهمين دليل
ثابتهاي برنامه را بايد قبل از جمله CODE. بنويسيم . مثال :


. MODEL SMALL
SECTORS EQU 18
SIDES EQU 2
. CODE
:
:


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

. MODEL SMALL
BELL EQU 7
. CODE
ORG 100H
MOV AH/0EH
MOV AL/BELL
INT 10H
INT 20H
END



متغيرها

ے از متغيرها براي نگهداري موقتي داده ها استفاده ميكنيم . مثلا در زبان پاسكال
ے ميتوانيم با عبارت Var يك متغير تعريف كنيم مثل Var Buffer:Byte; و در زبان
سي مثل unsigned char Buffer; .
ے متغيرها در زبان اسمبلي بايد حتما در داخل قطعه داده (DS) تعريف بشوند و در
ے برنامه هاي COM. هم از آنجائي كه قطعه داده ها و كد يكي است ميتوانيم در قطعه كد
نيز تعريف كنيم .
ے براي تعريف يك متغير بايد بعد از نام آن يكي از عبارات ..DB/DW/DD/ را
ے بياوريم . DB مشخصه نوع بايت ،DW مشخصه نوع Word (دوبايتي ) و DD مشخصه نوع
(Double Word) 4 بايتي است .
مثلا :

. CODE
SIZE DW 1024
BELL DB 7


ے در اين مثال Size يك متغير دو بايتي بوده و مقدار اوليه ان 1024 است و BELL نيز
يك متغير تك بايتي با مقدار 7 ميباشد .
ے اگر نميخواهيم به متغير مقدار اوليه بدهيم ، ميتوانيم از علامت (?) بجاي مقدار
استفاده كنيم مانند : MaxLen DW ?

ے براي تعريف يك رشته كاراكتري از معرفه DB استفاده كرده و محتواي رشته را داخل
('') يا ("") قرار ميدهيم . مثلا :

MSG DB "ASSEMBLY / A QUICK LOOK ! "


ے در اين مثال MSG يك متغير كاراكتري است . در اسمبلي ميتوانيم از كد اسكي
ے كاراكتر ها نيز استفاده كنيم . مثلا اگر در تعريف DB بخواهيم كدهاي اسكي 13و 10
را به MSG اضافه كنيم ميتوانيم با كاما اين كار را انجام دهيم :


MSG DB "ASSEMBLY / A QUICK LOOK ! "/13/10


يا : MSG DB "ASSEMBLY / A QUICK LOOK ! "/0Ah/0Dh

يا حتي : MSG DB "ASSEMBLY / A QUICK LOOK ! "/0Ah/0Dh/'$'


اين تركيبها همه يك رشته كاراكتري معرفي ميكنند .

براي تعريف آرايه ها نيز از روشي مشابه و به شكل زير استفاده ميكنيم :

(مقدار اوليه )DUP تعداد عناصر DB/DW/DD نام متغير
مانند: BUFFER DB 1024 DUP(0 )

كه ارايه اي يك كيلوبايتي تعريف كرده و همه عناصر آن را با 0 پر ميكند.
اگر نخواهيم مقدار اوليه اي در نظر گرفته شود از ? استفاده ميكنيم .
مانند: BUFFER DB 1024 DUP(? )

و براي تعريف يك آرايه حرفي بايد با يك حرف يا عبارت آن را پر كنيم : BUFFER DB 1024 DUP("A" )

و حتي : BUFFER DB 1024 DUP("STACK" )


ے گفتيم كه متغيرها هميشه (در برنامه هاي COM.) در قطعه كد و بعد از CODE. نوشته
ے ميشوند ، بنا براين اسمبلر هميشه سعي خواهد كرد كه آنها را بصورت يك كدماشين
ے قابل اجرا تفسير كند. به همين دليل هميشه بايك دستور JMP از روي آنها پرش
ميكنيم . مثال :


. MODEL SMALL
RDISK EQU 2
. CODE
ORG1 100H
START :
JMP MAIN
BUFFER DB 512 DUP(0 )
MSG DB "DISK DUP."/13/10/'$'
MAIN :


مجموعه كدهاي اجرايي برنامه :
: END START

همانطور كه ميبينيد با دستور JMP MAIN از قسمت تعريف داده ها پرش كرده ايم .

در قسمت بعد نحوه استفاده از متغيرها و بافرها (Buffers) را مطالعه
ميكنيم . با همين معلومات برنامه اي براي نمايش محتواي سكتوهاي ديسك
مينويسيم و پيغامهاي لازم را به آن اضافه ميكنيم .

پيروز باشيد

Nesta
31-03-2005, 20:45
در اين قسمت نحوه دسترسي به مقادير متغير ها را ياد ميگيريم .
ے وقتي كه ميخواهيم مقدار يك متغير را به يك متغير يا ثبات ديگر منتقل كنيم بايد
ے به اندازه آن توجه داشته باشيم . مثلا اگر متغيري بصورت LOCATE DB 10 تعريف كرده
ے باشيم ، به دليل تك بايتي بودن ، نميتوانيم آن را به يك ثبات كامل مثل AX يا
متغير دوبايتي كه با DW تعريف شده است ارسال كنيم .
ے اما انتقال آن به يك نيم ثبات مثل ALيا AHا و ... مجاز است مانند . MOV BH/LOCATE


ے از متغيرها بيشتر براي نگهداري موقت داده ها استفاده ميشود . مثلا وقتي كه
ے برنامه اي براي كار با قطاعهاي ديسك مينويسيم ، بايد يك محل موقتي براي ذخيره
ے محتواي قطاع هاي خوانده شده ايجاد كنيم . در اين موقع يك متغير به شكل (ترجيحا)
آرايه تعريف ميكنيم .
ے وقتي به اين شكل با متغيرها برخورد ميشود، به دانستن آدرس آن نياز پيدا ميكنيم
فرض كنيد ميخواهيم جمله A QUICK START TO ASSEMBLY PROGRAMMING را چاپ كنيم .
در قدم اول بايد متغيري تعريف كرده و اين جمله را داخل آن قرار دهيم .
پس : MSG DB 'A QUICK START TO ASSEMBLY PROGRAMMING'/13/10/'$'


ے اعداد 13وَ10 انتهاي رشته براي انتقال مكان نما به سطر بعد هستند و كاراكتر '$'
ے از اين جهت وجود دارد كه تابع چاپ رشته انتهاي رشته كاراكتري را با بودن $
تشخيص ميدهد.
ے براي چاپ رشته كاراكتري راه هائي وجود دارد كه يكي از آنها استفاده از تابع 9h
مربوط به INT 21h ميباشد .
براي فراخواني آن بايد به اين صورت رجيستر ها را پر كنيم : AH=09H

آدرس رشته كاراكتري DS:DX = INT 21H

ے عبارت DS:DX نشان ميدهد كه مقدار قطعه (Segment) رشته كاراكتري ، يعني آن قطعه
ے اي كه متغير تعريف شده در آن قرار گرفته است ، را بايد در DS قرار بدهيم . به
همين صورت نيز مقدار آفست (Offset) آن را به DX انتقال ميدهيم .
براي بدست آوردن شماره قطعه يك متغير از عملگر SEG استفاده ميكنيم .
ے مثلا براي بدست آوردن شماره قطعه MSGاز MOV AX/Seg MSGز استفاده ميكنيم . اين
دستور شماره سگمنت MSG را پيدا كرده و در AX قرار ميدهد .
براي بدست آوردن شماره آفست هم از OFFSET استفاده ميكنيم مثلا MOV DX/OFFSET MSG
پس براي چاپ رشته MSG بايد به اين صورت عمل كنيم :


MOV AH/09H
MOV DX/OFFSET MSG
INT 21H



ے اين قطعه كاري كه ما ميخواهيم را انجام ميدهد و اگر دقت كنيد متوجه ميشويد كه
ے اصلا شماره قطعه (Segment) را محاسبه نكرده ايم . علت اينست كه متغير ما به دليل
ے COM. بودن برنامه در Code Segment ( كه با CODE. مشخص ميشود) تعريف شده پس خود
ے بخود DS حاوي مقدار سگمنت آن هست . ( باز هم ياد آوري ميكنيم كه CS حاوي شماره
ثبات كد و DS حاوي ثبات داده ها است و در برنامه هاي COM. مقدار برابر دارند)

ے يك دستور خلاصه براي بدست آوردن عدد آفست وجود دارد بنام LEA .كل كاري كه اين
ے دستورالعمل انجام ميدهد اينست كه ديگر احتياج به نوشتن OFFSET نخواهد بود . به
عنوان مثال MOV DX/OFFSET MSGبا LEA DX/MSGا برابر است .
با اين تفاسير كل برنامه به اين شكل خواهد بود .

. MODEL SMALL
. CODE
ORG 100H
START :
JMP MAIN ; skip to main codes
MSG DB 'A QUICK START TO ASSEMBLY PROGRAMMING'/13/10/'$'
MAIN :
LEA DX/MSG ; get MSG offset
MOV AH/09 ; write string function
INT 21H ; call interrupt 21h
INT 20H ; terminate program
END START



تمرين :
ے براي اينكه تمرين بهتري داشته باشيم ، ميخواهيم خودمان و فقط با استفاده از وقفه
ے مربوط به چاپ كاراكتر همين جمله را چاپ كنيم . قبلا گفتيم كه تابع 0Eh از وقفه
ے 10h يك كاراكتر را در محل مكان نما چاپ كرده و مكان نما را يك خانه به راست
انتقال ميدهد. ميخواهيم رشته كاراكتري بالا را تا رسيدن به علامت $ چاپ كنيم .
ے بهترين كار اينست كه عدد آفست را در BX قرار بدهيم . در اينموقع آفست اولين
ے كاراكتر در BX است . مقدار داخل اين آفست را بصورت MOV al/[bx] به ثبات AL
ے منتقل كرده و بعد چاپ ميكنيم . براي كاراكتر بعدي يكواحد به BX اضافه ميكنيم و
ے دوباره همان كارهاي قبلي ... . اين عمليات را بايد تا رسيدن به كاراكتر '$' ادامه
بدهيم .
ے ** اين برنامه را خودتان و بدون توجه به راه حل ارائه شده بنويسيد و فايل COM.
آن را بسازيد.


. MODEL SMALL
. CODE
ORG 100H
START :
JMP MAIN ; jump to MAIN
MSG DB 'A QUICK START TO ASSEMBLY PROGRAMMING'/13/10/'$'
MAIN :
LEA BX/MSG ; get MSG offset
MOV AH/0EH ; write char function
LOOP :_
MOV AL/[BX] ; move [BX] to AL: charactre code
CMP AL/'$' ; if al is equal with '$'
JE END _; then jump to END _
INT 10H ; otherwise call interrupt 10h
INC BX ; BX=BX+1
JMP LOOP _; jump to next caharcter
END :_
INT 20H ; terminae program
END START

Nesta
31-03-2005, 20:46
در اين قسمت يك تمرين جديد انجام داده و بوسيله آن تجربيات خودمان را افزايش
ميدهيم . اين تمرين اولين برنامه ما هست يك عمليات سطح پائين سيستم ، يعني
دسترسي به ديسك ، را انجام ميدهد .

تابع شماره 2(AH=2() از وقفه 13h كه وقفه ديسك ميباشد ، براي خواندن سكتورهاي
ديسك بكار ميرود . وقتي شماره هد، شيار،سكتور و ... را مشخص كرده و وقفه را
فراخواني نموديم ، محتواي قطاع خوانده شده در محلي كه با جفت ثبات ES:BX مشخص
ميشود قرار ميگيرد. به همين دليل ما يك آرايه تعريف ميكنيم وآدرس آن را در ES:BX
قرار ميدهيم تا اطلاعات خوانده شده در آن قرار بگيرد. BUF DB 512 DUP(0)

در اين مثال ما ميخواهيم برنامه اي بنويسيم كه محتواي قطاع بوت كننده ديسكي را
خوانده و نمايش دهد . چون ميخواهيم يك سكتور را بخوانيم و اندزه هر قطاع 512
بايت است ، پس آرايه اي با 512 عنصر تك بايتي تعريف كرده ايم .
در قدم بعدي شماره شيار (0-79) را در CH، شماره قطاع (1-18) را در CL، شماره
درايو را در DL(.../.:0=A ) ، رويه ديسك را در DH(0=1st Side() و تعداد قطاعهائي
كه بايد خوانده شوند را در AL قرار ميدهيم .

MOV AX/0201H
MOV CH/0 ;TRACK NUMBER
MOV CL/1 ;SECTOR NUMBER
MOV DH/0 ; SIDE #1
MOV DL/0 ; 0=A / 1=B /( ...DRIVE NUMBER )



حالا بايد آدرس بافر تعريف شده (BUF) را به ES:BX منتقل كنيم . گفتيم كه براي
بدست آوردن شماره سگمنت هر متغير از Seg و براي بدست آوردن مقدار آفست از Offset
استفاده ميكنيم . چون برنامه ما COM. است ، پس عدد سگمنت بطور خودكار در
ثبات DS هست و احتياجي به يافتن آن نداريم . بلكه فقط بايد آن را به ES منتقل
كنيم .
گفتيم كه نميتوانيم ثبات ESو DSو را مستقيما و با MOV به هم منتقل كنيم ، بلكه
بايد از يك ثبات همه منظوره مثل AX كمك بگيريم . بنا براين و براي اينكه مقدار
فعلي ثبات AX از بين نرود، ابتدا AX را در Stack قرار داده و مقدار DS را در آن
قرار ميدهيم : PUSH AX ; SAVE AX
MOV AX/DS

و پس از آن محتواي AX را به ES داده و دوباره مقدار AX را از پشته POP ميكنيم : MOV ES/AX
POP AX

پس از آن با دستور LEA مقدار آفست BUF را بدست مي آوريم : LEA BX/BUF

و در نهايت فراخواني اينتراپت 13h. INT 13H


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


MOV AL/BUF[BX]
CMP AL/32
JB SKIP
:
:
SKIP :
MOV AL/'.'
INT 10H
:
:


ليست كامل :

.MODEL SMALL
.CODE
ORG 100H
START:
JMP MAIN
BUF DB 512 DUP(0)
MAIN:
MOV AX/0201H
MOV CH/0 ;TRACK NUMBER
MOV CL/1 ;SECTOR NUMBER
MOV DH/0 ; SIDE #1
MOV DL/0 ; 0=A / 1=B /( ...DRIVE NUMBER)
PUSH AX ; SAVE AX
MOV AX/DS
MOV ES/AX
POP AX
LEA BX/BUF
INT 13H

MOV BX/1
MOV AH/0EH ; WRITE CHAR.
PRINT:
MOV AL/BUF[BX]
CMP AL/32
JB SKIP
INT 10H
JMP CONT
SKIP:
MOV AL/'.'
INT 10H
CONT:
INC BX
CMP BX/513
JNZ PRINT
INT 20H
END START

Nesta
31-03-2005, 20:47
عملگرهاي بيتي

عملگرهاي بيتي مانند عملوندهاي حسابي هستند با اين تفاوت كه روي بيت ها كار
ميكنند. اين عملگرها عبارتند از : ... AND/OR/XOR/SHR/SHL/RCL/RCR/ .

عملگر AND : اين اپراتور بيتهاي دو عدد(متغير) را با هم AND كرده و حاصل را در
متغير (يا ثبات ) سمت چپ قرار ميدهد . اگر فرض كنيم كه هميشه 1 بودن بيت به
معناي Trueو 0و بودن آن به معناي False است ، AND هميشه در صورتيكه هر دوبيت
مقايسه شونده 1 باشند، حاصل 1يا Trueا را بر ميگرداند .
جدول ارزشي AND :
X | Y | X and Y |

1 | 1 | 1 |
1 | 0 | 0 |
0 | 1 | 0 |
0 | 0 | 0 |


پس وقتي ما عملوند AND را با دو رجيستر بكار ميبريم ، بصورتي كه گفته شد بيتها
با هم مقايسه شده و حاصل مقايسه در محل متناظر بيتها در ثبات سمت چپ قرار
ميگيرند . مثلا اگر دستور AND Ah/Dh را اجرا كنيم ، حالتي نظير شكل زير را داريم :
AH : 01101010
DH : 01111101

AH :AH AND DH : 01101000


به نتيجه بدست آمده توجه كنيد .
هر وقت كه بخواهيم بيت هاي خاصي از يك رجيستر را 0 كنيم ، يك عدد باينري كه
همه بيتهاي آن ، بجز بيتهاي مورد نظر 1 هستند را در نظر گرفته با رجيستر مورد نظرAND
ميكنيم .مثلا اگر بخواهيم بيتهاي دوم و سوم ثبات AX را صفر كنيم : AND AX/11111001b

عملگر OR :
اين عملوند بيتهاي دو عدد را با هم مقايسه كرده و اگر يكي از آن دو 1 بود ، بيت
متناظر در ثبات سمت چپ را 1 ميكند . مثلا با دستور OR AH/DH بيتهاي AHبا DHا
مقايسه شده و هر دو بيت متناظر كه با هم 0 بودند ، بيت تناظر در AHهم 0 ميشود
AH : 01101010
DH : 01111100

AH : AH OR DH : 01111110


هرگاه كه بخواهيم بيت هاي خاصي از يك متغير يا رجيستر را 1 كنيم ، يك عدد
باينري كه همه بيتهاي آن غير از بيتهاي مورد نظر 0 هستند در نظر گرفته و با ثبات
مورد نظر OR ميكنيم . مثلا اگر بخواهيم دو بيت پائين AHرا 1ا كنيم منويسيم : OR AH/00000011b


عملگر : XOR
عملوند XOR تنها در صورتي نتيجه 1 ميدهد كه دو بيت مقايسه شونده غيرهم ارزش
باشند . يعني يكي 1 و ديگري 0 باشد .
بعنوان مثال با اجراي XOR AH/DH اين عمليات روي بيتها انجام ميشود
AH : 01101010
DH : 01111100

AH : AH XOR DH : 11101001


وقتي بخواهيم يك مقدار ثبات را برابر صفر قرار بدهيم ، معمولا از آن را با خودش XOR
ميكنيم . مثلا XOR CX/CX محتواي ثبات CX را برابر 0 قرار ميدهد .

عملگرهاي SHRو SHLو :

اين عملگرها، بيتها را به راست و چپ شيفت ( انتقال ) ميدهند .
SHR Reg.nnum و SHL Reg.nnum
.Reg اسم يك ثبات است مثلا AXو numو معلوم ميكند كه چند بيت بايد به طرف
راست يا چپ انتقال پيدا كند . مثلا SHR AX/6 بيتهاي AXرا 6ا واحد به راست
انتقال داده و بيتهاي چپ را با 0 پر ميكند . AX :10100010
AX :SHR AX/4 : 00001010

SHL
هم عكس اين عمل را انجام ميدهد . يعني بيتها را به چپ شيفت داده و از
طرف راست با 0 پر ميكند . AX :10100010
AX :SHR AX/4 : 00100000

مثال : اگر بخواهيم كه محتواي نيم ثبات CL را به نيم ثبات CH منتقل كنيم ،
كافيت كه CXرا 8ا بيت به سمت چپ شيفت بدهيم . يعني SHL CX/8

CL CH | 10110100 | 00101101 |


محتواي اوليه CX CX

CL CH | 00101101 | 00000000 |


محتواي CX بعد از SHL CX/8
انتقال

ادامه اين بحث را در قسمت بعد انجام ميدهيم .

Nesta
31-03-2005, 20:47
در قسمت قبلي چند عملگر بيتي را ديديم . در اين قسمت هم اين مبحص را دنبال
ميكنيم .
عملگر Not : عملوند Not ارزش همه بيتهاي يك بايت يا كلمه را برعكس ميكند .
يعني تمام بيتهاي 1را 0ا و تمام بيتهاي 0را 1ا ميكند . بعنوان مثال اگر AH حاوي
مقدار 10101101 باشد، بعد از اجراي Not Al ، محتواي AL بصورت 01010010 خواهد
بود.

جدول ارزشي Not Not X X N
F | T
T | F



عملگر Neg . اين اپراتور معمولا با Not اشتباه ميشود در صورتي كه كمتر شباهتي بين
آنها وجود دارد . Neg ارزش عددي يك عدد علامتدار را برعكس ميكند . يعني يك عدد
منفي را مثبت ميكند و برعكس . در اعداد علامتدار ( همانطور كه بعدا هم خواهيم
ديد )، اولين بيت سمت چپ ( بيت هشتم ) بيت علامت است . 1 بودن آن نشاندهنده
منفي بودن و 0 بودن آن نشان دهنده مثبت بودن است .
عملگر Neg با عكس كردن بيت علامت ، ارزش عدد را عكس ميكند .
اين عملوند را در مبحث اعداد علامتدار مفصلا ميخوانيم .
مثال : ميخواهيم برنامه اي بنويسيم كه تمام حروف كوچك يك عبارت را به حروف
بزرگ تبديل كند . از نظر مقدار عددي ، تفاوت حروف كوچك و بزرگ در اينست كه
بيت پنجم در حروف بزرگ برابر 0 و در حروف كوچك 1 است . مثلا كداسكي حرف a
به باينري برابر 01100001 و كد A برابر 01000001 است . پس برنامه اي مينويسيم
كه تمام كاراكترهاي رشته كاراكتري مورد نظر را خوانده و بيت پنچم آنها را 0 كند
. در قسمت قبلي ديديم كه براي 0 كردن يك بيت ، يك عدد باينري كه تمام بيتهاي
آن 1 ، و بيت مورد نظر (براي 0 كردن ) 0 باشد را با عدد مورد نظر AND ميكنيم .


. MODEL SMALL
. CODE
ORG 100H
START :
JMP MAIN
MSG DB ' this is an example/ ..$'
MAIN :


بدست آوردن آدرس رشته ; LEA BX/MSG LOOP :_

قرار دادن كاركتر در AL ; MOV AL/[BX]
آيا كاراكتر $ است ? ; CMP AL/'$'
بله ، به MAIN برو ; _JZ END
بيت پنجم را صفر كن ; AND [BX]/11011111B
يكواحد به BX اضافه كن - كاراكتر بعدي ; INC BX JMP LOOP _;
END :_

قرار دادن آفست رشته در DX ; MOV DX/BX
تابع 9 براي نمايش رشته ; MOV AH/9
قفه 21h ; INT 21H
پايان برنامه ; INT 20H END START


معمولا برنامه هائي كه پيغامهاي خود را كد ميكنند ( مثل ويروسها ) از اين روش يا
روشي مشابه براي Decode كردن پيغامها استفاده ميكنند .

مثال :
بايت وضعيت صفحه كليد كه مربوط به وضعيت كليد هاي كنترلي CapsLock/NumLock
در بايوس هاي AT/PS2 در آدرس 0017h:0040h قرار دارد.
بيتهاي اين بايت نشان ميدهد كه كدام كليد فعال است . 1 بودن به معني روشن بودن
و 0 به معني خاموش بودن آن است . در مثال زير بيت ششم براي كليد CapsLockرا 1ا
ميكنيم تا Capslock روشن شود .


.MODEL SMALL
.CODE
ORG 100h
START:
PUSH ES
MOV AX/0040h
MOV ES/AX
MOV AL/ES:[17h]
OR AL/32
MOV BYTE PTR ES:[17h]/AL
POP ES
MOV AH/1
INT

Nesta
31-03-2005, 20:48
ضرب و تقسيم با 8088
8088
براي ضرب دو عدد از دستورالعمل MUL با كد ماشين F7h استفاده ميكند .
اين دستورالعمل روي كلمه ها ( دوبايتي ها) كار ميكند . بنا براين حاصلضرب دو عدد16
بيتي ميتواند 32 بيتي يا 2 كلمه اي باشد . به همين دليل براي ذخيره نتيجه ضرب
يك ثبات تنها كافي نيست . MUL
هميشه محتواي يك ثبات را در محتواي ثبات AX ضرب كرده و حاصلضرب را در
جفت ثبات DX:AX ذخيره ميكند . به اينصورت كه دوبايت بالا را در DX و كلمه
پائين را در AX قرار ميدهد. وقتي از حساب 8088 صحبت ميكنيم ، نوشتن DX:AX به
معني نگهداري عدد 32 بيتي در آن جفت ثبات است نه محلي از حافظه كه با DX:AX
مشخص ميشود.
براي ضرب محتواي يك رجيستر در AX ( هميشه در AX ضرب ميشود) از MUL بصورت زير
استفاده ميكنيم : MUL Register


كه منظور از رجيستر يك ثبات مانند DXيا BXا است .
چون هميشه محتواي رجيستر مورد نظر در AX ضرب ميشود، نيازي به نوشتن AX نداريم .

بعنوان مثال اگر AX برابر 100hو BXو برابر 7C4Bh باشد ، حاصل MUL BX برابر 7C4B00h
ميشود و چون اين مقدار يك عدد 32 بيتي است ، در جفت ثبات DX:AX
نگهداري ميشود به شكلي كه DX=7C4Bhو AXو برابر 0000h باشد.

عمل تقسيم هم به شيوه مشابهي انجام ميشود.
وقتي كه دوعدد را به هم تقسيم ميكنيم حاصل تقسيم ( قسمت صحيح يا خارج قسمت ) در AX
و باقيمانده در DX قرار ميگيرد.

براي تقسيم كردن دو ثبات به هم ، از دستور DIV استفاده ميكنيم . وقتي از DIV براي
تقسيم استفاده ميكنيم ، CPU محتواي جفت ثبات DX:AX را بر محتواي ثبات ذكر
شده تقسيم ميكند بنا براين در هنگام استفاده از DIV فقط نام همان ثبات را ذكر
ميكنيم . مثلا اگر بخواهيم ( برعكس ضربي كه در بالا انجام داديم ) عدد 7CB400h را
بر 100h تقسيم كنيم ، BX را برابر 100h و جفت ثبات Dx:Ax را برابر 7CB4100h
قرار ميدهيم . براي اينكار عدد 7CB4100h را به دو نيمه 007Ch و 4100h تقسيم كرده
و در DXو AXو قرار ميدهيم . (/ DX:007Ch AX=4100h) . در نهايت با DIV BX عمل
تقسيم را انجام ميدهيم .
بعد از انجام تقسيم 7C4Bhدر AXر و 0000hدر DXر قرار ميگيرد.

مثال براي ضرب

براي ديدن مطالب گفته شده ، همان دو عدد 100hو 4CBhو را با ديباگ در هم ضرب
ميكنيم .
بنا براين Debug را اجرا كرده و سطرهاي زير را وارد ميكنيم .


17AA:0100 mov ax/100
17AA:0103 mov bx/4cb
17AA:0106 mul bx
17AA:0108 int 20
17AA:010A
- t


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

در اين سطر محتواي AX برابر 0100 است


AX=0100 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=17AA ES=17AA SS=17AA CS=17AA IP=0103 NV UP EI PL NZ NA PO NC
17AA:0103 BBCB04 MOV BX/04CB
- t


محتواي BX برابر 0$CB شده ...

AX=0100 BX=04CB CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=17AA ES=17AA SS=17AA CS=17AA IP=0106 NV UP EI PL NZ NA PO NC
17AA:0106 F7E3 MUL BX
- t


بعد از انجام ضرب ، محتواي DX برابر 4 و محتواي AX برابر CB00 شد ه . بنابراين
جفت ثبات DX:AX مقدار 4CB00 را نشان ميدهد . يعني همان عددي كه قبلا هم بدست
آورده بوديم .


AX=CB00 BX=04CB CX=0000 DX=0004 SP=FFEE BP=0000 SI=0000 DI=0000
DS=17AA ES=17AA SS=17AA CS=17AA IP=0108 OV UP EI PL NZ NA PO CY
17AA:0108 CD20 INT 20
- t
- q



تمرين :

همين عمل را براي تقسيم نيز با ديباگ انجام بدهيد .

در قسمت بعدي نوشتن رويه ها و برنامه هاي واحدي را
ياد ميگيريم .

پيروز باشيد

Nesta
31-03-2005, 20:48
رويه ها (Procedures) .

در برنامه نويسي با زبانهاي سطح بالا ، با زيربرنامه ها يا پروسيجر ها آشنا شديم .
مثلا در زبان بيسيك به آنها Subroutine ميگوئيم يا در زبان پاسكال Procedure و..

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

در زبان اسمبلي براي ساختن يك زيربرنامه از اعلان Proc در مقابل نام آن استفاده
ميكنيم . مثلا براي تعريف رويه Print آن را با Print Proc تعريف ميكنيم .
در واقع Proc يك برچسب ( از نوع Near) است كه اسمبلر توجه بيشتري نسبت به
يك برچسب عادي به آن ميكند.

وقتي يك اعلان زيرروال را تعريف كرديم ، كد اجرائي آن را مينويسيم . مثلا اگر
بخواهيم زير روال ما كاراكتر مشخص شده با AL را چاپ كند ميتوانيم با استفاده از
تابع 0Eh از وقفه 10h چنين چيزي بنويسيم :


Print Proc
Mov Ah/0Eh
Int 10h
:
:




وقتي به انتهاي كد مورد نظر رسيديم ، با يك دستور Ret به محلي از برنامه اصلي كه
زيرروال فراخواني شده بود برميگرديم .

Print Proc
Mov Ah/0Eh
Int 10h
Ret
:



و در پايان هم دوباره نام پروسيجر را به همراه يك كلمه End به معني پايان كار
زيرروال مي آوريم . در مورد مثال ما

: Print End Print Proc
Mov Ah/0Eh
Int 10h
Ret
Print End



نكته مهمي كه بايد به آن توجه كنيم اينست كه اعلان Proc مانند يك برچسب است و
در صورتي كه از روي آن با دستور JMP جهش نكنيم ، داخل پروسيجر هم اجرا خواهد شد
بنا براين هميشه بعد از Org 100h يا پروسيجر ها را نوشته و با يك فرمان JMP از
روي آنها به كداصلي پرش ميكنيم .


. MODEL SMALL
. CODE
ORG 100H
START :


JMP MAIN
پرش به | اعلان پروسيجر Print Proc
قسمت اصلي | Mov Ah/0Eh
برنامه | Int 10h Ret |

| پايان رويه Print End
:MAIN :
:
END START


پس از آن ميتوانيم با يك فرمان Call رويه مورد نظر را فراخواني كنيم .
مثلا Call print . قبل از فراخواني رويه مورد نظر بايد پارامترها و شراي اجراي آن
را فراهم كرده باشيم . مثلا در مورد رويه Print بايد هميشه ثبات AL كه حاوي كد
اسكي كاراكتر براي چاپ است ، مقدار دهي صحيح شده باشد .
مثال :

MAIN :
Mov Al/65 ;'A'
Call Print
:
END START



مثال : اين همان برنامه چاپ كاراكترهاي اسكي با كد 128 تا 255 است كه قبلا بدون
استفاده از رويه ها نوشتيم :

. MODEL SMALL
. CODE
ORG 100H ; IP points to 100h / starting offset
START
JMP MAIN ; Jump over the procedure
Print Proc ; procedure declaration
Mov Ah/0Eh
Int 10h
Ret ; procedure ends
Print End
MAIN :; main program
MOV BH/128
LOOP :_
MOV AL/BH ; AL = Ascii character code
CALL PRINT ; Call procedure
INC BH
CMP BH/255
JNP LOOP _; Jmp LOOP _if BH is below 255
INT 20H ; otherwise terminate normally
END START ; program ends according the START label

**

معمولا كد اصلي برنامه را هم بصورت يك Procedure مينويسند . مثلا در مثال بالا
بعد از برچسب Main ميتوان يك Prog proc و در انتهاي برنامه هم يك Prog End
قرار داد . اين امر تفاوتي در اجراي برنامه نخواهد داشت ولي معمولا و براي داشتن
انعطاف بيشتر در نوشتن برنامه اين كار را انجام ميدهند.
**
اگر دستور Ret را در برنامه اصلي بكار ببريم ، برنامه پايان مي يابد .

در قسمت بعدي با استفاده از رويه ها و اعمال بيتي و محاسباتي كه
خوانديم ، رويه هائي براي چاپ اعداد در مبناهاي مختلف مينويسيم .

Nesta
31-03-2005, 20:50
برنامه نويسي صفحه كليد

ميخواهيم با مطالبي كه تا حالا ياد گرفتيم ، به برنامه نويسي اجزاي PC بپردازيم
براي شروع هم ابتدا برنامه ريزي صفحه كليد را مطالعه ميكنيم .
براي استفاده از صفحه كليد معمولا از توابع داس (Int 21h) يا BIOS استفاده ميشود
با اينحال برنامه هاي حرفه اي و مخصوصا برنامه هاي رزيدنت از دسترسي مستقيم به
بافر صفحه كليد سود ميبرند . بنا براين بعد از يادگيري توابع داس و بايوس ،
روش دستيابي به بافر صفحه كليد را هم خواهيم خواند.

تابع 1 از وقفه / INT 21h دريافت كاراكتر همراه با نمايش
ورودي خروجي AH=1
=AL كد اسكلي كليد فشرده شده
كليد فشرده شده در صفحه هم به نمايش در مي آيد

اولين و اساسي ترين روش خواندن كليد ها همين تابع از بايوس است . اين تابع
كليدي را از صفحه خوانده و همزمان آن را در صفحه نير نشان ميدهد ضمن اينكه ^C
را هم تست ميكند. بعد از فراخواني وقفه كد اسكي (ASCII) كليد فشرده شده در AL
قرار ميگيرد.
بعلاوه 3 تابع ديگر ( مجموعا 4 تا ) نيز در DOS براي خواندن كاراكتر ها داريم كه
در جدول زير ميبينيد :

ECHO دارد | تست | CCtrl-C متظر ميماند | شماره تابع
1 | x | x | x
6 | | |
7 | x | |
8 | x | x |


براي فراخواني هر كدام از اين توابع ، عدد مربوط به تابع را در AH قرار داده و
وقفه 21h را اجرا ميكنيم . پس از آن كد اسكي كليد فشرده شده در AL قرار ميگيرد.
مثلا : MOV AH/01
INT 21h

اگر پس از خواندن كليد ، عدد 0در ALر قرار بگيرد ، يعني يكي از كليد هاي گسترش
يافته مثل SHitيا Altا و .. فشرده شده است . وقتي اين امر صورت بگيرد 2 بايت
كه محتوي يك 0 و كد اسكي كليد است در بافر صفحه كليد قرار ميگيرد . بنا براين
براي خارج كردن بايت دوم از بافر صفحه كليد ، يك بار ديگر همان تابع را اجرا
ميكنيم :

MOV AH/1 ; Read Key with echo / ^C sensitivity
INT 21H ; Call int 21h
CMP AL/0 ; Enhanced key pressed?
JNZ CONT ; No! jump to CONTinue
INT 21H ; otherwise read the 2nd byte
CONT :



مثال : ميخواهيم برنامه بنويسيم كه يك رشته كاراكتري را گرفته و چاپ كند.
در اين برنامه GETKEY يك پروسيجر است . اين رويه كاراكتري را با استفاهده از
سرويس 1 داس ميخواند و كد آن را در AL قرار ميدهد . وقتي كه كاراكتري را به اين
صورت خوانديم ، كد آن را به انتهاي رشته Str اضافه ميكنيم . سپس تست ميكنيم
كه آيا كليد فشرده شده برابر 13 يا همان Enter است ? اگر اينچني باشد ، از حلقه
خارج شده و با استفاده از سرويس 9 مربوط به Int 21h رشته دريافت شده را چاپ
ميكنيم .( قبل از آن يك كاراكتر $ با انتهاي رشته كاراكتري اضافه كرده ايم )


ENTER EQU 13
.MODEL SMALL
.CODE
ORG 100H
START:
JMP MAIN
GETKEY PROC ; getkey reads a key from keyboard
MOV AH/1
INT 21H
RET
GETKEY ENDP
PUTCHAR PROC ; displays a character
MOV AH/0EH
INT 10H
RET
PUTCHAR ENDP

STR DB 255 DUP(0) ; Buffer
MAIN:
LEA BX/STR ; 1st CHARACTER
LOOP:_
CALL GETKEY
MOV [BX]/AL
INC BX
CMP AL/ENTER ; if Enter pressed then jump over the loop
JNZ LOOP_

MOV AL/'$' ; END OF STRING
MOV [BX]/AL

LEA DX/STR ; get the Str address

MOV AL/10 ; CR
CALL PUTCHAR

MOV AL/13 ;LF
CALL PUTCHAR

MOV AH/9 ; Display Str

Nesta
31-03-2005, 20:50
برنامه نويسي صفحه كليد
دريافت رشته كاراكتري

نوشتن رويه اي كه با استفاده از توابع دريافت كاراكتر ، رشته اي كاراكتري را
دريافت كند مشكل است . مخصوصا اگر بخواهيم بوسيله كليد BackSpace اشتباهات را
هم جبران كنيم .

خود Dos تابعي براي دريافت رشته كاراكتري دارد: تابع شماره 0Ah از وقفه 21h

ورودي خروجي
AH=0Ah
كليد هاي تايپ شده در بافري كه با DS:DX Ds:Dx
طول بافر مشخص ميشود قرار ميگيرند.

كليد هاي تايپ شده در صفحه نمايش ديده ميشوند . و ميتوان با كليد Ctrl-C به عمل
ريافت رشته كاراكتري بطور غير نرمال پايان داد.

براي دريافت رشته كاراكتري بوسيله اين تابع ، ابتدا AH را برابر 0Ah قرار ميدهيم
بايتي كه DS:DX به آن اشاره ميكند ( اولين بايت بافر) برابر بيشترين كاراكتر
مجاز است . مثلا اگر بخواهيم طول رشته كاراكتري از 100 حرف تجاوز نكند بايد
اين بايت را با 100 پر كنيم . مانند اينكه در زبان پاسكال يك متغير را به شكل String[100]
تعريف كنيم .
بايت دوم بعد از اينكه عمل دريافت رشته را با Enter پايان داديم پر ميشود.
به اين صورت كه تعداد كاراكتر هاي تايپ شده در آن بايت قرار ميگيرند.
مثلا اگر كلمه تايپ شده ALI باشد ، بايتي كه در DS:DX+1 قرار دارد برابر 3
ميشود . هنگام دريافت رشته ، ميتوان با كليد BackSpace كاراكترهاي قبلي را پاك
كرد .

كاراكترهاي تايپ شده از اينجا در بافر قرار ميگيرند DS:DX | |

|1|2|3| .................................... | | | | |

| |
DS:DX
به اين بايت اشاره ميكند و(حداكثر طول باف ) |

تعداد كاراكترهائي كه كاربر تايپ كرده

مثال :
برنامه زير يك بافر 100 حرفي بنام BUF تعريف كرده و با استفاده از اين
تابع ، يك رشته كاراكتري را دريافت ميكند
تمرين :
به همين برنامه قسمتي اضافه كنيد كه رشته كاراكتري دريافت شده را چاپ
كند
راهنمائي :
بعد از دريافت رشته كاراكتري بايد به انتهاي آن يك كاراكتر $ اضافه
كنيد تا قابل چاپ توسط تابع 9 از وقفه 21h بشود . همچنين آفست شروع رشته كه در DS:DX
قرار دارد را به DS:DX+2 تبديل كنيد تا بايتهاي اول و دوم كه مربوط به طول
شته و تعداد كاراكتر ها هستند چاپ نشوند.

.MODEL SMALL
.CODE
ORG 100H
START:
JMP MAIN
BUF DB 101 DUP(0)/'$'

MAIN:
LEA DX/BUF
MOV BX/DX
MOV AL/100
MOV [BX]/AL
MOV A

Nesta
31-03-2005, 20:51
بافر صفحه كليد و بافرحلقوي

صفحه كليد از طريف وقفه سخت افزاري 9h كنترل ميشود . وقتي كه كليدي فشرده شده يا
رها شود اين وقفه فراخواني خواهد شد.
صفحه كليد هم مانند بسياري از وسايل جانبي مثل ديسك درايو و ... از طريق پورت
مخصوص به خودش با CPU مرتبط ميشود. وقتي كه كليدي فشرده ميشود پردازنده مخصوص
صفحه كليد كه يك تراشه 8048 است كدي موسوم به كد اسكن توليد كرده و در پورت 60h
قرار ميدهد. پس از آن وقفه 9h اين مقدار را از پورت 60h خوانده و در بافر صفحه
كليد قرار ميدهد . با فرصفحه كليد در ناحيه داده هاي بايوس يعني سگمنت شماره 40h
قرار دارد.

بافر حلقوي ----------

بافر صفحه كليد يك ناحيه 16 كلمه اي (32 بايتي ) در ناحيه داده هاي
كاراكتر بعدي از آنجا خوانده خواهد شد . كلمه ديگري بنام Tile هم به موقعيتي
اشاره ميكند كه كاراكتر بعدي كه تايپ ميشود در آن محل قرار ميگيرد.
وقتي يك كاراكتر تايپ ميشود head حركت ميكند و وقتي يك كاراكتر وارد ميشود Tile
يك خانه به جلو ميرود و وقتي هركدام به انتها رسيد به جاي اولش برميگردد. به
شكلي كه هميشه head در تعقيب tile است و وقتي بافر خالي باشد به آندو برهم منطبق
ميشوند . به خاطر اين شكل عملكرد بافر به آن بافرخلقوي ميگوئيم .

دستيابي به ناحيه داده ها و بافر صفحه كليد -----------------------------------------

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

Bios_Data SEGMENT
:
:
Bios_Data Ends


به اين ترتيب يك ناحيه داده اي جديد به اسم Bios_data تعريف كرديم .
براي اينكه اين ناحيه را بر سگمنت 40h يا همان ناحيه داده هاي بايوس منطبق كنيم
راهنماي AT را هم به انتهاي تعريف سگمنت اضافه ميكنيم . Bios_Data SEGMENT AT 40H
:
Bios_Data Ends

حالا در داخل سگمنت تعريفي چند متغير را در آدرسهاي مخصوص تعريف ميكنيم . چون
بايتهاي مربوط به صفحه كليد در آفست 1Ah قرار دارند يك org 1Ah در نظر گرفته و
متغيرها را تعريف ميكنيم .

Bios_Data SEGMENT AT 40H
ORG 100H
HEAD DW ?
TAIL DW ?
BUFFER DW 10 DUP(?)
Bios_Data Ends
org 1ah

به اسمبلر ميگويد كه همه متغيرهائي كه در زير تعريف ميشوند را منطبق
بر آدرس 1ah قرار بدهد . اين كار باعث ميشود كه هميشه متغير head حاوي دوبايت 1ah
و 1bhو باشد.
ون هميشه مقدار آدرسها نسبت به محتواي DS سنجيده ميشوند ( مثلا mov al/[bx] با mov al/ds:[bx]
برابر است ) بايد به اسمبلر بگوئيم كه داده ها در كجا هستند.
براي اين منظور از ASSUME استفاده ميكنيم . به اين صورت


Bios_Data SEGMENT AT 40H
ORG 100H
HEAD DW ?
BUFFER DW 10 DUP(?)
Bios_Data Ends
. CODE
ORG 100H
START:
ASSUME DS:BIOS_DATA


بعد آدرس ناحيه Bios_data را به DS لود ميكنيم . ( با استفاده از رجيستر واسطه ) MOV BX/BIOS_DATA
MOV DS/BX

حال تست ميكنيم كه اگر headو tail برهم منطبق باشند يعني بافر خالي است و
شروع به خواندن كليد ها ميكنيم اما اگر برهم منطبق نباشند محتواي بافر را خوانده
و آن را خالي ميكنيم .

GETCHAR:
MOV BX/HEAD
CMP BX/TAIL
JE GETCHAR



واگر بافر خالي نبود BX محل آخرين كاراكتر داخل بافر را نشان ميدهد . پس محتواي
آن قسمت را خوانده و سپس Tail را هم به همان آدرس تنظيم ميكنيم كه Tailو Headو
بر هم منطبق شوند . ( يعني بافر خالي بشود) MOV DX/[BX]

و ميتوانيم تست كنيم كه اگر كليد خوانده شده Q ( به معني Quit) بود از برنامه
خارج شويم و در غير اينصورت يك كليد ديگر را بخوانيم و در صفحه چاپ كنيم .

آيا كاراكتر Q است ? ; CMP DDL/'Q' JE BYE ;

آيا كاراكتر q است ? ; CMP DL/'q' JE BYE ;

تابع 2 براي خواندن كليد ; .MOV AH/2 ; READ A CHAR
وقفه داس ; INT 21H
پرش به روتين برنامه ; JMP GETCHAR BYE :INT 20H

Nesta
31-03-2005, 20:51
برنامه نويسي صفحه تصوير
ے بايوس كامپيوتر هاي PC شامل روالهائي براي كنترل و برنامه نويسي صفحه نمايش
هستند. متداولترين آنها وقفه 10h است و در زير متداولترين توابع آن را شرح ميدهيم

سرويس 2 براي تنظيم محل مكان نما
ورودي ها: AH=2

سطر و ستون موقعيت جديد DH/DL=
شماره صفحه BH=
ے شماره صفحه در حالت پيش فرض 0 است (BH=0) و گوشه بالا و سمت چپ صفحه نمايش
ے هم با مختصات (0/0) سنجيده ميشود . بنا براين درحالت 80x25 حداكثر شماره سطر و
ستون برابر 79x24 است .

سرويس 3 . دريافت موقعيت مكان نما
ورودي ها AH=3

شماره صفحه BH=
بعد از فراخواني وقفه 10h اين مقادير برگشت داده ميشوند:
سطر و ستون فعلي مكان نما DH/DL=

مثال

MOV AH/2 ; Set curson position .
MOV DH/20 ; X
MOV DL/10 ; Y
XOR BH/BH ; BH=0
INT 10H ; Call Interrupt 10h




لغزاندن صفحه به بالا يا پائين
ے با استفاده از دو تابع زير ميتوانيم بخشي از صفحه تصوير را به بالا يا پائين حركت
داده يا آن قسمت را پاك كنيم .
براي فراخواني اين توابع بايد رجيسترها را بصورت زير تنظيم كنيم
لغزش بطرف بالا AH=6
لغزش بطرف پائين AH=7
تعداد سطرهائي كه بايد حركت كند AL=
سطروستون گوشه سمت چپ و بالا Ch/Cl=
سطرو ستون گوشه سمت راست و پائين Dh/Dl=
مشخصه رنگ Bh=

ے اگر AH برابر 6 باشد محدوده تعريفي بطرف بالا و در صورتي كه 7 باشد بطرف پائين
حركت داده ميشود .
AL مشخص ميكند كه محدوده مورد نظر چند سطر بايد حركت كند .
ے اگر AL برابر 0 باشد محدوده تعريفي پاك ميشود . مثلا فرمان CLSدر DOSر از همين
وقفه استفاده كرده و كل صفحه نمايش را پاك ميكند.
ے مشخصه رنگي كه با BH تعريف ميشود معلوم ميكند كه فضاي خالي كه بعد از حركت دادن
محدوده بوجود مي آيد بايد با چه رنگي پر شود. اين مقدار از رابطه زير بدست
مي آيد:
رنگ زمينه +16*رنگ متن BH=
مثلا براي اينكه رنگ سفيد روي آبي داشته باشيم : BH=15+1*16=31

نوشتن كاراكتر و خصوصيات در محل مكان نما
ے با استفاده از تابع شماره 9h ميتوانيم كاراكتري را با خصوصيات مورد نظر در محل
مكان نما چاپ كنيم .
AH=9

كد اسكي AL=
شماره صفحه BH=
خصوصيات كاراكترBL=
تعداد دفعاتي كه كاراكتر چاپ ميشودCX=

ے خصوصيات كاراكتر از روشي كه در بالا توضيح داديم بدست مي آيد. رجيستر CX معلوم
ے ميكند كه كاراكتر مشخص شده با AL چند بار بايد چاپ شود. معمولا اين مقدار را 1
قرار ميدهيم .
مثال : چاپ رشته كاراكتري بصورت سفيد روي آبي

.MODEL SMALL
.CODE
ORG 100H
START:
JMP MAIN
MSG DB ' THIS IS AN EXAMPLE FOR INT 10H/FUNCTION 9H$'

SETCURSOR PROC ; SET CURSOR POSITION
MOV AH/2 ; FUNCTION 2 / INT 10H
MOV DH/10 ; ROW:10
PUSH BX ; SAVE BX VALUE
MOV BH/0
INT 10H
POP BX
RET
SETCURSOR ENDP

WRITE PROC ;WRITE A CHARACTER USING FUNCTION 9H/INT 10H
MOV AH/09
PUSH BX
XOR BH/BH
MOV BL/31
MOV CX/1
INT 10H
POP BX
RET
WRITE ENDP

MAIN:
LEA BX/MSG ; GET MSG ADDRESS
MOV DL/10 ; COLUMN : 10
CALL SETCURSOR
LOOP:_
MOV AL/[BX]
CMP AL/'$'
JZ QUIT
CALL WRITE
INC BX
INC DL ; SET NEW CURSOR LOCATION
CALL SETCURSOR
JMP LOOP_
QUIT:
INT 20H ; TERMINATE
END START





سرويس 0Eh براي چاپ بصورت تله تايپ

ے وقتي ميخواهيم با استفاده از سرويس 9h كاراكتري را چاپ كنيم خودمان بايد محل
ے مكان نما را معين كنيم . مثلا براي چاپ يك رشته كاراكتري بعد از چاپ هر كاراكتر
يكواحد به شماره ستون مكان نما اضافه كرده ، محل جديد آن را تنظيم مكنيم و... .
اما سرويس 0E بعد از چاپ هر كاراكتر مكان نما را يكخانه با راست انتقال ميدهد
به خاطر همين خصوصيت به اين روش چاپ " تله تايپ " ميگويند.
ے قبلا با اين سرويس كار كرده ايم بنا براين تنها به ياد آوري پارامترهاي لازم اكتفا
كرده و از آوردن مثال خودداري ميكنيم . ( مثال : فايل ALLCHAR.ASM)

بايد تنظيم شود: AH=0Eh

كد اسكي كاراكترAL=
شماره صفحه BH =

Nesta
31-03-2005, 20:52
برنامه نويسي در مد گرافيك

ے تنها امكاني كه بايوس براي انجام كارهاي گرافيكي در اختيار ما قرار ميدهد روشن
ے كردن يك نقطه (Pixel) است . با استفاده از تابع مربوطه بايد زيربرنامه هائي
براي رسم ساير اشكال گرافيكي مثل مربع ، دايره و ... بنويسيم .

ے قبل از اينكه يك شكل گرافيكي رسم كنيم ، بايد ايتدا صفحه نمايش را در مد
ے گرافيكي قرار بدهيم . به عبارت ديگر چون در حالت عادي تعداد سطر و ستونهاي صفحه
ے تصوير كم ( مثلا در مد 25x80 برابر 2000 تا) است ، نميتوان شكل گرافيكي رسم كرد .
بهمين دليل صفحه نمايش را در مد گرافيكي مثلا 480x640 نقطه قرار ميدهيم و ... .

سرويس 0 از وقفه 10h براي تنظيم حالت صفحه نمايش =================================================

ے براي تنظيم حالت صفحه نمايش به يك حالت متني يا گرافيكي خاص بايد اين
پارامتر ها را تنظيم كنيم . AH=0

مود مورد نظر AL=

مود مورد نظر از جدولي بدست مي آيد كه خلاصه آن را در زير مي آوريم .

كد | وضوح | تعداد رنگ
16 | Text Mode | 03h
16 | 320x200 | 0Dh
16 | 640x200 | 0Eh
Mono | 640x350 | 0Fh
16 | 640x350 | 10h
2 | 640x480 | 11h
16 | 640x480 | 12h
256 | 320x200 | 13h



سرويس 0Ch از وقفه 10h براي نوشتن نقطه گرافيكي =============================================

براي روشن كردن يك پيكسل از تابع 0Ch استفاده ميكنيم .
AH= 0Ch

شماره رديف بسته به مود تصويري DX=
شماره ستون بسته به مد تصويري CX=
عدد رنگ بسته به مد تصويري AL=
شماره صفحه ، شماره 0 معمولا استفاده ميشودBh=

ے به عنوان مثال براي روشن كردن نقطه اي كه ( مد 640x480 ) در وسط صفحه است به
اينصورت بايد وقفه را فراخواني كنيم .

MOV AH/0CH
MOV DX/240
MOV CX/320
XOR BH/BH



ے مثال : برنامه زير200 رنگ از رنگهاي موجود در حالت 320x200 نقطه و 256 رنگ را
نمايش ميدهد.

.MODEL SMALL
.CODE
ORG 100H
START:
MOV AH/00H
MOV AL/13H
MOV BX/00H ; PAGE NUMBER
INT 10H ; SET TO 320x200 256 COLORS

MOV AH/0CH ; PUTPIXEL FUNCTION
MOV AL/25 ; COLOR #25
MOV DX/0 ; ROW 0
ROW:
MOV CX/319 ; COLUMN 319 = START COLUMN
COL:
INT 10H ; CALL INTERRUPT 10H
LOOP COL ; DOWN TO CX=0
INC DX ; DX=DX+1
INC AL ; AL=AL+1( COLOR NUMBER)
CMP DX/199 ; IF DX=199
JNZ ROW ; ELSE JUMP TO ROW

MOV AH/00H ; GET KEY
INT 16H

MOV AH/00H ; VIDEO MODE SET
MOV AL/03H ; 80x25 16 COLORS
INT 10H ; CALL INT .10H

INT 20H ; TERMINATE PROGRAM
END START




سرويس 10h از اينتراپت 10h براي تنظيم پالت رنگ ================================================

ے با استفاده از اين سرويس ميتوانيم رنگهاي فعلي سيستم را بصورت دلخواه خودمان
ے تعريف كنيم . در هر كارت گرافيكي مبتني بر VGA ثباتي بنام رجيستر DAC وجود
دارد كه مشخصات رنگ را بصورت زير نگهداري ميكند.
6 bytes

DAC Register : RRRRRRGGGGGGBBBBBB

6 bytes 6 bytes


ے هر رنگ از سه رنگ اصلي قرمز (R) ، سبز (G) و آبي (B) تشكيل ميشود . ثبات DAC
ے كه بصورت يك متغير 18 بيتي است ، مقدار هر كدام از رنگهاي اصلي كه در رنگ
ے مورد نظر شركت دارند را معين ميكند . هر كدام از اين مقدار ها يك داده 6 بيتي
ے هستند ، بنا براين عددي بين 0 تا 63 را نشان ميدهند . براي تنظيم اين رجيستر از
سرويس 10 استفاده ميكنيم .
AH= 10H
AL= 10H

كد مربوط به رنگ (0-255) BX=
شدت رنگ سبز CH=
شدت رنگ آبي CL=
شدت رنگ قرمز CH=

ے منظور از شدت رنگ ، همان عدد 6 بيتي است كه در بالا گفتيم . مثلا اگر بخواهيم كه
ے رنگ شماره 1 كه در حالت عادي آبي است را به قرمز تند تبديل كنيم از اين كد
استفاده ميكنيم .


MOV AX/1010H
MOV BX/1
MOV CH/1
MOV CL/1
MOV CH/60
INT 10H




پايان قسمت بيست ودوم

Nesta
31-03-2005, 20:53
دسترسي مستقيم به حافظه ويدئو

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

ے كارت هاي گرافيكي مبتني بر CGA/EGA/VGA داراي قطعه حافظه ي مخصوصي هستند كه
ے اطلاعات صفحه نمايش در آن نگهداري ميشود . به عبارت ديگر هر چيزي در اين قسمت
از حافظه نوشته شود ، روي صفحه نمايش ائير صفحه نمايش ميگذارد .
ے در حالت گرافيكي اين قطعه از آدرس A000h و در مود متني از B000h يا B8000h شروع
ے ميشود . در حالت متني قطعه ويدئو در هر سگمنتي كه باشد ، هميشه بايتهاي زوج ( با
ے شروع از صفر) مربوط به كد اسكي كاراكتر بوده و بايتهاي فرد ( با شروع از يك )
ے مربوط به خصلت كاراكتر است . يعني خصلت كاراكتري كه در آفست n ام قرار دارد
در آفست n+1 ام قرار ميگيرد و ... .
خصلت مربوط به هر كاراكتر از فرمول زير محاسبه ميشود :
رنگ زمينه +16*رنگ متن = خصلت

بعنوان مثال اگر بخواهيم كه كاراكتر چاپ شونده با رنگ سفيد روي زمينه آبي ديده
شود داريم : = 31 َ= 15+1*16 خصلت .

( كد اسكي كاركتر) آفست 0
( بايت خصيصه ) آفست 1 | | |
| |
0 1 2 3 4 5 ......................................





| | | | | | | |


ے در كارتهاي گرافيكي تك رنگ (Monochrome) مانند CGA/MDA و ... قطعه حافظه ويدئو (
براي مود متني ) از آدرس B000h و در كارتهاي رنگي از آدرس B800h شروع ميشود .
ے مثال : برنامه زير تمام صفحه نمايش را با كاراكتر (.) و به رنگ سفيد روي زمينه
آبي پر ميكند.



مشخصه كاراكترها ; EQU 31 ATTR E
آدرس قطعه ويدئو ; VIDSEG EEQU 0B800H . MODEL SMALL
. CODE
ORG 100H
START :
JMP MAIN
DPRINT PROC
PUSH ES
MOV AX/VIDSEG
MOV ES/AX ;

كاراكتر براي چاپ MOV AL/'.' ; MOV ES:[BX]/AL ;

آفست قرار گيري بايت خصيصه INC BX ; MOV AL/ATTR
MOV ES:[BX]/AL
POP ES
RET
DPRINT ENDP

MAIN :

آفست شروع = صفر ; XOR BX/BX LOOP :_
CALL DPRINT

كاراكتر بعدي ; INC BX CMP BX/4000 ;( 80X25)x2
JNZ LOOP _
INT 20H
END START



ے در برنامه بالا محل آفست ها با BX مشخص ميشود . بنابراين ابتدا كد اسكي كاراكتر
را در آدرس ES:[BX] نوشته و سپس بايت مشخصه را در ES:[BX+1] قرار ميدهيم و..
ے چون صفحه نمايش داراي 80x25 عنصر بوده و هر كاراكتر 2 بايت ( كد كاراكتر و
ے بايت مشخصه ) اشغال ميكند بنا براين 80x25x2=4000 بايت طول دارد . بنا براين در
پايان برنامه تست ميكنيم كه آيا BX با آفست 4000 ام رسيده يا نه .

تمرين : برنامه اي بنويسيد و در آن پروسيجري طراحي كنيد كه يك رشته كاراكتري
را با اين روش بنويسد.

ے با همين روش ميتوانيم قسمتي از صفحه نمايش را ذخيره و بازيابي كنيم . برنامه
ے هائي كه در آنها از منوها و پنجره ها استفاده شده همگي از اين روشها استفاده
ے ميكنند . به اينصورت كه قبل از نمايش يك پنجره ( مثل يك Dialog box) ابتدا
محتواي پشت آن را ذخيره كرده و بعدا دوباره در همان محل قرار ميدهند.
برنامه زير ابتدا كل صفحه نمايش را در بافري ذخيره كرده و مجددا نمايش ميدهد

VIDSEG EQU 0B800h
.MODEL SMALL
.CODE
ORG 100H
START:
JMP MAIN
BUF DB 4000 DUP(0)
MSG DB ' NOW PRESS ANY KEY TO RESTORE SCREEN IMAGE$'
MAIN:
XOR BX/BX
PUSH ES
MOV AX/VIDSEG
MOV ES/AX
LOOP1:
MOV AL/ BYTE PTR ES:[BX]
MOV BUF[BX]/AL
INC BX
CMP BX/4000
JNZ LOOP1

; Clera screen.
MOV AL/0
MOV DH/24
MOV DL/79
XOR CX/CX
XOR BH/BH
MOV AH/7
INT 10H

; DISPLAY MESSAGE
MOV AH/9
LEA DX/MSG
INT 21H

; Wait for a key
MOV AH/0
INT 16H

XOR BX/BX
LOOP2:
MOV AL/BUF[BX]
MOV BYTE PTR ES:[BX]/AL
INC BX
CMP BX/4000
JNZ

Nesta
31-03-2005, 20:53
دستورات مربوط به رشته ها

قبلا تا حدودي نحوه كار با كاراكتر ها و رشته هاي كاراكتري را ديديم و ميتوانيم
آنها را از طريق ورودي دريافت كرده يا روي صفحه نمايش چاپ كنيم .
بعلاوه عمليات فوق ، امكانات و دستورات ديگري هم در نظر گرفته شده اند . مثلا
پردازنده 8086 دو رجيستر SI/DI را براي پردازش رشته هاي كاراكتري در نظر گرفته .
اين دو براي انتقال داده ها بين دو آدرس مختلف يا انتقال محتواي يك رشته
كاراكتري به رشته كاراكتري ديگر بكار ميروند.
هميشه DS:SI(SI( مخفف Source Index است ) به رشته مبدا و ES:DI(Destination Index)
به رشته مقصد (يا آدرس مقصد) اشاره ميكند. وقتي كه يك بايت (يا كلمه ) بوسيله
دستورات مخصوص اينكار انتقال پيدا كرد، خود بخود SI يكواحد افزايش (يا كاهش )
پيدا ميكند.
*
دستور MOVSB

دستور MOVSB يك بايت را از آدرس DS:SI به آدرس ES:DI منتقل ميكند.
براي انتقال يكبايت ، ابتدا ثباتهاي DS:SI را به رشته مبدا و ES:DI را به رشته
مقصد تنظيم كرده و با تشكيل يك حلقه (Loop) به تعداد مورد نياز داده انتقال
ميدهيم .
در مثال زير ميخواهيم تمام محتواي رشته كاراكتري S1 را به رشته كاراكتري S2
انتقال داده و S2 را چاپ كنيم .
بنا براين دو رشته كاراكتري (با طول 20 حرف ) تعريف ميكنيم .


S1 DB ' THE SOURCE STRING $'
S2 DB ' $'



سپس ds:di را به طوري تنظيم ميكنيم كه به S1 اشاره كند. چون برنامه ما com. است
و محتواي ds ثابت ، فقط مقدار آفست s1 را به SI انتقال ميدهيم : lea ax/s1
mov ds/ax

براي تنظيم كردن آدرس مقصد، مقدار DS كه حاوي عدد قطعه S2 است را به ES انتقال
ميدهيم : mov ax/ds
mov es/ax

سپس مقدار آفست S2 را بدست آورده و در DI قرار ميدهيم : lea ax/s2
mov di/ax

الان آدرسهاي مبدا و مقصد معلوم شده اند، بنا براين كافيست 20 بار (براي انتقال 20
بايت ) ، دستور movsb را اجرا كنيم : mov cx/20 ; loop counter
loop :_
movsb
loop loop _


سپس با استفاده از تابه 9h از وقفه 21h محتواي S2 را چاپ ميكنيم و خواهيم ديد
كه عبارت داخل S1 چاپ ميشود .



ليست كامل : .286c
.MODEL SMALL
.CODE
ORG 100H
START:
JMP MAIN
S1 DB 'THE SOURCE STRING $'
S2 DB ' $'
MAIN:
LEA AX/S1
MOV SI/AX

LEA AX/S2
MOV DI/AX
;PUSH ES
MOV AX/DS
MOV ES/AX

MOV CX/20
CLD
LOOP:_
MOVSB
LOOP LOOP_
MOV AH/09
LEA DX/S1
INT 21H
INT 20H
END START

*

دستور MOVSW

دستور MOVSW دقيقا مانند MOVSB است با اين تفاوت كه بجاي يك بايت ، يك كلمه
(َ2 بايت ) منتقل ميكند . براي انتقال دو بايت بين دو آدرس كافيست مانند
عملياتي كه براي movsb توضيح داديم ، آدرسها را تنظيم كرده و از movsw استفاده
كنيم .
بعنوان مثال ، اگر بخواهيم در برنامه بالا بجاي movsbاز movswز استفاده كنيم ،
بايد حلقه اي كه تشكيل داديم 10 بار اجرا شود:

mov cx/10
loop :_
movsw
loop loop _



دستورات CLSو SLDو

وقتي كه از movsbيا movswا استفاده ميكنيم ، مقدار ثباتهاي SIو DIو يكواحد
افزايش پيدا ميكنند . در صورتي كه بخواهيم با اتقال هر بايت (يا كلمه ) مقدار
اين ثباتها كاهش پيدا كنند، قبل از movsbيا movswا يك دستور SLD قرار ميدهيم .
مثلا:

SLD
MOV CX/10 ; LOOP COUNTER
LOOP :_
MOVSB
LOOP LOOP _


دستور CLD عكس دستور SLD عمل كرده و مقدار ثباتهاي DIو SIو با انتقال هربايت يا
كلمه ، يكواحد افزايش پيدا ميكنند. اين عمل بطور پيش فرض توسط movsbو movswو
انجام ميشود ، با اينحال براي جلوگيري از تاثر SLD هاي قبلي و اطمينان بيشتر
معمولا از آن استفاده ميشود.


CLD
MOV CX/10 ; LOOP COUNTER
LOOP :_
MOVSB
LOOP LOOP _

CLS

*
مخفف CLear Direction flagو SLDو مخفف Set Direction flag است .

دستور REP
براي راحتي كار و اينكه مجبور به ايجاد حلقه توسط دستور Loop نباشيم ، دستورالعمل REP
در 8086 تعبيه شده . اين دستور مانند دستور Loop هر بار يكواحد از محتواي CX
كم كرده و تا صفر شدن CX به تكرار عمليات ادامه ميدهد . در واقع REP يك
دستورالعمل نبوده بلكه پيشوندي براي دستورات انتقال ( مانند movsb) است .
بعنوان مثال براي انتقال 20 بايت از رشته S1 به رشته S2 ( مثال بالا) ميتوانيم
حلقه را به اين صورت بنويسيم :

CLD
MOV CX/20
REP MOVSB




در قسمت بعد ، بقيه دستورات مربوط به رشته ها را باد ميگيريم .

Nesta
31-03-2005, 20:54
دستورات مربوط به رشته ها

دستورات REPNE/REPE/CMPSW/CMPSB/CMPS

دستور CMPS براي مقايسه كردن دو رشته كاراكتري بكار ميرود. اين دستور دوبايت
واقع در ES:DIو DS:SIو را با هم مقايسه كرده و فلاگها را براساس نتيجه تنظيم
ميكند. سپس SIو DIو را افزايش (يا كاهش ) ميدهد. مثلا راي مقايسه كردن دو رشته
كاراكتري Str1وStr2و به اينصورت عمل ميكنيم : str1 db 'This is a set of chars$'
str2 db 'This is a set of chars$'

دقت كنيد كه در اين مثال دو رشته محتواي يكسان دارند . پس از آن آدرس اولين
رشته را در es:di و آدرس دومين رشته را در ds:si قرار ميدهيم .


lea si/str1
mov ax/ds
mov es/ax
lea di/str2



پس از آن چون ميخواهيم از بايت اول تا انتهاي رشته (22 بايت ) را مقايسه كنيم
ابتدا دستور CLS را براي افزاش si/di بكار برده ، و به شكل زير با تنظيم محتواي cx
به 22 ، 22 بايت دو رشته را مقايسه ميكنيم :


cld
mov cx/22
repe cmps str1/str2
jne mis_match
:
:
mis_mathc :
:



دستور Repe (مخفف Repeat while equal) تا زماني كه به اولين تفاوت برسد، بايتهاي
دو رشته كاراكتري را با هم مقايسه ميكند . وقتي به اولين مورد تفاوت برسيم فلاگ
وضعيت 1 شده و با دستور jne ميتوانيم به روال مورد نظر پرش كنيم .
تفاوت CMPSبا CMPSBاو CMPSWو در اينست كه CMPS به با توجه به نوع تعريفي رشته
ها عمل ميكند . يعني اگر رشته كاراكتري با تعريف DB ايجاد شده باشد، CMPS آندو
را بايت با بايت ، و اگر بصورت DW تعريف شده باشند، بصورت كلمه به كلمه
مقايسه ميكند .
اما CMPSB هميشه دو رشته كاراكتري را بايت به بايت مقايسه ميكند . و همينطور CMPSW
كه مقايسه را بصورت دوبايتي انجام ميدهد.
مزيت مهم cmpsbو cmpswوبر cmpsر اينست كه cmpsb/ccmpsw احتياج به اپراند ندارند
و تنها با اجراي cmpsbيا cmpswا بايتهاي لازم با هم مقايسه ميشوند .
مثال بالا را با استفاده از cmpsb مينويسيم :


:
cld
mov cx/22
RepNe cmpsb
je same
different :
:
:



ميبينيم كه در مقابل CMPSB هيچ اپراندي قرار ندارد . علاوه بر آن تغيير ديگري هم
ملاحظه ميكنيم : اينكه بجاي Repeاز Repneز (مخفف Repeat until not equal) استفاده
كرده ايم . RepNe تا وقتي كه به مورد مشابهي نرشد به مقايسه كردن رشته ها ادامه
ميدهد . وقتي به دو بايت يكسان برسد فلاگ وضعيت را طوري تغيير ميدهد كه بتوانيم
با JE به روتيني پرش كنيم كه ميخواهيم در صورت مساوي بودن رشته ها اجرا شود.
(در مثال بالا به same جهش كرده ايم ) .

دستورات SCAS/SCASB/SCASW

اين دستورات بايت واقع در AL با كلمه واقع در AX را با بايت با كلمات واقع در
رشته مقايسه ميكند . مانند CMPS، SCAS، به نوع تعريف رشته ها دقت ميكند و اگر
بصورت DB تعريف شده باشند ، بايت به بايت و اگر بصورت DW تعريف شده باشند
بصورت كلمه به كلمه جستجو ميكند . همچنين SCASBو SCASWو به اپراند احتياج ندارند
و فقط با تنظيم آدرسهاي DS:SIو ES:SIو ميتوانند بايت يا كلمه مورد نظر را جستجو
كنند.
مثال .
ميخواهيم برنامه اي بنويسيم كه محل اولين حرف 'a' را در رشته كاراكتري مورد نظر
پيدا كنيم . بنابراين ابتدا رشته كاراكتري را تعريف ميكنيم :
str db 'This is a sample text' ; 21 bytes .


سپس آدرس رشته را در DS:DI ، و 'a' را در نيم ثبات AL قرار ميدهيم .
mov al/'a'
lea di/str


پس از آن با عبارات زير عمل جستجو را آغاز ميكنيم . RepNe Scasb
JNE out
out
روتيني است كه در صورت يافته نشدن رشته كاراكتري بايد به آنجا جهش كنيم و
مثلا عبارتي مبني بر يافته نشدن رشته كاراكتري را چاپ كنيم . بدين ترتيب تمام
رشته كاراكتري را براي يافتن 'a' جستجو ميكنيم و درصورتي كه حداقل يك حرف 'a'
در رشته كاراكتري باشد ، با دستور JNE Out به روتين لازم جهش ميكنيم .

ليست كامل :


.286c
.MODEL SMALL
.CODE
ORG 100h
START:
JMP MAIN
STR1 DB 'This is a sample String' ; 25 Bytes
FOUND DB 'Match case found$'
NFOUND DB 'No Match case found$'
MAIN:
LEA DI/STR1
MOV CX/25
MOV AL/'a'
REPNE SCASB
JE OUT_
MOV AH/9
LEA DX/NFOUND
INT 21H
INT 20h
OUT:_
LEA DX/FOUND
MOV AH/9
INT 21H

INT 20H
END START

Nesta
31-03-2005, 20:55
استفاده از ماوس

ے كاركردن با ماوس ساده تر و سريع تر از كار كردن با صفحه كليد است . بهمين علت
داشتن امكانات بكارگيري ماوس در برنامه ها، يك امتياز مهم محسوب ميشود.
ے براي اينكه برنامه اي بتواند از ماوس استفاده كند، بايد درايور مخصوص ماوس كه
ے عبارت از يك برنامه COM. (مانند MOUSE.COM) است را اجرا كنيم . اين درايور ها
روتينهاي مخصوص ماوس را در اينتراپت 33h لود ميكنند.
ے براي استفاده از ماوس ، پس از بارگذاري درايور مخصوص ، بايد ان درايور را فال
كنيم ، اين كار را با استفاده از سرويس 00h از وقفه 33h انجام ميدهيم :

براي اجرا باز ميگرداند

AX=0 =AX كد خطا
ے اگر راه اندازي ماوس موفقيت آميز باشد ، پس از فراخواني AX=0 خواهد بود در غير
ے اينصورت ، مثلا در مواقعي كه درايور ماوس نصب نشده باشد، AX حاوي كد خطا خواهد
بود.
ے مثال : اين برنامه ماوس را در صورت وجود راه اندازي ميكند و در صورت وقوع خطا
به نقطه لازم پرش ميكند: XOR AX/AX ;
INT 33H ;

آيا كد خطا وجود دارد? ; CMP AX/0
بله ، پرش به روتين خطا ; JNZ ERROR

ے بعد از آن بايد مكان نماي ماوس را نمايش بدهيم . براي اينكار كافيست كه AX را
برابر 1 قرار داده و INT 33h را اجرا كنيم . MOV AX/1
INT 33h


ے در صورتيكه درايور ماوس نصب يا راه اندازي نشده باشد، نتايج غير قابل پيش بيني
رخ خواهد بدست خواهد آمد.
ے به همين سادگي هم ميتوان مكان نماي ماوس را مخفي كرد. براي مخفي كردن مكان نماي
ماوس AX را برابر 2 تنظيم كرده و INT 33h را اجرا ميكنيم . MOV AX/2
INT 33h

ے بايد توجه كنيم كه اين سرويس را در حالتي كه مكان نماي ماوس فعال نيست اجرا
ے نكنيم . در صورتي كه عمل مخفي كردن مكان نماي ماوس را 2 بار انجام دهيم ، براي
ے ظاهر كردن آن هم بايد 2 بار متوالي سرويس راه اندازي مكان نماي ماوس را اجرا
كنيم .

ے در هنگام كار با ماوس لازم داريم كه موقعيت مكان نماي آن و هم چنين وضعيت
كليدهائي از ماوس كه فشرده يا رها شده اند را بدانيم .
ے براي دريافت اطلاعات ماوس از سرويس 3 وقفه 33h ، با پارامتر هاي زير استفاده
ميشود :
براي فراخواني برگردانده ميشود

AX=3 BX=0 هيچ دكمه اي فشرده نشده
BX=2 دكمه چپ فشرده شده است
BX=2 دكمه راست فشرده شده است
BX=3 هردو دكمه فشرده شده اند
CX: ستون فعلي مكان نما
DX: سطر فعلي مكان نما

ے همانطور كه ميبينيد، بعد از فراخواني وقفه ، رجيستر BX مشخص ميكند كه كدام
ے كليد(يا كليدها) فشرده شده اند. هم چنين موقعيت سطر و ستون مكان نما بر حسب
Pixel
بدست مي آيد.
ے بعنوان مثال اگر از ماوس در محيط گرافيكي با وضوح 480 َ640 استفاده ميكنيد ،
ے مقادير CXو DXو به ترتيب 640 و 480 خواهند بود . براي حالت متني كافيست كه
ے مقادير بدست آمده با CXو DXو را به 8 تقسيم و با 1 جمع كنيم تا موقعيت مكان
نما بر حسب سطر و ستون متني (Text) بدست بيايد.
مثال :


.MODEL SMALL
.CODE
ORG 100H
START:
JMP MAIN
MSG DB ' Press the Left Mouse Key to Terminate'/13/10/'$'
MAIN:
XOR AX/AX
INT 33H
MOV AX/1
INT 33H
MOV AX/3
LOOP:_
INT 33H
CMP BX/1
JNZ LOOP_
MOV AX/

Nesta
31-03-2005, 20:55
بكارگيري Mouse

ے وقتي كه از سرويس 3h براي دريافت وضعيت ماوس استفاده ميكنيم ، تنها ميتوانيم
ے وضعيت ماوس در لحظه اجراي سرويس را بدست بياوريم . راه بهتر اينست كه از
ے سرويس 5h كه اعمال ماوس را در يك "صف " قرار ميدهد استفاده كنيم . در اين
ے حالت نيازي نيست كه اطلاعات فشرده شدن كليد را درست در همان لحظه دريافت كنيد
بلكه ميتوانيد آن را از "صف " برداريد.

سرويس 5h ، خواندن صف ماوس برميگردآند

AX=5 :BX تعداد دفعات فشرده شدن كليد ماوس
BX : 1: براي اطلاعات دكمه چپ :CX ستون فعلي مكان نما
0 براي اطلاعات دكمه راست :DX سطر فعلي مكان نما

ے اين سرويس به ما امكان ميدهد كه بدانيم دكمه مورد نظر بعد از آخرين دريافت
ے وضعيت چند مرتبه فشرده شده است . بهمين ترتيب ميتوانيم مختصات فعلي ماوس را
هم بدست بياوريم .
ے روش كار به اينصورت است كه ابتدا با قرار دادن 1يا 0ا در ثبات BX معلوم ميكنيم
ے كه اطلاعات كدام كليد را ميخواهيم دريافت كنيم . بعد از اجراي وقفه ، اطلاعات
ے كليد درخواستي از طريق ثبات BX و مختصات فعلي مكان نماي ماوس از طريق CX/DX
بدست مي آيد .

مثال :
ے اين مثال منتظر ميماند كه شما يكي از كليد هاي ماوس را فشار دهيد و در نهايت
به شما اعلام ميكند كه چند بار كليد سمت راست را فشار داده ايد.

.MODEL SMALL
.CODE
ORG 100H
START :JMP TOP
ANSWER DB " Number of times right botton was pressed"
TOP:
XOR AX/AX
INT 33H

MOV AX/1
INT 33H

MOV AH/7
INT 21H ; WAIT FOR A KEY

MOV BX/1
MOV AX/5
INT 33H

LEA DX/ANSWER
MOV AH/9
INT 21H

INT 20H
END START



ے سرويس 5h هنوز هم داراي محدوديتي است . به اين ترتيب كه موقعيت مكان نما در
ے زمان اجراي وقفه را برميگرداند. به همين خاطر مجبور خواهيد بود كه مرتبا با اجراي
ے اين سرويس ، اطلاعات ماوس را دريافت كنيد . براي حل اين مشكل از سرويس 6 كه
مكمل سرويس 5h است استفاده ميكنيم .

سرويس 6h از اينتراپت 33h

بايد تنظيم كنيم برميگرداند

AH=6h BX تعداد دفعات رها شدن دكمه ماوس
BX=1
اطلاعات دكمه چپ CX شماره ستون مكان نما در آخرين رها شدن
BX=0 اطلاعات دكمه راست DX شماره سطر مكان نما در آخرين رها شدن

ے مانند سرويس 5h بايد با قرار دادن 0يا 1ا در ثبات BX معلوم كنيم كه اطلاعات
ے كدام كليد را لازم داريم . اگرBX=1 باشد اطلاعات دكمه سمت چپ و در غير اينصورت
اطلاعات دكمه سمت راست بدست مي آيد.
ے بعد از فراخواني وقفه ، ثبات BX معلوم ميكند كه دكمه مورد نظر ، بعد از آخرين
ے دريافت وضعيت چند بار رها شده است . در مواقعي كه ميخواهيم چيزي را با ماوس
ے روي صفحه نمايش حركت بدهيم (مانند حركت دادن يك آيكون در ويندوز) رها شدن
دكمه هاي ماوس را هم بايد در نظر بگيريم .

حركت دادن مكان نماي ماوس


بعلاوه ميتوانيم مكان نماي ماوس را در موقعيت خاصي از صفحه نمايش قرار بدهيم .
كافيست كه از سرويس 4h به اين شكل استفاده كنيم . AX=4h

CX ستون جديد مكان نما
DX سطر جديد مكان نما

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

.MODEL SMALL
.CODE
ORG 100H
START:
XOR AX/AX
INT 33H

MOV AH/7
INT 21H

XOR CX/CX
XOR DX/DX
MOV AX/4
INT 33H

MOV AH/7
INT 21H

INT 20H
END START


مويد باشيد

Nesta
31-03-2005, 20:56
بكارگرفتن فايلها
ے تا قبل از DOS V.2 سيستم عامل داس براي كنترل فايلها از (Control Block
ے File)FCB استفاده ميكرد . اطلاعات هر فايل (شامل نام ، طول ، مسير و ... ) در
ے FCB قرار داشت و اين باعص ميشد نام فايل هميشه به 12 حرف محدود باشد. بنا
ے براين امكان ذكر مسير فايل به همراه اسم آن نبود . مثلا نميشد فايل SAMPLE.DAT
ے كه در C:\ قرار داشت را بصورت C:SSAMPLE.DAT نوشت . به همين دليل با انجام
ے تغيراتي در ساختار فايل گرداني DOS ، شناسه فايل (File Handle) ابداع شد و در
نگارشهاي 3/3 به بالاي DOS مورد استفاده قرار گرفت .
ے شناسه فايل يك كلمه (Word) 16 بيتي و معرف يك فايل در DOS است . هنگامي كه
ے ميخواهيد با فايلي كاركنيد ، نام آن را به DOS ميدهيد و DOS يك File Handle
به شما ميدهد . بعد از آن فقط با شناسه فايل كار داريم .

ے DOS بهمراه وقفه مخصوص خودش (Int 21h) ، سرويسهائي را بهمراه دارد كه براي
كاربا فايلها بكار ميرود .
ے اساسي ترين كار اينست كه بتوانيم يك فايل ايجاد كنيم . براي ايجاد فايل از
سرويس 3Ch به شكل زير استفاده ميكنيم .
AH=3CH

خصوصيات فايل CX=
اشاره به رشته كاراكتري محتوي نام فايل و مختوم به 0: DS:DX:

ے نام فايل بايد حتما به كاراكتر اسكي 0 مختوم باشد . CX مشخص كننده خصلت فايل
است و از جدول زير بدست مي آيد :
0
: فايل ساده 1
: فايل فقط خواندني 2
: فايل مخفي 4
: فايل سيستمي 8
: اسم ديسك اين خصلت را دارد 10h
: سابدايركتوري

ے مثال : در اين مثال يك فايل به اسم DUMMY.TXT ميسازيم . بعد از اجراي برنامه اگر
ے دستور DIR در سيستم عامل را اجرا كنيد ، فايلي به اسم DUMMY.TXT با طول صفر بايت
مشاهده ميكنيد.


.MODEL SMALL
.CODE
ORG 100H
START:
JMP MAIN
FILE DB 'DUMMY.TXT'/0
MAIN:
LEA DX/FILE
MOV AH/3CH
XOR CX/CX
INT 21H
INT 20H

END START




ے قدم بعدي نوشتن اطلاعات در فايل است . براي نوشتن در فايل ابتدا شناسه فايل را در
ے BX قرار ميدهيم . موقعيت محلي از حافظه كه داده ها از آن خوانده خواهند شد
ے با DS:DX و تعداد بايتهائي كه بايد منتقل شود با CX مشخص ميشود. در نهايت بايد
سرويس 40h را اجرا كنيم .
AH= 40H

اشاره به بافر DS:DX=
تعداد بايتها براي نوشتن CX=
شناسه فايل BX=

ے هر وقت يك فايل را ايجاد يا باز ميكنيم ، بايد آن را ببنديم مقداري از داده هاي
ے آن كه در حافظه مانده اند در فايل نوشته شده و حافظه اختصاص يافته به آن هم آزاد
ے شود . براي بستن يك فايل شناسه فايل را در BX قرار داده و وقفه 21h را با AH=3Eh
اجرا ميكنيم .

مثال :
ے قبلا در برنامه نويسي سيستم تصوير ديديم كه صفحه نمايش VGA در حالت 80x25
ے شامل 4000 بايت (80x25x2) براي كاراكتر و مشخصه است كه از آدرس 0000:َB800 شروع
ے ميشود . در اين مثال برنامه اي مينويسيم كه كل صفحه نمايش را از اين محل خوانده
و در فايل DUMMY.TXT ذخيره كند.


.MODEL SMALL
.CODE
ORG 100H
START:
JMP MAIN
BUF DB 4000 DUP(0)
FILE DB 'DUMMY.TXT'/0
HANDLE DW 0
MAIN:
LEA DX/FILE
MOV AH/3CH
XOR CX/CX
INT 21H
MOV HANDLE/AX

PUSH ES
MOV AX/DS
MOV ES/AX
LEA DI/BUF
PUSH DS
MOV AX/0B800H
MOV DS/AX
XOR SI/SI
MOV CX/4000
REP MOVSB
POP DS
POP ES

MOV AH/40H
MOV BX/HANDLE
MOV CX/4000
LEA DX/BUF
INT 21H

MOV AH/3EH
INT 21H

INT 20H
END START



ے ما همچنين ميتوانيم داده هاي يك فايل را بخوانيم و در محل خاصي از حافظه (معروف
ے به Buffer) قرار دهيم . براي خواندن از فايل سرويس 3Fh را با اين پارامترها بكار
ميبريم .
AH=3FH

شناسه فايل BX=
تعداد بايتها براي خواندن CX=
آدرس بافر براي انتقال داده هاDS:DX=

ے مثال : حالا برنامه اي مينويسيم كه فايل DUMMY.TXT ايجاد شده در مثال قبل را
ے خوانده و داده هاي آن را به بافر صفحه تصوير منتقل كند . به اين ترتيب محتواي
ے صفحه نمايش كه در هنگام اجراي SAVEFILE.COM ( برنامه مثال قبل ) ذخيره شده بود ،
مجددا نمايش داده ميشود.


.MODEL SMALL
.CODE
ORG 100H
START:
JMP MAIN
BUF DB 4000 DUP(0)
FILE DB 'DUMMY.TXT'/0
HANDLE DW 0
MAIN:
LEA DX/FILE
MOV AH/3DH
MOV AL/0 ; READ-ONLY
INT 21H
MOV BX/AX

MOV AH/3FH
MOV CX/4000
LEA DX/BUF
INT 21H

LEA SI/BUF
PUSH ES
MOV AX/0B800H
MOV ES/AX
XOR DI/DI
MOV CX/4000
REP MOVSB
POP ES

MOV AH/3EH
INT 21H

INT 20H
END START

Nesta
31-03-2005, 20:57
دسترسي به پورتهاي كامپيوتر

تا اينجا برنامه نويسي خيلي از اجزاي كامپيوتر مثل صفحه كليد ، صفحه نمايش ،
ديسك و .. را ديديم . براي برنامه ريزي اينها از وقفه هاي BIOSيا DOSا استفاده
كرديم . وقفه هاي ياد شده همه زير برنامه هائي هستند كه به محض اجرا ، زير
برنامه هاي ديگري كه در بخشي از ROM كامپيوتر قرار دارند را به اجرا در مي
آورند . اين بخشها همگي داراي آدرسهائي هستند كه خارج از حوزه آدرس دهي CPU
قرار دارند . يعني نميتوان با الگوي Segment:Offset به آنها دسترسي پيدا كرد . در
عوض داراي شماره خاصي هستند كه به شماره Port معروفست . از طريق اين پورتها
ميتوانيم مستقيما و بدون واسطه به راهبري اجزاي كامپيوتر بپردازيم . مثلا ميتوانيم
با استفاده از پورت شماره 60h ، كار صفحه كليد را كنترل كنيم يا اينكه پورتهاي
...37Ah/379h/ به كنترل پورت موازي و چاپگر بپر دازيم . همينكار را وقفه 17h
(دسترسي به چاپگر) هم انجام ميدهد اما اين وقفه فقط براي كار با چاپگر طراحي شده
و نميتوانيم با استفاده از آن كار ديگري انجام بدهيم .

دستورات مربوط به پورتها

به هر پورتي ميتوان يك عدد تك بايتي فرستاد يا از آن خواند. براي نوشتن
در يك به روش زير عمل ميكنيم :
1- اگر عدد پورت كوچكتر از 255 باشد ، ميتوانيم با استفاده از دستور OUT و
بطور مستقيم عدد را به پورت بفرستيم . يعني بصورت OUT Portnumber/Value

مثال : OUT 80h/60
2
- اگر عدد مربوط به پورت از 255 ( يك بايت ) بيشتر باشد ، بايد ابتدا آن
را در ثبات DX قراردهيم ، سپس با استفاده از OUT ، مقدار را به پورت
بنويسيم .
مثلا براي نوشتن در پورت 3F8h ابتدا آن را در DX قرار ميدهيم و بعد عدد
را به پورت مشخص شده با DX انتقال ميدهيم . mov dx/3F8h
out dx/0FFh


در اين حالت يك بايت قابل انتقال است . براي ارسال يك عدد دوبايتي بايد
به سراغ نيم ثبات AL برويم . به اينصورت كه ابتدا 8 بايت سمت پائين
(سمت راست ) را در AL قرار داده و به پورت ميفرستيم . بعد بلافاصله 8 بايت
بالا را به همين ترتيب در پورت قرار ميدهيم . مثلا فر ض كنيد ميخواهيم عدد 0AFDh
را به پورت 61h بفرستيم :


mov ax/0AFDh
out 61h/al
shr ax/8
out 61h/al



براي خواندن پورت هم اگر شماره پورت از 255 بزرگتر باشد ، آن را در DX قرار
ميدهيم . وقتي ميخواهيم پورتي را بخوانيم ، بايد حتما اين كار را با كمك نيم
ثبات AL ، و با استفاده از دستور IN انجام دهيم . يعني بصورت :
IN AL/PortNo .


مثلا براي خواندن پورت 71h ، عبارت IN AL/71h را بكار ميبريم .

مثال :
عددي كه در پورت 61h قرار دارد براي بكار انداختن بلند گوي كامپيوتر بكار ميرود
( و برعكس براي از كار انداختن ) . اگر 3 بيت انتهائي آن 1 باشند ، بلندگوي
كامپيوتر ( با مشخصاتي كه در پورت هاي 41h و 43h قرار ميدهيم ) شروع به توليد
صوت ميكند . و اگر آن بيتها را بازهم 0 كنيم ، بلندگوي كامپيوتر از كار باز مي
ايستد . در اين برنامه بلند گوي كامپيوتر به كار مي افتد و بعد از فشردن يك
كليد مجددا" خاموش ميشود.


.MODEL SMALL
.CODE
ORG 100H
START:
IN AL/61H
OR AL/00000111B
OUT 61H/AL

XOR AH/AH
INT 16H

IN AL/61H
AND AL/11111000B
OUT 61H/AL

INT 20H
END START

Nesta
31-03-2005, 20:57
حتماٌ زماني كه درس زبان ماشين را پاس ميكرديد ،در مقايسه با زبانهايي چون C++ , Object Pascal(Delphi) آنرا فوسيل ? ميناميديد و هرگز فكرنميكرديدكه MASM بتواند فايلهاي .EXE, .DLL, .sys تحت ويندوز را بسازد.
اما حقيقت اين است كه اكنون اسمبلرهاي فراواني وجود دارند كه به شما امكان ايجاد فايلهاي اجرايي با فرمت PE را ميدهند.اگر نميدانيد PE(Portable Executable) چيست بهتر است به مقالات موجود در MSDN مراجعه كنيد.
اسمبلرهاي زير جزء معروف ترينها هستند:

• MASM(Macro Assembler) from Microsoft
• TASM(Turbo Assembler)from Borland
• NASM(Netwide Assembler)
• FASM


(در اين مقاله صرفاٌ از MASM استفاده خواهم كرد.)
اين مقاله براي چه كساني مناسب است؟
براي آنكه بتوانيد از مطالب اين مقاله استفاده كنيد (به نظر من) بايد با مطالب زير آشنايي داشته باشيد:
• زبان Assembly
• WIN32/64 API يا شايد بتوان گفت: Visual C++ بدون MFC ويا Delphi بدون VCL

براي شروع بهتره كلماتي مثل MASM و Download را در GOOGLE جستجو كنيد و MASM را Download كنيد.
توجه:به شما پيشنهاد ميكنم براي هماهنگي با من Package آقاي HUTCH را از آدرس زير Download كنيد:
[ برای مشاهده لینک ، لطفا با نام کاربری خود وارد شوید یا ثبت نام کنید ] (ورژن Package من 8 است اما ورژنهاي پايين تر هم مناسب خواهند بود.) در ضمن به MSDN هم احتياج پيدا خوايد كرد.(البته اگر نداشته باشيدش هم No Problem ?)
اگر دلفي يا BCB را نصب كرده ايد ميتواند از فايل win32sdk.hlp در پوشه MSHelp بجاي MSDN استفاده كنيد.(هرچند مال زمان پادشاه وزوزه?)
خوب حالا كه MASM را Download كرديد بايد اقدام به نصب آن كنيد.
آقاي HUTCH فرد دانايي است و براي كاهش حجم Package زيركي خاصي را بكار برده اما در عوض زمان نصب بسيار افزايش پيداكرده پس بهتر است در زمان نصب صبور باشيد.

شروع!!
تمامي برنامه ها تحت ويندرز در حالت Protected Mode اجرا ميشوند و اين يعني هربرنامه 4GB Address Space, خواهد داشت(4 گيگا بايت يعني زندگي!!)البته توجه كنيد كه گفتم Address Space نه Memory Space .با اينحال بدليل طراحي عالي NT تقريباً هيچ وقت با كمبود حافظه مواجه نخواهيد شد.
همه اينا رو به اين جهت گفتم كه شما را براي يك خبر خوشحال كننده آماده كنم:
? عمر Segment هاي اعصاب خورد كن بسر آمده.(يوهو!!)
فكر كنم همين اندازه تئوري كافي باشه ، بهتره دست بكار بشيم(يا بشم).كد زير را كپي كرده و در فايلي بنام test.asm پيست!! كنيد.


.386
.model flat, stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib

.code
start:
invoke ExitProcess,0
end start


حالا ببينيم كد فوق چگونه عمل ميكند:
.386
اين دستور به اسمبلر ميگويد كه ما قصد استفاده از دستورات پردازندههاي 386 را داريم.
سوال:بابا مگه 386 تاريخ نشده؟
جواب:معماري 386 استاندارد مورد استفاده تمامي پردازندههاي 32بيتي بالاتر حتي p4 است.

.model flat, stdcall
با .model كه از روزگار گذشته آشنايي داريد.
Flat براي تمامي برنامه هاي تحت ويندوز استفاده ميشود و معني اش همان 4GB است.
Stdcall يا Standard Call ، Calling Convention مورد استفاده ما است.
Stdcall يعني آرگومان ها را از راست به چپ داخل stack گذاشته و در ضمن تابع فراخوانده شده مسئول خالي كرد (pop) stac است. (فرقش هم با روش cdecl در همين مورد آخره)
اگه قات زديد, بهتره Stdcall رو فعلاً بيخيال شيد ، بعداً ميفهميد چيه.!!

option casemap:none
حتماً ميدانيد كه MASM مانند Delphi(object Pascal) و بر خلاف C/C++ ،case sensitive نيست اما شما براي استفاده از MASM تحت ويندوز مجبوريد آنرا case sensitive كنيد .دستور فوق دقيقاً اين كار را انجام ميدهد.بشما توصيه ميكنم حتماً از اين دستور در ابتداي هر برنامه خود استفاده كنيد.


include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib


دوتا خط اول كه واضح است اما يك توضيح كوچك در مورد فايلهاي .inc :
فايلهاي include موجود در package آقاي HUTCH به اين شكلند كه :
windows.inc :شامل تمامي ثوابت مانند WM_PAINT ميباشد.
DllName.inc : شامل prototype توابع DllName ميباشد.بطور مثال kernel32.inc شامل prototype توابع export شده از kernel32.dll ميباشد.
Includelib هم براي link كردن با فايلهاي .lib استفاده ميشود.(اگر نميدانيد فايلهاي .lib چه هستند به من ربط نداره!!همه چي رو كه من نبايد توضيح بدم?)

.code
نشان دهنده نقطه شروعSection (ونه Segment) كد است.

invoke ExitProcess,0

invoke دستور جديدي است كه به MASM (ونه CPU) اضافه شده و به شما امكان فرا خواني توابع بدون Push كردن دستي آرگومانهاي تابع را ميدهد.مثلاً كد فوق برابر است با:
Push 0
Call ExitProcess

end start
اين دستور 2 كار انجام ميدهد:
• پايان Section كد را مشخص ميكند.
• Entry Point برنامه را تعيين ميكند.

حال براي Assemble كردن اين كد ابتدا يك پنجره كنسول باز كنيدوابتدا Assembler و سپس Linker را اجرا كنيد:


ml /c /coff /Cp test.asm

ml.exe نام Assembler است.
/c به Assembler ميگويد كه فقط فايل .obj ايجاد كند (نه .exe)
/coff فايل .obj با استاندارد coff (استاندارد unix) ميسازد.
/Cp :معادل همان option casemap:none است.

پس از اينكه همه كار بخوبي پيش رفت بايد فايلي بنام test.obj براي شما ايجاد شده باشد.
اكنون بايد اين فايل را Link كنيم(كه البته بهتره آنرا Dynamic Linking بناميم)


Link /SUBSYSTEM:WINDOWS test.obj


/SUBSYSTEMمهمترين سويچ Linker است و به آن ميگويد كه شما قصد داريد چه جور exe اي را ايجاد كنيد.كلاً دو حالت مهم دارد:
/SUBSYSTEM:WINDOWS

/SUBSYSTEM:CONSOLE

(در VC++ اين سويچ Entry point برنامه را از ميان main و WinMain انتخاب ميكند)
بعد از اينكه Linker را اجرا كرديد فايل test.exe براي شما ايجاد ميشود وهمانطور كه از سورس برنامه معلوم است ،با اجراي اين برنامه اتفاق خاصي نخواهد افتاد.
سلام دنيا!!
اكنون بهتر است يك برنامه بنويسيم كه پيام Hello World را نمايش دهد.

.386
.model flat,stdcall
include windows.inc
include kernel32.inc
include user32.inc
includelib kernel32.lib
includelib user32.lib

.data
Mes BYTE "Hello Wolrd",0.

.code
WinMain:
invoke MessageBox,NULL,offset Mes,NULL,0
invoke ExitProcess,0
end WinMain


كد فوق ساده است واحتياجي به توضيح ندارد.اين فايل را همانطوري كه قبلاً توضيح دادم assemble و link كنيد.پس از پايان كار يه نگاهي به حجم فايل exe ايجاد شده بيندازيد...حتماً تعجب خواهيد كرد!!!

پنجره اي رو به آفتاب!!
شايد وقتش شده باشه كه با هم يك پنجره بسازيم!!!
در مورد كد زير فكر ميكنم توضيحات درون خود كد كافي باشد اما متذكر ميشوم كه اگر نحوه ساختن يك پنجره بوسيله Win32 API را نميدانيد ممكن است كد زير كمي براي شما پيچيده باشد (براي اطلاعات بيشتر به كتاب Programming Windows نوشته Charles Petzoldمراجعه كنيد.)

.386
.model flat, stdcall
option casemap:none
include \masm32\include\WINDOWS.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib

WinMain proto :HWND,:DWORD,:DWORD,:DWORD

.DATA ; initialized data
ClassName db "SimpleWinClass",0 ; the name of our window class
AppName db "Our First Window",0 ; the name of our window



;-------------------------------------------------------------------
.DATA? ; Uninitialized data
hInstance HINSTANCE ? ; Instance handle of our program
CommandLine LPSTR ?
;-------------------------------------------------------------------
.CODE ; Here begins the code

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR, CmdShow:DWORD
; creating local variables on stack
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND

mov wc.cbSize,SIZEOF WNDCLASSEX ; fill values in members of wc
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInstance
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax

invoke RegisterClassEx, addr wc ; register our window class
invoke CreateWindowEx,NULL,\
ADDR ClassName,\
ADDR AppName,\
WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
NULL,\
NULL,\
hInst,\
NULL
mov hwnd,eax
invoke ShowWindow, hwnd,CmdShow ; display our window on desktop
invoke UpdateWindow, hwnd ; refresh the client area

; Enter message loop
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam ; return exit code in eax
ret
WinMain endp

;----------------------------------------------------------------------
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM

.IF uMsg==WM_DESTROY ;if the user closes our window
invoke PostQuitMessage,NULL ; quit
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam ;Default message processing
ret
.ENDIF
xor eax,eax
ret
WndProc endp
;-----------------------------------------------------------------------

start:
invoke GetModuleHandle, NULL ;get the instance handle of our program.
mov hInstance,eax
invoke GetCommandLine ; get the command line. You don't have to call this
; function IF your program doesn't process the command line.
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine,SW_SHOWDEFAULT ;call the main ;function
invoke ExitProcess, eax ;quit our program. The exit code is returned in eax ;from WinMain.
end start

نتيجه برنامه را در شكل زير مشاهده ميكنيد:


آيا فكر ميكنيد اين برنامه هيچكاري جز نمايش يك پنجره خالي انجام نميدهد؟

نكته: در كد فوق از عملگرد جديد MASM با نام ADDR استفاده شده است .اين عملگرآدرس effective يك متغييررا محاسبه ميكند ولي مشكل آن اينست كه تنها به همراه invoke قابل استفاده است ،پس در حالات ديگرنميتوانيد ADDR را بكار بريدو بايد از همان دستور LEA استفاده كنيد.
نظرات و پيشنهادات و سوالات خود را به ادرس titanwalker@mail.com ارسال كنيد.

Real Programmers code in asm.

Nesta
31-03-2005, 21:00
سلام
دوستان اين آموزش اسمبلی هست که حدودن 80 صفحه پی دی اف ميشه مثله يه کتاب هست خيلی کامل و خلاصه چون اکثر کتابهيه اسمبلی 300 صفحه هست و هر کی ميخواد ياد بگيره ميترسه نگش کنه ولی اينطوری با دستورات آشنا ميشين که دستورات به درد بخور و کارا هست از آقاينی که ميخوان آموزش بگزرن توصيه ميکنم که هم جور آموزش نتری بگزرن نه فقط html و چيژهعی که ديگه رفته رفته همه بلدن
:wink:

elahe_64
21-12-2005, 18:39
ممكن هست دستور اسمبلي چراغ راهنما را بنويسيد

seyyed.ali
26-01-2006, 19:49
حال دادي
خوب بابا تو كه اينقدر باحالي به صورت pdf و upload میکردی مرامتو شکر
با مرام
مرسی از اموزشت ( از طرف همه ی اونایی که دیدن)

seyyed.ali
28-01-2006, 23:23
من یه کاری کردم و صد البته از Nesta معذرت می خواهم و امیدوارم که ناراحت نشه
اونم به خاطر اینکه برای save کردن این صفحات خیلی اذیت شدم
حالا چیکار کردم
تمام این نوشته ها رو بی کم و زیاد به صورت pdf ی که rar شده upload کردم
اینم لینکش
[ برای مشاهده لینک ، لطفا با نام کاربری خود وارد شوید یا ثبت نام کنید ]
بازم از Nesta معذرت می خواهم و هدفم فقط کمک بود
have fun

MARTIN
02-02-2006, 01:53
دمت گرم سید . دست نستا جان هم خاطر اموزش درد نکنه

Hazy
22-03-2006, 22:54
دستتان درد نكند

Arashpc
23-03-2006, 15:18
با سلام خدمت نستا جان بايد بگم كه دستت درد نكنه عالي بود :biggrin: