Assembler

Shamistan
Pragmatech
Published in
11 min readApr 4, 2020

1952-ci ildə amerikalı qadın Qreys Hopper dünyada ilk mnemonik proqramlasdırma dili olan Assembler dilini yaratdı. Onun adı “assemble” ingilis sözündən götürülmüşdür, “yığmaq”, “qurraşdırmaq” mənasını vərir. O, özündə mnemonik komandalar sistemini (komandaların siyahısı), prosedurlar kitabxanasını və proqram mətnlərini maşın koduna çevirmək üçün xüsusi proqramı birləşdirirdi.

Assembler dili digər proqramlaşdırma dillərinə bənzəmir və tamamilə fərqli bir dildir. Assembler dilinin 2-ci nəsil dil olması və 3-cü nəsil dillərin assembler üzərində inkişaf etdirilməsi qətiyyən o demək deyil ki, Assembler dili artıq köhnədir və o istifadə olunmur.

Assembler dili hal-hazırda proqramlaşdırmada çox böyük çəkiyə malikdir və onsuz müasir proqramlaşdırmanı təsəvvür eləmək kompüteri prosessorsuz təsəvvür eləmək kimi bir şeydir. Assembler dili hal-hazırda inkişaf etdirilir, yeni-yeni standartları hazırlanıb test edilir, müxtəlif instutlar, İT şirkətlər tərəfindən.

Assembler hal-hazırda ancaq sistem proqramlaşdırmada istifadə olunur.

Assembler dili system proqramlaşdırmanın açarıdır.

Sistem proqramlaşdırmada istifadəsi geniş yayılmış digər dil əlbətdəki ki C dilidir. Assembler əsasən C ilə tərtib olunması mümkün olmayan kritik kod hissələrinin tərtibində istifadə olunur. Buraya əsasən qurğu və proqram kəsilmələrini, giriş — çıxış portlarını v.s. idarə edən kod hissələri aiddir

Əməliyyatlar sistemi kəsilmələr vasitəsilə idarə olunur. Bu kəsilmələrin hər biri sistemin normal fəaliyyəti üçün olduqca vacibdir. Misal üçün sistem üçün ən vacib kəsilmələrdən hesab olunan saat kəsilməsi zamanı (timer interrupt — əməliyyatlar sisteminin ürək döyüntüsü) icra olunan kod yalnız assemblerd realiz oluna bilər. Aşağıda linux nüvəsinin 1.0 buraxılışından assembler dilində müvafiq kod kəsimi göstərilir(x86):

Program Strukturu

Kompüterdə icra olunan hər-bir proqram “hissə” adlandırılan struktur vahidlərinden ibarət olur. Yəni sade dildə desək proqram müxtəlif məqsədlər üçün nəzərdə tutulmuş bir neçə hissə-dən ibaret olur.

Proqramın ən vacib hissəsi əlbətdəki onun instruksiyalarını özündə saxlayan .text hissəsidir.

Proqramın diggər ən vacib hissəsi proqram məlumatlarını özündə saxlayan .data hissəsidir.

Proqramın diggər vacib hissəsi .stek dir, lakin hələki men .text və .data lardan danişacağam.

Proqramı sadə halda aşağıdakı kimi iki hissədən təsəvvür edə bilərik.

Hissə adlarının əvvəlinə(.data,.text) nöqtə qoyulduğuna diqqət yetirək.Assembler də nöqtə ilə başlayan ifadələr direktiv adlanir.Direktiv kompliyatira məlumat ötürmək üçündür.Misal: .data direktivi komplytora məlumat hissəsinin başlandığı yeri bildirir, .text direktivi isə instruksiyanın başlanğıcını bildirir.

Assembler dilində sadə proqram

Assembler də sadə proqram nümunəsi aşağıdakı kimi olar bilər.

.data

.text

.global _start

_start:

movl $5 %ebx

movl $1 %eax

int $0x80

proqramı sətir sətir təhlil edək.Burada .data direktivi məlumatın başladığı yeri bildirir. Amma biz hələki .data direktivinə heç bir məlumat yazdırmamışıq.

Burada yeni olaraq qeyd etmək istədiyim .global direktividir. Bu direktiv kompliyatora əhəmiyyətli nişanları bildirir.Burda da _start əhəmiyyətli nişan kimi qeyd edilmişdir.

