-
براي بدست آوردن مقدار يك بايت مشخص از حافظه ما بايد عد مربوط به سگمنت و
همچنين شماره آن بايت در سگمنت ( كه آفست 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
-
در اين قسمت ميخواهيم با استفاده از مطالبي كه در بخشهاي قبلي ياد گرفتيم
برنامه اي بنويسيم كه كامل و قابل استفاده باشد . با اين برنامه ميتوانيم
فلاپي ديسكهاي خودمان را با سرعت كپي كنيم ! امروز برنامه را به شكلي مينويسيم كه
بتواند ديسكهاي 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
-
خوب ، رجيسترها را ديديم و آشنائي كلي با آنها پيدا كرديم .
حالا ميخواهيم به رجيتسرها مقدار بدهيم و آنها را بخوانيم و ... . ما معمولا در
ےزبانهاي ديگر از علامت =(يا =ا براي مقدار دهي استفاده ميكنيم ولي در زبان
ےاسمبلي اين كار ممكن نيست . در عوض از دستورالعمل 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 را وارد كنيد .
-
تا اينجا ياد گرفتيم كه چطور مقادير را بين ثباتها منتقل كنيم : با فرمان 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 استفاده خواهيم
كرد .
دستيابي به مكانهاي حافظه نكته هاي جالب ديگري هم دارد كه در قسمت بعدي ياد
خواهيم گرفت .
-
وقتي كه ما به روش گفته شده مقداري را از حافظه ميخوانيم ، يك داده تك بايتي
از حافظه گرفته ميشود . اما ممكن است بخواهيم كه يك كلمه يا كلمه
مضاعف ( 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
-
وقفه ها (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 توليد ميكند .
-
در اين قسمت طرز استفاده از ماكرواسمبلر را ياد ميگيريم و برنامه هايمان را بدون
استفاده از 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
را اجرا كرده و نتيجه را مشاهده كنيد %
-
-
برنامه اي
براي تعريف رنگهاي جديد!
اگر داراي كارت ويدئوي 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 را ياد گرفتيم و
ديديم كه نوشتن يك برنامه اسمبلي برخلاف آنچه تا بحال تصور ميكرديم چقدر ساده
و جالب است .
-
دستورالعمل 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و
استفاده ميكنيم .
برچسب برای این موضوع
مجوز های ارسال و ویرایش
- شما نمی توانید موضوع جدید ارسال کنید
- شما نمی توانید به پست ها پاسخ دهید
- شما strong>نمی توانید فایل پیوست ضمیمه کنید
- شما نمی توانید پست های خود را ویرایش کنید
-
قوانین انجمن