کامپیوتر Computer

برنامه نویسان ایرانی

آموزش اسمبلی از پایه تا پیشرفته

 

آموزش اسمبلی از پایه تا پیشرفته

 
با سلام خدمت شما دوستان عزیز به درخواست بسیاری از دوستان براتون آموزش اسمبلی رو آماده کردم این آموزش از امروز شروع شد ما در این آموزش سعی داریم که آموزش را از سطح پایین مقدماتی شروع کنیم و به سطح پیشرفته برسیم







بخش اول آموزش : 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
 
  •  

     

    قدم دوم : با اطلاعاتی که تا کنون شروع کردیم به یاد گرفتن . از این به بعد باید این اطلاعا رو جلوه بدیم


    در این قسمت میخواهیم با استفاده از مطالبی که در بخشهای قبلی یاد گرفتیم
    برنامه ای بنویسیم که کامل و قابل استفاده باشد . با این برنامه میتوانیم
    فلاپی دیسکهای خودمان را با سرعت کپی کنیم ! امروز برنامه را به شکلی مینویسیم که
    بتواند دیسکهای 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. است . پس کد برنامه را به شکل زیر
    اصلاح کنید :

    . MODEL SMALL
    . CODE

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


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

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

  • پرشهای غیر شرطی

    ے اگر با زبانهائی مثل 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
    ے باید در یک سطر جای داده شود و اگر از این مقدار بیشتر بود میتوانیم در سطر بعد
    هم یک کاراکتر ( درج کرده و ادامه توضیحات را بعد از آن بیاوریم .


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

  •  
  • برنامه ای
    برای تعریف رنگهای جدید!
    اگر دارای کارت ویدئوی 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و
    استفاده میکنیم .
                                                                                         باتشکر از شما
                                                                                            نویسنده:  جواد موسویلبخند
  • +