_start:

Assembler də qoşa nöqtə ilə bitən ifadələr nişan adlanir.Nişanlar məlumat və ya instruksiyaların başlanğıc ünvanını bildirir (.data,.text) və həmin ünvana istinad etmək üçün istifade olunur. _strat nişanı xüsusi nişandır proqram ilk icra olunan zaman _start nişanından başlayır.

movl $5,

%ebx movl $1,

%eax int $0x80

Bu sətirlər artıq prosessor tərəfindən icra olunan instruksiyalardır. İlk iki instruksiya müvafiq olaraq prosessorun %ebx v ə %eax reqistrlerine 5 və 1 qiymetlərini yazır, sonuncu instruksiya isə 0x80 nömrəli kəsilməni çağırır.

Bu hissələr içərisində bizim biləmli olduğumuz və istifadə edəcəyimiz hissə reqistrlərdir.

Prosessorun diggər hissələri ilə sistem proqramçılar məşğul olur.

Reqistrlər prosessora aid çox kiçik yaddaş elementləridir.

Prosessor kompüterin fiziki yaddaşından(RAM) məlumatı kiçik hissələrə (4–8 bayt) reqistrlərə köçürür, sonra emal edir.Reqistrlerin hər-birin öz adları ilə müraciət olunur.

Reqistrlərin ölçüsü və sayı konkret prosessor arxitekturasından asılı olur.x86 arxitekturasının əsas işçi reqistrləri rax, rbx, rcx, rdx, rdi, rsi, rsp, rbprip reqistrləridir. Bu reqistrlərdən rax, rbx, rcx, rdx, rdi, rsi məlumatlarla, rsp, rbp stek yaddaşı ilə işlemək üçün istifade olunur.rip reqistri özünd icra olunan instruksiyanın ünvanın saxlayır. Bu reqistrlərin hər-birinin ölçüsü 8 baytdır (64 bit).

Bundan əlavə rax,rbx,rcx,rdx reqistrlərinin hər birinin ilk 32 bitinə uyğun olaraq eax,ebx,ecx və edx adı ilə müraciət etmək olar.32 bitlik(4 bayıtlıq) əməliyyatlar zamanı bu reqistrlərlə işləmək daha əlverişlidir.

Instruksiyalar:

Add(cəmləmək),Sub(çıxmaq),Imul(vurmaq),Div(bölmək)

Div digərlərinə nisbətən daha çətindir.

Bunlardan əlavə Inc(instruksiyası operandının qiymətini 1 vahid artırır.)Dec(bir vahid çıxır) və s.

Qeyd edim ki, assembler dilində, ümumiyyətlə proqramlaşdırmanın aşağı səviyyəsində bütün məlumatlar ədədlərlə ifadə olunur, ikili, onluq və 16 -lıq formada.

Dəyişənlər

Dəyişənlər proqramın .data hissəsinde elan olunur.Bildiyimiz kimi dəyişənlər hər hansı bir məlumatı yadda saxlamaq üçün istifadə olunur.Assembler də dəyişən elan etmək üçün aşagıdakı sintaksisden istifadə olunur.

.nişan:

.tip

Nişan dəyişənin adını bildirir tip isə dəyişənin yaddaşda neçə bayt yer tutduğunu göstərir.

Assembler hələ yüksək səviyyəli dillərdə olduğu kimi tam tipi, həqiqi tipi, v.s. tiplər xarakteristik deyil.Tip deyərkən əsasən yaddaşda tutulan yerin ölçüsü nəzərdə tutulur.Ən geniş istifadə olunan :byte,int, longasci tiplər bir bayt, intlong isə uyğun olaraq 2 və 4 bayt qədər yer tutur.

x:

.long

y:

.long

Bu zaman yaddaşda x və y adlı hər biri 4 bayt yer tutan iki dəyişən elan etmiş oluruq.

Yaddaş

Bu başlıqda da biz ən mürəkkəb və vacib mövzulardan biri olan yaddaş ilə tanış olacağıq.

Prosessorun yaddaşda yerləşən məlumatı əldə etməyin yollarını örgənəcəyik.

