آموزش جامع زبان اسمبلی - Assembly
در ابتدا اين سوال مطرح ميشود اسمبلي چيست ؟
زبانهاي برنامه نويسي كامپيوتر عموما به دو دسته زبانهاي سطح بالا و زبانهاي سطح پايين تقسيم ميشوند . برخي اين زبانها را به صه دسته تقسيم بندي كرده اند . زبانهاي سطح بالا و زبانهاي سطح پايين و زبانهاي سطح مياني . زبان برنامه نويسي اسمبلي جز زبانهاي سطح پايين است .
زبان سطح پايين به زباني گفته ميشود كه از لحاض ساختاري و ترجمه بسيار به زبان ماشين نزديك است . يعني قابليت فهم ان براي ماشين بهتر و راحتتر است . اما زبانهاي سطح بالا با كاربر رابطه بهتري دارند و كاربر يا برنامه نويس با اين زبان راحتتر ارتباط برقرار ميكند .
در زبان اسمبلي به سبب پايين بودن سطح ان ويژگيهايي نهفته است كه در هيچ يك از زبانهاي ديگر اين ويژگيها را نميتوان يافت : يكي از ويژگيهاي مهم اين زبان باز گذاشتن دست كاربر در كنترل سخت افزار بويژه Cpu است . در واقع كاربر ميتواند با جز به جز پردازشگر و سخت افزار كامپيوتر ارتباط برقرار كند . بنابراين سرعت اينگونه برنامه ها نسبت به زبانهاي سطح بالا بسيار بالاتر است .
البته اين زبان داراي مشكلاتي نيز هست . كه از جمله مهمترين انها زياد بودن تعداد دستوراتي است كه كاربر بايد براي انجام عملي خاص از انها استفاده كند .
برنامه نويس براي برنامه نويسي بايد بر ارشيتكت ساخت Cpu مسلط باشد .
سورس اين برنامه ها اصولا خطوط زيادي دارد .
اين برنامه ها بسته به ماشين عمل ميكنند . يعني اگر ساختار اصلي ماشين تغيير كند . اين برنامه ها قابليت اجرا ندارند .
پاسخ : آموزش جامع زبان اسمبلی - Assembly
اميدوارم به مبناها تسلط داشته باشيد ولي براي اونايي كه بلد نيستن مقداري توضيح ميدم
از اونجايي كه زبان ماشين 0و1 هست هر عددي كه به ما ميدن بايد به اين دوعدد تبديل بشه.
مبناي اعداد:2-8-10-16
كه معمولاً اعداد رو در مبناي 10 به كار ميبريم.مثل 12؛45,67895
حالا ميخوايم يك عدد مبناي 10 رو به مبناي 2 تبديل كنيم
1-تقسيمهاي متوالي كه همتون بلدين ديگه؟؟؟
35=(100011) ‹در مبناي دو›
2-اين راه خيلي آسونتره و اگه يك عدد بزرگ دادن خيلي زودتر ميشه به جواب رسيد(عدد مورد نظر را به توانهاي 2 عدد تجزيه ميكنيم تا عدد مورد نظر يا استفاده از توانهاي 2 ساخته شود.به جاي عددهايي كه داريم 1 ميگذاريم)
35=32(5^2)+3
0^2 1^2 2^2 3^2 4^2 5^2
1 1 0 0 0 1 = 35
حالا اين سوال پيش مياد كه اعداد منفي رو چه جوري بايد به مبناي 2 برد؟؟؟
ابتدا توضيح مختصري درباره ي Bit وByte بدم.
به هر كدام از اين مربع ها يك بيت گفته ميشود با 0 و 1 پر ميشود.پس 8^2=256 حالت براي پر شدن اين مربع ها وجود دارد.
8bit=1byte 16bit=1Word
32bit=1Dw 64bit=Qw
پس باتوجه به اين جدول بازه بايت(براي اعداد مثبت)[0,255]=
براي اعداد منفي اين مقادير قرينه نميشود بلكه با استفاده از قانون مكمل2 اعداد رو منفي ميكنيم.
نكته:با عوض كردن بيت علامت عدد منفي نميشود
Byte= [-128,127]
مكمل1 = جاي 1و0 عوض ميشود (0به جاي1 و برعكس)
مكمل2 = از سمت راست به اولين يك رسيديم بدون تغيير مينويسيم ولي بقيه 0و1 ها عوض ميشود.
براي اينكه بيشتر متوجه بشيد يك مثال ميزنم
عدد 10- را به مبناي 2 ببريد؟
حل:ابتدا عدد 10 را به مبناي دو ميبريم (00001010)= 10
توجه كنيد كه اين مسئله در 8بيت حل ميشود و بايد صفرهاي پشت عدد حتماً نوشته شود
طبق قانون مكمل2 از سمت راست عددها را ميخونيم.0 و1 (به يك رسيد اعداد عوض ميشود)1و0و1و1و1و1.
(11110110)=10- در مبناي دو
با يك سوال اين مبحث رو تموم ميكنم
10000000 در مبناي دو برابر چه عدد يا عددهايي است؟
سلامت و موفق باشيد
پاسخ : آموزش جامع زبان اسمبلی - Assembly
كي ديگر از مبحث هايي كه در زبان اسمبلي شما خيلي استفاده ميكنيد جمع و تفريق مبناها است.
جمع:
مثل جمع كردن معمولي هست وزياد فرق نميكنه.
0+0=0 1+0=1 1+1=10 0+1=1
مثال:23o+110111b=?h
حل:23 در مبناي 8=010011
010011+110111=1001010=4ah
تفريق:
0-1=1 0-0=0 1-1=0 1-0=2(تو تفريق معمولي وقتي يه عدد كوچكتر رو ميخواستيم از يه عدد بزرگتر كم كنيم از عدد بعديش غرض ميگرفتيم.در اينجا هم به همين صورت عمل ميكنيم.با اين تفاوت كه مبناي عدد بايد توجه كنيم.مثلاً اگر در مبناي 2 باشه در موقع غرض گرفتن 2واحد به عدد اضاقه ميكنيم واگر مبناي 16 باشد 16 واحد)
مثال:202h-76d=?b
حل:
202h=1000000010 , 76d=000011110
پاسخ : آموزش جامع زبان اسمبلی - Assembly
از آنجاييكه برنامه نويسي به زبان اسمبلي؛به ماشين ربط دارد؛آشنايي با سخت افزار كامپيوتر هم ميتواند در بهتر نوشتن برنامه ها به شما كمك كند.البته من قصد ندارم به طور كامل درباره سخت افزار بحث كنم.چونهم شما دوستان ميدونيد هم خيلي گستردست.فقط قسمتهايي رو ميگم كه ممكنه درموردش اطلاعات نداشته باشيد.
در تمام كامپيوترها قسمتي به نام حافظه اصلي ياCPU وجود دارد كه اين مجموعه ميتواند دستورها يا دادها را به صورت يك بايت ذخيره كند.هر بايت حافظه اصلي داراي يك برچسب عددي با نام آدرس ميباشد كه اين آدرس هي ميتواند از 0000 شروع شده و مقدار ماكزيمم آن برابر عدد بدون علامتFFFF16 باشد.يك آدرس را ميتوان با پنج رقم شانزده شانزدهي نشان داد.اندازه ماكزيمم حافظه اصلي برابر 1مگابايت ميباشد.
اطلاعات در داخلCPU در محلهايي به نام ثبات(Register) ذخيره ميشود.رجيستر ها به سه دسته تقسيم ميشوند.
1-General Registerhttp://pnu-club.com/images/smilies/froown.gifثبات عمومي)اين ها ثبات هايي هستند كه در دستورعمل ها استفاده ميشود.به بيان ساده تر بيشتر يا اين علامتها سروكار داريم.
Ax: نتايج محاسبات در آن قرار ميگيرد به همين دليل به آن انباره هم گفته ميشود
Bx: پايه؛كاربرد همگاني
Cx: شمارش؛كاربرد همگاني
Dx :داده؛كاربرد همگاني
در مورد اين نشانه ها در قسمت برنامه نويسي مفصل تر توضيح ميدم فقط در اين حد كه به گوشتون خورده باشه بدونيد
اين رجيسترهاي همگاني به دو ثبات8بيتي مستقل يعني نصف بالايي براي 8بيت سمت چپ و نصف پاييني براي8بيت سمت راست استفاده نمود.اسامي اين ثباتهاي 8بيتي عبارتند ازAH,AL,BH,BL,CH,CL,DH,DL
2-Pointer & index Register: (ثباتهاي اشاره گر شاخص)
IP: Instruction pointer اشاره گر دستور العمل؛به دستور العمل درحال اجرا اشاره ميكند وبا اجراي دستورات به صورت اتوماتيك عوض ميشود
BP: اشاره گر پايه؛معمولاً براي آدرس دهي استفاده ميشود
SP: اشاره گر پشته؛به آخرين خانه پرشده پشته(Stock) اشاره ميكند.يعني وقتي اطلاعات در داخل آن قرار ميگيرد آخرين اطلاعات اول خارج ميشود
SI: شاخص مبدا
DI: شاخص مقصد
SI, DI در آدرس دهي براي گذاشتن يا برداشتن اطلاعات در حافظه اصلي استفاده ميشود
3-Segment register(ثباتهاي سگمنت)
DS:داده
CS:كد يا برنامه(دستورات به كد تبديل ميشود)
SSپشته(در فراخواندن استفاده ميشود)
EX :اضافي(شبيه داده است؛اگر داده زياد باشد استفاده ميشود)
پاسخ : آموزش جامع زبان اسمبلی - Assembly
درباره رجيسترها چند مطلب ديگه هم مانده كه در اينجا ميگم.
از پردازنده هاي 386 به بعد(+386) ثباتهاي عمومي به صورت زير معرفي ميشوند كه 36 بيتي ميباشند:
Eax, Ebx, Ecx, Edx
در ثباتهاي اشاره گر به صورت:
Edi, Esi, Esp, Ebp, Eip
در ثباتهاي سگمنت به صورت:
Fs, Gs
براي ديدن رجيسترها در Debug ,dosرا اجرا كنيد و فرمان 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
پاسخ : آموزش جامع زبان اسمبلی - Assembly
حافظه و آدرس دهي
هر كامپيوتر مبتني بر 8086 داراي حداقل 640 كيلوبايت حافظه است . اين 640
كيلوبايت به قطعات 64 كيلوبايتي تقسيم شده و ما اين قطعات را "قطعه " يا Segment
ميناميم . هر سگمنت هم به خانه هاي تك بايتي ديگري تقسيم شده است .
براي بدست آوردن مقدار يك بايت مشخص از حافظه ما بايد عدد مربوط به سگمنت و
همچنين شماره آن بايت در سگمنت ( كه آفست Offset ناميده ميشود-چندمين خانه از شروع سگمنت ) را بدانيم .
مثلا اگر مقدار مورد نظر در قطعه 0030h( يعني عدد در مبناي 16 است ) و آفست 13C4h
باشد ما بايد قطعه اي كه شماره آن 0030h است را بيابيم و بعد در همان قطعه
مقدار بايت شماره 13C4 را بخوانيم .
براي نمايش اين حالت بين عدد سگمنت و آفست علامت ( : ) قرار ميدهيم . يعني
ابتدا عدد مربوط به قطعه را نوشته و سپس عدد آفست را مي آوريم :
Segment: Offset
هميشه در آدرس دهي ها از اعداد مبناي 16 استفاده ميكنيم .
مثال:1234H:5678H
براي به دست آوردن آدرس فيزيكي جلوي آدرسBase يك صفر ميگذاريم و با آفست جمع ميزنيم.
12340+5678=179B8H
پاسخ : آموزش جامع زبان اسمبلی - Assembly
دستورهاي اجرايي اسمبلي
حالا ميخواهيم به رجيتسرها مقدار بدهيم و آنها را بخوانيم و ... . ما معمولا در
زبانهاي ديگر از علامت =(يا =ا براي مقدار دهي استفاده ميكنيم ولي در زبان
اسمبلي اين كار ممكن نيست . در عوض از دستورالعمل MOV كمك ميگيريم . با MOV
ميتوانيم داده ها را بين رجيسترها يا خانه هاي حافظه انتقال بدهيم . به اين صورت :
MOV D/S
مقاديري كهDميتوانند بگيرنند يك رجيستر، نام يك متغير، يا آدرس يك مكان از حافظه است و مقاديري كهS ميتواند بگيردهم يك مقدار عددي يا حرفي ، نام يك رجيستر و ... ميباشد .(cte=مقدار ثابت)
registe <r----> register
register ---> memory
memory ---> register
register --->cte
memory --->cte
segment register ---> general register
general register --->segment register
پاسخ : آموزش جامع زبان اسمبلی - Assembly
حالت هاي غير ممكن:
segment register --->segment register
segment register ---> cte
؟ ‹--- ثابت
باز هم بايد به يك يا دوبايتي بودن ثباتها توجه كنيم . به عبارت ديگر ما
نميتوانيم مقدار يك ثبات تك بايتي را به يك ثبات كامل دوبايتي منتقل كنيم .
مثلا عبارت mov DX/AL قابل قبول نيست چون AL يك بايتي بوده و DX دوبايتي است .
به عبارت ساده و كامل تر دو طرف عملوند MOV بايد از نظر اندازه برابر باشند.
بنابر اين :
MOV DL/AL
و MOV CL/BHوM درست ولي MOV DS/AH نادرست است .
به علاوه ما فقط ميتوانيم ثباتهاي همه منظوره AXتا DX را به اين صورت مقدار دهي
كنيم . در صورتي كه بخواهيم ثباتهائي مثل ..DS/ES/ را مقدار دهي كنيم بايد از
رجيستر AX به عنوان واسطه استفاده كرده و مقدار را از طريق آن انتقال دهيم .
مثلا:
نميتوانيم بنويسيم mov ds/20h
ولي ميتوانيم داشته باشيم :
mov ax/20h
mov ds/ax
به اين ترتيب مقدار 20hبه DS انتقال پيدا ميكند ( گرچه تغيير دادن DS ايده خوبي
نيست !)
حالا مطالب گفته شده را تمرين ميكنيم . ما ميتوانيم با DEBUG اسمبلي بنويسيم و
حتي برنامه هاي COM. درست كنيم . بنا براين در DOS، DEBUG، را اجرا كنيد .
D:\LNG\ASM> DEBUG
يك خط تيره به صورت - ظاهر ميشود . اين خط تيره اعلان DEUBG براي وارد كردن
دستورات است .
حرف A (به معني شروع وارد كردن دستورات اسمبلي ) را وارد كرده و Enter را بزنيد .
عددي بصورت xxxx:0100 ظاهر ميشود . اين عدد براي شما (فعلا) مهم نيست ، پس به
آن توجه نكنيد .
حالا ميتوانيد دستورات زير را وارد كنيد :
MOV AX/100
MOV BX/AX
MOV ES/AX
بعد از وارد كردن خط آخر يكبار ديگر كليد Enter را بزنيد تا اعلان (-) دوباره ظاهر
شود .
در سطر اول ما عدد 100h ( پيش فرض اعداد در Debug هگزا است ) را به AX منتقل
كرديم . بعد مقدار AXبه BX و سپس مقدار AXبه ES منتقل شده . به اين ترتيب همه
ثباتهاي AX/BX/ES بايد در نهايت برابر 100h باشند .
براي ديدن صحت اين مطلب دستور T ( به معناي Trace) را وارد كنيد .
با هر بار اجراي اين دستور يكي از سطرهاي برنامه اجرا ميشود . بعلاوه شما ميتوانيد
محتواي رجيسترها را هم ببينيد .
با اولين فرمان T ، سطر اول اجرا ميشود . بازهم فرمان T را وارد كنيد . الان مقدار100h
به BX داده شد و اگر به محتواي رجيستر AX توجه كنيد خواهيد ديد كه مقدار آن
(همانطور كه انتظار داشتيم ) برابر 100h است . دوبار ديگر هم فرمان T را صادر
كنيد و در نهايت مقدار ثباتهاي AX/BX/ES را ببينيد . هر سه ثبات (حالا) برابر 100h
هستند .
براي خارج شدن از Debug هم فرمان Q به معني Quit را وارد كنيد .
حالا فرض كنيد با اين آدرس روبرو شديد MOV [200H]/70H
اين دو از حافظه هستند پس چه جوري بايد مقدار را منتقل كنيم؟؟
خيلي راحت با استفاده از دستورPTR؛ براي اينكه آدرس يك متغير بيان كننده نوع آن متغير نميباشد(يعني چند بايتي است) از عملگر PTR استفاده ميشود.
BYTE PTR [......]
WORD PTR [......]
DWORD PTR [......]
MOV BYTE [200H]/70H.
مثال) كداميك از دستورات زير درست ميباشد؟
1) MOV BYTE PTR[DI]/BL
MOV BYTE PTR[SI]/300(2
MOV BYTE PTR[SI]/BYTE PTR[DI] (3
MOV [300H]/ BX (4
حل:
گزينه 2و3 غلطه؛چرا
2- چون 300 بيشتر از يك بايت است
3- انتقال از حافظه امكان ندارد
پاسخ : آموزش جامع زبان اسمبلی - Assembly
قبل از اينكه ادامه دستورهاي اسمبلي رو خدمتتون عرض كنم؛توضيحي درباره ي رجيستر وضعيت و كنترل(status& contorol register):
CF: carry flat
ZF: zero flag
PF: paritty flag
اگر نتيجه محاسبه صفر باشدZF؛يك ميشود واگر نتيجه صفر نشد؛اين بيت صفر ميشود
AF: پرچم نقلي كمكي=اگر درموقع محاسبه از بيت 3به4 رقم نقلي داشته باشيم اين بيت يك ميشود.
PF: پرچم تقارن= اگر تعداد 1هاي نتيجه زوج باشدPF-1ميباشد و اگر تعداد يكهاي نتيجه فرد باشدPF=0ميشود.
SF: پرچم بيت علامت.اگر نتيجه منفي باشدSF=1واگر تنيجه مثبت باشدSF=0ميشود.
OF:پرچم سرريز.اگر تنيجه محاسبه خيلي بزرگ يا خيلي كوچك باشد كه در مقدار پيشبيني شده جا نشود؛سرريز اتفاق مي افتد و OF=1ميشود.
دستورات اسمبلي:
دستورات انتقال داده:دستور MOVرو قبلا گفتم.در اينجا دستورهاي LEA ,XCHG رو بررسي ميكنيم.
XCHG:اين دستور محتواي D,Sرا با هم عوض ميكند.
XCHG D,S
D<==>S
Genelar register <==> general register
general register<==> memory
memory<==>general register
در اين دستور يك طرف حتماً بايد رجيسترباشد.
پاسخ : آموزش جامع زبان اسمبلی - Assembly
براي بدست آوردن مقدار يك بايت مشخص از حافظه ما بايد عد مربوط به سگمنت و
همچنين شماره آن بايت در سگمنت ( كه آفست 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