بخش اول آموزش : 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با 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
شماره ارسال: #2 RE:آموزش اسمبلی از پایه تا پیشرفته قدم دوم : با اطلاعاتی که تا کنون شروع کردیم به یاد گرفتن . از این به بعد باید این اطلاعا رو جلوه بدیم در این قسمت میخواهیم با استفاده از مطالبی كه در بخشهای قبلی یاد گرفتیم برنامه ای بنویسیم كه كامل و قابل استفاده باشد . با این برنامه میتوانیم فلاپی دیسكهای خودمان را با سرعت كپی كنیم ! امروز برنامه را به شكلی مینویسیم كه بتواند دیسكهای 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 RIVE 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 آدرس بافر برای ذخیره كه DSX است 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
شماره ارسال: #3 RE:آموزش اسمبلی از پایه تا پیشرفته خوب ، رجیسترها را دیدیم و آشنائی كلی با آنها پیدا كردیم . حالا میخواهیم به رجیتسرها مقدار بدهیم و آنها را بخوانیم و ... . ما معمولا در ےزبانهای دیگر از علامت =(یا =ابرای مقدار دهی استفاده میكنیم ولی در زبان ےاسمبلی این كار ممكن نیست . در عوض از دستورالعمل 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 را بزنید . ےعددی بصورت ****: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 مطالعه كنید . در قسمت بعد چیزهای بیشتری رو خواهیم خواند و یاد خواهیم گرفت .
شماره ارسال: #4 RE:آموزش اسمبلی از پایه تا پیشرفته تا اینجا یاد گرفتیم كه چطور مقادیر را بین ثباتها منتقل كنیم : با فرمان 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 استفاده خواهیم كرد . دستیابی به مكانهای حافظه نكته های جالب دیگری هم دارد كه در قسمت بعدی یاد خواهیم گرفت .
شماره ارسال: #5 RE:آموزش اسمبلی از پایه تا پیشرفته وقتی كه ما به روش گفته شده مقداری را از حافظه میخوانیم ، یك داده تك بایتی از حافظه گرفته میشود . اما ممكن است بخواهیم كه یك كلمه یا كلمه مضاعف ( 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 قابل استفاده هستند .
شماره ارسال: #6 RE:آموزش اسمبلی از پایه تا پیشرفته وقفه ها (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 ****: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 تولید میكند . ما امروز اولین برنامه اسمبلی خودمان را نوشتیم ، در قسمت بعد یاد میگیریم كه چطور از اسمبلر استفاده كنیم و امكانات آن را بكار ببریم
شماره ارسال: #7 RE:آموزش اسمبلی از پایه تا پیشرفته در این قسمت طرز استفاده از ماكرواسمبلر را یاد میگیریم و برنامه هایمان را بدون استفاده از 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 اسمبلی بنویسیم ، دستوراتمان همیشه از آدرس ****: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 را اجرا كرده و نتیجه را مشاهده كنید % امروز برنامه ای با اسمبلر نوشیتم . از این پس نیز تمام برنامه های را با اسمبلر مینویسیم و از توانائیهای آن استفاده میكنیم .
شماره ارسال: #8 RE:آموزش اسمبلی از پایه تا پیشرفته پرشهای غیر شرطی ے اگر با زبانهائی مثل 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 ے باید در یك سطر جای داده شود و اگر از این مقدار بیشتر بود میتوانیم در سطر بعد هم یك كاراكتر ( درج كرده و ادامه توضیحات را بعد از آن بیاوریم .
شماره ارسال: #9 RE:آموزش اسمبلی از پایه تا پیشرفته برنامه ای برای تعریف رنگهای جدید! اگر دارای كارت ویدئوی 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 را یاد گرفتیم و دیدیم كه نوشتن یك برنامه اسمبلی برخلاف آنچه تا بحال تصور میكردیم چقدر ساده و جالب است .
شماره ارسال: #10 RE:آموزش اسمبلی از پایه تا پیشرفته دستورالعمل 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و استفاده میكنیم .