Yaddaş Strukturu

Kompyuterin yaddaşı ardıcıl düzülmüş və 0-dan başlayaraq nömrələnmiş kiçik bir yaddaş yuvaları kimi təssəvvür etmək olar.

Sonuncu yaddaş yuvasının indeks nömrəsi(k) yaddaşın həcmi ilə müəyyən olunur.Hər bir yaddaş yuvasının ölçüsü 1 baytdır və bu yaddaş yuvalarında yalnız və yalnız 0-dan 255 –ə kimi tam ədədlər yerləşdirmək olar.

Hər bir bayt öz növbəsində 8 bitdən ibarətdir.

Bu bitlər ardıcıllığının hər biri 0-dan 255-ə kimi hər hansı bir ədədə uyğun gəlir.

Buradan aydın olur ki,kompyuterin yaddaşında ədələrdən başqa heçnə yerləşdirmək olmaz.Istənilən tipli məlumatlar yaddaşda ədədlər ardıcıllığı şəklinde yerləşir.

Sonradan programlar həmin ədədlər ardıcıllığını istifadəçiyə tələb olunan şəkilde (musiqi,şəkil,metin v.s)göstərir.

Kompyuterin yaddaşında yerləşən məlumata müraciət etmək üçün onun ünvanını bilmək lazimdir.

Ünvan

Hər bir yaddaş yuvasının indeks nömrəsi onun “ünvanı” adlanır. İndeks nömrələri 0-dan başlayaraq nömrələndiyindən, yaddaşın ilk baytının ünvanı 0, növbəti baytının ünvanı 1 v.s. olar. Gördüyümüz kimi yaddaş ünvanları da öz növbəsində ədədlər vasitəsilə ifadə olunur. Hər bir məlumat və instruksiya yaddaşda müəyyən bir ünvanda yerləşir. Dəyişənin ünavnın əldə etmək üçün onun adının əvvəlinə dollar — ‘$’ işarəsi artırmaq lazımdır.

Prosessorun yaddaşa müraciət üsulu

Prosessor yaddaşdakı məlumatı əldə etmək üçün onun ünvanını bilməlidir.Ünvanı göstərmək üçün prosessorun təqdim elədiyi müxtəlif imkanlardan istifadə edə bilərik.

Yəqin ki,məlumatın prosessora ötürmənin ən sade üsulu məlumatın bir başa instruksiyaya yerləşdirmə üsuludur.

Movl $45 %ebx

Yuxardakı kodda 45 %ebx reqistrinə köçürülür.Bu zaman 45 qiyməti birbaşa movl $45 %ebx instruksiyasina yerləşdirilir,prosessor onu yaddaşın hansısa Ünvanında əldə etmir.Bir şeyə diqqet yetirək ki, baxdığımız üsulda ədədin əvvəlinde dollar $ işarəsi artrılıb.

Ikinci üsul nişandan istifadə etməkdir.Misal üçün yaddaşa long tipli y dəyişəni elan etmişik və ona 3 qiymətini mənimsətmişik.

.data

y:

.long 3

Bu zaman həmin məlumatı %ebx dəyişəninə köçürmək istəsək yaziriq:

movl y, %ebx

Nəticədə %ebx reqistrinə y dəyişənin-in “qiyməti” nə — 3 yazılmış olacaq. Bu dəfə artıq məlumat prosessora yaddaşdan köçürülür. Dəyişənin adının əvvəlində $ işarəsinin olmamasına diqqət yetirək.

Cərgələr

Cərgələrin özəlliyi odur ki ,onu təşkil edən elementlər yaddaş da ardıcıl düzülür və hər biri eyni ölçüdə yer tutur.Bu imkan verir ki, cərgənin ilk elementinin və ya hər-hansı başqa elementlərinin ünvanını bilməklə digər elementlerin ünvanını əldə edə bilirik.

Biz bir dəyişənə qiymət təyin etdikdə onun arxasınca vergülle ‘,’ ayırmaqla istənilən sayda qiymət əlavə ede bilərik.Bu zaman həmin qiymətlər ardıcıllığından ibarət cərgə alırıq.

.data

y:

.long 56, 45, 7, 890, 21, 9

