برای یاد گرفتن اسمبلی باید با مبناهای عدد نویسی ، ساختمان داخلی کامپیوتر
و برنامه نویسی آشنا باشیم .
ما برنامه هایمان را مستقیما با اسمبلر 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
بیایید یک برنامه بنویسیم
در این قسمت میخواهیم با استفاده از مطالبی که در بخشهای قبلی یاد گرفتیم
برنامه ای بنویسیم که کامل و قابل استفاده باشد .
با این برنامه میتوانیم
فلاپی دیسکهای خودمان را با سرعت کپی کنیم !
امروز برنامه را به شکلی مینویسیم که
بتواند دیسکهای 1.44 را بوسیله درایو A کپی کند .
بیشتر نیاز ما در کپی (تکثیر)
دیسکها هم به همین شکل هست .
با اینحال در قسمت بعدی نگارش (Version) جدیدتری از
برنامه را مینویسیم و قابلیت تشخیص نوع دیسک و قابلیت مشخص کردن درایو را به آن
اضافه میکنیم .
بهترین کاری که میتوانیم بکنیم اینست که بتوانیم داده های خوانده شده از
دیسک را در حافظه EMS بنویسیم (در این نسخه روی هارددیسک مینویسیم ) .
وقتی که
نحوه کار را حافظه گسترش یافته (Extended Memory) را هم یاد گرفتیم ، برنامه
خود را کامل کرده و از آن بعنوان اولین دستختمان در برنامه نویسی اسمبلی لذت
میبریم .
لیست برنامه در زیر قرار دارد و توضیحات برنامه را روی آن میبینیم
قبل از آن یاد آوری میکنم که هر دیسک HD َ1.44 دارای دو طرف و در هر طرف 80 شیار
(Track) بوده و هر شیار هم به 18 بخش بنام قطاع (Sector) تقسیم میشود .
برنامه
ما باید محتوای تمام این قطاعها را خوانده و در فایلی روی دیسک سخت ذخیره کند.
سپس همین داده ها را از فایل خوانده و مجددا روی دیسک جدید بنویسد.
در این قسمت میخواهیم با استفاده از مطالبی که در بخشهای قبلی یاد گرفتیم برنامه ای بنویسیم که کامل و قابل استفاده باشد .
با این برنامه میتوانیم فلاپی دیسکهای خودمان را با سرعت کپی کنیم !
امروز برنامه را به شکلی مینویسیم که بتواند دیسکهای 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 را وارد کنید .
****** پس امروز یاد گرفتیم گه چطور مقادیر و داده ها را بین ثباتها منتقل کنیم .
خودتان همین تمرینات را با DEBUG انجام داده و در مورد MOV مطالعه کنید .
در قسمت بعد چیزهای بیشتری رو خواهیم خواند و یاد خواهیم گرفت.
تا اینجا یاد گرفتیم که چطور مقادیر را بین ثباتها منتقل کنیم : با فرمان 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.
است .
پس کد برنامه را به شکل زیر اصلاح کنید : .
CODE دستورالعمل جدید ORG 100H MOV AH/0EH MOV AL/7 INT 10H INT 20H END راهنمای Org 100hبه DOS میگوید که برنامه باید از آدرس 100h شروع شود .
ما این کد را اجبارا در همه برنامه ها قرار خواهیم داد .
حالا برنامه را با تغییرات اعمل شده ذخیره کرده و با انجام مراحل قبلی دوباره ترجمه کنید .
پس از ترجمه فایل BELL.COM را اجرا کرده و نتیجه را مشاهده کنید % امروز برنامه ای با اسمبلر نوشیتم .
از این پس نیز تمام برنامه های را با اسمبلر مینویسیم و از توانائیهای آن استفاده میکنیم .