Başqa sözle dəyişənlər bir elementdən ibarət cərgə kimi baxmaq olar. Ümumilikdə isə cərgələr aşağıdakı kimi elan olunur: əvvəlcə cərgənin adı göstərilir, daha sonra tipi, daha sonra isə cərgənin elementləri vergüllə ayrılmaqla sıralanır.

Cərgənin yaddaşdakı vəziyyəti

Gördüyünüz kimi cərgənin adı cərgənin birinci elementinə istinad edir.Bütün elementlər yaddaşda eyni ölçüde yer tutur və ardıcıl yerləşiblər.

Tutaq ki,cərgənin ilk elementinin ünvanını bilirik, onun şərti olaraq ÜNVAN ilə işare edək.

Tutaq ki, cərgənin elemetnləri long tiplidir ,yəni cərgənin hər bir elementi yaddaşda 4 bayt yer tutur.Bu zaman cərgənin ikinci elementi birincidən 4 bayt “yuxarıda” yerləşir.Əgər cərgənin birinci elementi ÜNVAN dırsa demək ki ikinci element ÜNVAN+4 olar.Beləliklə cərgənin k cı elementinin Ünvanı ÜNVAN+(k-1)*4 olar.Bu dediklərimiz cərgənin ilk elementinin Ünvanı və ölçüsünü bildiyimiz zaman icra olunur.

Əgər biz cərgənin n –ci elementini tapmaq istəyiriksə aşağıdakı düstur istifadə olunur.

n-ci_elementin_ünvanı = ilk_elementin_ünvanı +

(n-1)*cərgə nin_tipinin_ölçüsü

Nəzərə alsaq ki,cərgənin adı cərgənin ilk elementinə istinad edir, yuxardakı düsturu aşağdakı kimi yaza bilərik.

n-ci_elementin_ünvanı = cərgənin_adı + (n-1)*cərgənin_tipinin_ölçüsü

Stek

Stek də asssemblerin ən vacib olan mövzularından biridir.

Müasir kompyuter arxtekturaları funksiyalara müraciet stek vasitəsi ilə təmin edir.Əməliyyatlar sisteminin ən zəif hissəsi də məhs stek sayılır.Buna görə sisteme nəzarəti ələ keçirmək üçün ən geniş yayılmış hücumlar –buferi daşırma (buffer overflow) steke mudaxilə vasitəsi ilə həyata keçirilir.

Stek nədir?

Əməliyyatlar sistemi hər bir proqrama yaddaşda müəyyən sahə ayırır.Bu sahənin bir hissəsi proqramın instruksiyası və məlumatların yerləşdirilməsinə sərf olunur. Yerdə qalan sahə boş sahə adlanır.

Yerdə qalan boş sahəni proqram 2 məqsəd üçün istifadə edir:funksiya parametrələri və dinamik dəyişənlərlə işləmək üçün.Bu sahələr uyğun olaraq stek və heap adlanır.

Stek proqrama aid yaddaş sahəsidir və əsasən funksiyalar tərəfindən istifadə olunur.Stekə məlumat yerleşdirmək və stekdən məlumat götürmək xüsusi qayda ilə həyata kecirirlir.

Stek yaddaşı ilə işləmək üçün xüsusi instruksiyalar mövcuddur.

Stekə məlumat əlavə etmək üçün puş və stekden məlumat götürmək üçün pop instruksiyalarından istifadə olunur.Reqistirləri isə %rsp və %rbp reqistirləridir.

Stekin iş prinsipi. Steklə işleməyi örgənmək üçün aşağıdakıları bilməliyik: Proqramın əvvəlinde stek boş olur, yeni stekdə heç bir məlumat olmur. Proqramın icrası boyu stek məlumatlar yerləşdirilir (puş) və stekdən məlumatlar götürülür (pop) bilər. Bu zaman stek yaddaşının həcmi müvafiq olaraq artır və azalır.

Stekin ən üstü

Bilməyimiz gərəkən ən vacib məqam və ümumiyyətlə stekin mahiyyətini təyin edən məqam məlumatların stekə necə gəldi yox, yalnız və yalnız bir yerden əlavə olunması və götürülməsidir.Stekdə olan məlumatlara və ya stekə istinad yeri stekin ən üstü adlanır və %rsp

reqistri ilə təyin olunur.Biz stekə məlumat yerleşdirərkən və stekdən məlumat götürərkən prosessor $rsp reqisterin qiymətini avtomatik olaraq yeniləyir və %rsp reqisteri həmişə stekin ən üst hissəsinə istinad edir.

Biz bir şeyə adət etmişik ki, nəyəsə birşey əlavə eden zaman onda artma baş verir və bu artım özünü rəqəmlər vasitəsi ilə ifade edir.Amma stek də hər şey tərsinədir.Yəni ki, stek ilk başda boş olur steke nəsə əlavə etdiyimiz zaman həcmi kiçilir götürdüyümüz zaman isə həcmi artır.

Stekə məlumat yerləşdirmə — puş

Stekden məlumat yerləşdirmək üçün puş instruksiyasından istifadə olunur. 32 bitlik sistemlərdə puşl ,64 bitlikdə isə puşq instruksiyası istifadə olunur.

Stekdən məlumat götürmək — Pop

Stekdən məlumat götürmək üçün pop instruksiyasından istifadə olunur . 32 bitlik sistemlərdə popl ,64 bitlikdə isə popq instruksiyası istifadə olunur.

Funksiyalar

Funksiyalar proqramın .text hissəsində elan olunmuş nişanlardır. 1-ci başlıqda jmp — keçid instruksiyası ilə tanış olduq və qeyd etdik ki, jmp instruksiyası proqramın icrasını göstərilən nişandan davam etdirmək üçündür. Funksiyalardan da bu məqsəd üçün istifadə edirik. Fərq yalnız ondadır ki, funksiyalar çağırıldığı ünvanı “yadda” saxlayır və buna görə proqramın hər hansı yerindən çağırmağımızdan asılı olmayaraq, öz işini qurtardıqdan sonra funksiya həmin yərə “geri qayıda” bilir.

Funksiyanı çağırmaq üçün call instruksiyası istifadə olunur . call instruksiyası bir arqument qəbul edir,çağirilmali olan funskiyanın adını aşağıdaki kimi:

call funksiyanın_adı

Nəticədə funksiyanın_adı funksiyası icra olunur, başqa sözlə funksiyanın_adı nişanına keçid edilir. call instruksiyası təkcə göstərilən nişana keçid etmir ( əks halda bunun üçün sadəcə jmp instruksiyasından istifadə edərdik), həm də funksiyanın “ geri qayıda” bilməsi üçün funksiyanın “qayıtma ünvanını” stek yerləşdirir.

Buradan bir məsələyə diqqət yetirmək tələb olunur: funksiyanın qayıtma ünvanı stekə yerləşdirilir, yəni call instruksiyası icra olunan zaman stekin vəziyyəti dəyişir.

Tutaq ki, call instruksiyasından əvvəl stekin vəziyyəti aşağıdakı kimidir:

call instruksiyasından sonra stekin vəziyyəti aşağıdakı kimi dəyişər:

Qayıtma ünvanı stekə yerləşdirilir və stek göstəricisi aşağı sürüşür.

Funksiyadan geri qayıtmaq.

Funksiyadan geri qayıtmaq üçün ret instruksiyasından istifadə olunur .ret instruksiyası heç bir arqument qəbul eləmir

Bu instruksiya icra olunan zaman icra olunma funksiyanın çağırıldığı yerə qayıdır və həmin yerdən davam edir. Bunun üçün ret instruksiyası sadəcə stekin ən üstündə yerləşən məlumatı qayıtma ünvanı kimi qəbul edir və həmin ünvana keçid edir. Buradan bir məqam ortaya çıxır ki, funksiyadan qayıdan zaman stekin ən üstündə funksiyanın qayıtma ünvanı yerləşməlidir.Əks halda başqa yerə keçid olunar.

Deməli funksiyanı çağırdıqdan sonra və funksiya kodun icra etdikdə stek üzərində əməliyyat aparan zaman bir məsələdən əmin olmalıyıq ki, biz funskiyanın qayıtma ünvanın korlamırıq və bir də stek əməliyyatları stek göstəricisini yuxarı-aşağı sürüşdürdüyündən funksiyadan qayıdan zaman stek göstəricisinin funksiyanın qayıtma ünvanına istinad etməsini təmin etməliyik. Başqa sözlə ret instruksiyasını icra etməzdən öncə tam əmin olmalıyıq ki, stekin ən üstündə qayıtma ünvanı yerləşir.

Say Sistemləri

Ikili say sistemi

Ikili say sistemində ifade olunan ədədlər cəmi iki simvoldan, 0 və 1simvollardan ibarət olur.Aşağdakı

0, 01, 00, 10, 00101

Gördüyünüz kimi bu ədədlər yalniı 0 və 1 lərden ibarət olur.

Ikili ədədin 10-luq ədədə çevrilməsi.

Ikili ədədin 10-luq ədədə çevirmənin addımlarını izah edək.

1 0 0 1 0 1 0 1 1

Əvvəlcə verilmiş ikili ədədin rəqəmlərinin sağdan sola 0-dan başlayaraq nömrəliyirik:

Bundan sonra 2 ədədin hər bir rəqəminin nömrəsinə uyğun qüvvətini hesablayaq.

2 8=256

2 7=128

2 6=64

2 5=32

2 4=16

2 3=8

2 2=4

2 1=2

2 0=1

Daha sonra hər bir rəqəmə uyğun hesabladığımız qüvvəti həmin rəqəmə vurub alınan hasilləri cəmləyirik.

Aşağıdakı qalın şriftlə yazılan rəqəmlər power məsələn(2 üstü 8)deməkdir.

1* 2 8 + 0* 2 7 + 0* 2 6 + 1* 2 5 + 0* 2 4 + 1* 2 3 + 0* 2 2 + 1* 2 1 + 1* 2 0 = 1 * 256 + 0 * 128 + 0 * 64 + 1 * 32 + 0 * 16 + 1 * 8 + 0 * 4 + 1 * 2 + 1 * 1 = 256 + 0 + 0 + 32 + 0 + 8 + 0 + 2 + 1 = 299

Nəticədə alınan ədəd verilmiş ədədin onluq qarşılığı olar.

(b) 1 0 0 1 0 1 0 1 1 = (d) 299

Binary və decimal..

0 il 0-rı toplayanda 0 alırıq

1 il 0-rı toplayanda 1 alırıq

1 il 1-i toplayanda 0 alırıq 1 yadda qalır

1 il 1-i toplayanda və yadda 1 olanda 0 alırıq 1 yadda qalır.

Onluq say sistemi: rəqəmləri: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 on rəqəmi olduğuna görə onluq say sitemi adlanır.

Onluq say sistemini ikilik (səkkizlik və onaltılıq) say sisteminə keçmək üçün ədədi ikiyə (səkkizə, onaltıya)bölmək və qalığı qeyd etmək lazımdır.Sonra aldığımız cavabı yenidən ikiyə(səkkizə, on altıya)bölüb qalığı qeyd edirik.Prosesi axıra kimi davam etdiririk.Sağdan sola ardıcıl qalıqları yazırıq.

Onluq say sistiemində ikilik say sisteminə keçməyə aid bir nümunəyə baxaq.

Səkkizlik say sistemində də çevirmə eynilə ikilikdə olduğu kimidir.Sadəcə orda ikiyə bölmürük səkkizə bölürük.

On altılıq say sistemi: On altılıq say sistemində 16 rəqəm var . 0-dan 9-a qədər olan rəqəmlər onluq say sistemində olduğu kimidir.Digər rəqəmlər isə hərflərlə işarələnib.

On altılıq say sistemində də çevirmə ikilikdə olduğu kimidir.Sadəcə orda ikiyə bölmürük on altıya bölürük.Qalıq 9-dan böyük alındıqda yerində uyğun hərfi yazırıq.

Aşağıdakı nümunədə olduğu kimi.

Onluq say sistemində verilmiş 77 ədədin 16 –lıq say sisteminə çevirmək lazımdır.Qaydada deyildiyi kimi 77-ni 16-ya bölürük.Cavab 4 qalıq isə 13 olur.9-dan böyük olduğu üçün uyğun hərflə işrə edirik 13D.

Oxuduğunuz üçün təşəkkürlərimi bildirirəm. ))

--

--