Погружение в assembler. Полный курс по программированию на асме от
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
Assembler
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, rbp və rip 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, long və asci tiplər bir bayt, int və long 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:
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.
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. ))
Погружение в assembler. Полный курс по программированию на асме от ][
Эту идею мы вынашивали долго. Наверное, несколько лет мы штурмовали ее со всех сторон, и всякий раз нам что-нибудь мешало. С одной стороны, ассемблер — это круто настолько, насколько вообще может быть круто для нашего читателя-хакера (крякера, реверсера) умение общаться с компьютером на его языке. С другой стороны — актуальных руководств по асму, в том числе издания этого века, достаточно, а времена нынче либеральные, веб-хакеры и любители JS могут нас не понять и не одобрить. Точку в споре физиков, лириков, старообрядцев, никониан, веб-хакеров и тру-крякеров поставил успех цикла статей по реверсу малвари. Оказалось, что сейчас, в XXI веке, тру-крякеры все еще не сдали своих позиций и нашим читателям это интересно!
INFO
Это первая (вступительная) статья курса. Курс рассчитан на тех, кто в целом знаком с высокоуровневым программированием и только приступает к изучению ассемблера.
Читай далее:
- Зачем учить ассемблер в 2020 году
- Делаем первые шаги в освоении асма
- Осваиваем арифметические инструкции
- Как работают переменные, режимы адресации, инструкции условного перехода
- Учимся работать с памятью
- Работаем с большими числами и делаем сложные математические вычисления
- Сокращаем размер программы
- Пишем клон игры Flappy Bird, который уместится в бутсектор
- Пишем бейсик и умещаем его в 512 байт
Но что такое программирование само по себе по своей сути, вне зависимости от какого-либо языка? Разнообразие ответов поражает. Наиболее часто можно услышать такое определение: программирование — это составление инструкций или команд для последовательного исполнения их машиной с целью решить ту или иную задачу. Такой ответ вполне справедлив, но, на мой взгляд, не отражает всей полноты, как если бы мы назвали литературу составлением из слов предложений для последовательного прочтения их читателем. Я склонен полагать, что программирование ближе к творчеству, к искусству. Как любой вид искусства — выражение творческой мысли, идеи, программирование представляет собой отражение человеческой мысли. Мысль же бывает и гениальная, и совершенно посредственная.
Но, каким бы видом программирования мы ни занимались, успех зависит от практических навыков вкупе со знанием фундаментальных основ и теории. Теория и практика, изучение и труд — вот краеугольные камни, на которых основывается успех.
В последнее время ассемблер незаслуженно находится в тени других языков. Обусловлено это глобальной коммерциализацией, направленной на то, чтобы в максимально короткие сроки получить как можно большую прибыль от продукта. Иными словами, массовость взяла верх над элитарностью. А ассемблер, по моему мнению, ближе к последнему. Гораздо выгоднее в сравнительно небольшие сроки поднатаскать ученика в таких, например, языках, как С++, С#, PHP, Java, JavaScript, Python, чтобы он был более-менее способен создавать ширпотребный софт, не задаваясь вопросами, зачем и почему он так делает, чем выпустить хорошего специалиста по ассемблеру. Примером тому служит обширнейший рынок всевозможных курсов по программированию на любом языке, за исключением ассемблера. Та же тенденция прослеживается как в преподавании в вузах, так и в учебной литературе. В обоих случаях вплоть до сегодняшнего дня большая часть материала базируется на ранних процессорах серии 8086, на так называемом «реальном» 16-битном режиме работы, операционной среде MS-DOS! Возможно, что одна из причин в том, что, с одной стороны, с появлением компьютеров IBM PC преподавателям пришлось перейти именно на эту платформу из-за недоступности других. А с другой стороны, по мере развития линейки 80х86 возможность запуска программ в режиме DOS сохранялась, что позволяло сэкономить деньги на приобретение новых учебных компьютеров и составление учебников для изучения архитектуры новых процессоров. Однако сейчас такой выбор платформы для изучения совершенно неприемлем. MS-DOS как среда выполнения программ безнадежно устарела уже к середине девяностых годов, а с переходом к 32-битным процессорам, начиная с процессора 80386, сама система команд стала намного более логичной. Так что бессмысленно тратить время на изучение и объяснение странностей архитектуры реального режима, которые заведомо никогда уже не появятся ни на одном процессоре.
Что касается выбора операционной среды для изучения ассемблера, то, если говорить о 32-битной системе команд, выбор сравнительно невелик. Это либо операционные системы Windows, либо представители семейства UNIX.
Также следует сказать несколько слов о том, какой именно ассемблер выбрать для той или другой операционной среды. Как известно, для работы с процессорами х86 используются два типа синтаксиса ассемблера — это синтаксис AT&T и синтаксис Intel. Эти синтаксисы представляют одни и те же команды совершенно по-разному. Например, команда в синтаксисе Intel выглядит так:
mov eax,ebx
В синтаксисе же AT&T уже будет иной вид:
movl %eax,%ebx
В среде ОС UNIX более популярен синтаксис типа AT&T, однако учебных пособий по нему нет, он описывается исключительно в справочной и технической литературе. Поэтому логично выбрать ассемблер на основе синтаксиса Intel. Для UNIX-систем есть два основных ассемблера — это NASM (Netwide Assembler) и FASM (Flat Assembler). Для линейки Windows популярностью пользуются FASM и MASM (Macro Assembler) от фирмы Microsoft, и также существовал еще TASM (Turbo Assembler) фирмы Borland, которая уже довольно давно отказалась от поддержки собственного детища.
В данном цикле статей изучение будем вести в среде Windows на основе языка ассемблера MASM (просто потому, что он мне нравится больше). Многие авторы на начальном этапе изучения ассемблера вписывают его в оболочку языка си, исходя из тех соображений, что перейти к практическим примерам в операционной среде якобы довольно трудно: нужно знать и основы программирования в ней, и команды процессора. Однако и такой подход требует хоть мало-мальских начатков знаний в языке си. Данный же цикл статей от самого своего начала будет сосредоточен только на самом ассемблере, не смущая читателя ничем иным, ему непонятным, хотя в дальнейшем и будет прослеживаться связь с другими языками.
Следует отметить, что при изучении основ программирования, и это касается не только программирования на ассемблере, крайне полезно иметь представление о культуре консольных приложений. И совершенно нежелательно начинать обучение сразу же с создания окошечек, кнопочек, то есть с оконных приложений. Бытует мнение, что консоль — архаичный пережиток прошлого. Однако это не так. Консольное приложение почти лишено всякой внешней зависимости от оконной оболочки и сосредоточено главным образом на выполнении конкретно поставленной задачи, что дает прекрасную возможность, не отвлекаясь ни на что другое, концентрировать внимание на изучении базовых основ как программирования, так и самого ассемблера, включая знакомство с алгоритмами и их разработку для решения практических задач. И к тому моменту, когда настанет время перейти к знакомству с оконными приложениями, за плечами уже будет внушительный запас знаний, ясное представление о работе процессора и, самое главное, осознание своих действий: как и что работает, зачем и почему.
Что такое ассемблер?
Само слово ассемблер (assembler) переводится с английского как «сборщик». На самом деле так называется программа-транслятор, принимающая на входе текст, содержащий условные обозначения машинных команд, удобные для человека, и переводящая эти обозначения в последовательность соответствующих кодов машинных команд, понятных процессору. В отличие от машинных команд, их условные обозначения, называемые также мнемониками, запомнить сравнительно легко, так как они представляют собой сокращения от английских слов. В дальнейшем мы будем для простоты именовать мнемоники ассемблерными командами. Язык условных обозначений и называется языком ассемблера.
На заре компьютерной эры первые ЭВМ занимали целые комнаты и весили не одну тонну, имея объем памяти с воробьиный мозг, а то и того меньше. Единственным способом программирования в те времена было вбивать программу в память компьютера непосредственно в цифровом виде, переключая тумблеры, проводки и кнопочки. Число таких переключений могло достигать нескольких сотен и росло по мере усложнения программ. Встал вопрос об экономии времени и денег. Поэтому следующим шагом в развитии стало появление в конце сороковых годов прошлого века первого транслятора-ассемблера, позволяющего удобно и просто писать машинные команды на человеческом языке и в результате автоматизировать весь процесс программирования, упростить, ускорить разработку программ и их отладку. Затем появились языки высокого уровня и компиляторы (более интеллектуальные генераторы кода с более понятного человеку языка) и интерпретаторы (исполнители написанной человеком программы на лету). Они совершенствовались, совершенствовались — и, наконец, дошло до того, что можно просто программировать мышкой.
Таким образом, ассемблер — это машинно ориентированный язык программирования, позволяющий работать с компьютером напрямую, один на один. Отсюда и его полная формулировка — язык программирования низкого уровня второго поколения (после машинного кода). Команды ассемблера один в один соответствуют командам процессора, но поскольку существуют различные модели процессоров со своим собственным набором команд, то, соответственно, существуют и разновидности, или диалекты, языка ассемблера. Поэтому использование термина «язык ассемблера» может вызвать ошибочное мнение о существовании единого языка низкого уровня или хотя бы стандарта на такие языки. Его не существует. Поэтому при именовании языка, на котором написана конкретная программа, необходимо уточнять, для какой архитектуры она предназначена и на каком диалекте языка написана. Поскольку ассемблер привязан к устройству процессора, а тип процессора жестко определяет набор доступных команд машинного языка, то программы на ассемблере не переносимы на иную компьютерную архитектуру.
Поскольку ассемблер всего лишь программа, написанная человеком, ничто не мешает другому программисту написать свой собственный ассемблер, что часто и происходит. На самом деле не так уж важно, язык какого именно ассемблера изучать. Главное — понять сам принцип работы на уровне команд процессора, и тогда не составит труда освоить не только другой ассемблер, но и любой другой процессор со своим набором команд.
Синтаксис
Общепринятого стандарта для синтаксиса языков ассемблера не существует. Однако большинство разработчиков языков ассемблера придерживаются общих традиционных подходов. Основные такие стандарты — Intel-синтаксис и AT&T-синтаксис.
Общий формат записи инструкций одинаков для обоих стандартов:
[метка:] опкод [операнды] [;комментарий]
Опкод — это и есть собственно ассемблерная команда, мнемоника инструкции процессору. К ней могут быть добавлены префиксы (например, повторения, изменения типа адресации). В качестве операндов могут выступать константы, названия регистров, адреса в оперативной памяти и так далее. Различия между стандартами Intel и AT&T касаются в основном порядка перечисления операндов и их синтаксиса при разных методах адресации.
Используемые команды обычно одинаковы для всех процессоров одной архитектуры или семейства архитектур (среди широко известных — команды процессоров и контроллеров Motorola, ARM, x86). Они описываются в спецификации процессоров.
Например, процессор Zilog Z80 наследовал систему команд Intel i8080, расширил ее и поменял некоторые команды (и обозначения регистров) на свой лад. Например, сменил Intel-команду mov на ld. Процессоры Motorola Fireball наследовали систему команд Z80, несколько ее урезав. Вместе с тем Motorola официально вернулась к Intel-командам, и в данный момент половина ассемблеров для Fireball работает с Intel-командами, а половина — с командами Zilog.
Директивы
Кроме ассемблерных команд, программа может содержать директивы — команды, не переводящиеся непосредственно в машинные инструкции, а управляющие работой компилятора. Набор и синтаксис их значительно разнятся и зависят не от аппаратной платформы, а от используемого компилятора. В качестве набора директив можно выделить:
- определение данных (констант и переменных);
- управление организацией программы в памяти и параметрами выходного файла;
- задание режима работы компилятора;
- всевозможные абстракции (то есть элементы языков высокого уровня) — от оформления процедур и функций (для упрощения реализации передачи параметров) до условных конструкций и циклов;
- макросы.
Достоинства и недостатки
К достоинствам можно отнести следующее:
- минимальное количество избыточного кода (использование меньшего количества команд и обращений в память). Как следствие — большая скорость и меньший размер программы;
- непосредственный доступ к аппаратуре: портам ввода-вывода, особым регистрам процессора;
- возможность написания самомодифицирующегося кода (то есть возможность приложению создавать или изменять часть своего кода во время выполнения, причем без необходимости программного интерпретатора);
- максимальная «подгонка» для нужной платформы (использование специальных инструкций, технических особенностей железа).
За недостатки можно принять:
- большие объемы кода, большое число дополнительных мелких задач;
- меньшее количество доступных библиотек, их малую совместимость;
- плохую читабельность кода, трудность поддержки (отладка, добавление возможностей);
- непереносимость на другие платформы (кроме двоично совместимых).
Почему следует изучать язык ассемблера?
В современной практике индустриального программирования языки ассемблера применяются крайне редко. Для разработки низкоуровневых программ практически в большинстве случаев используется язык си, позволяющий достигать тех же целей многократно меньшими затратами труда, причем с такой же, а иногда и большей эффективностью получаемого исполняемого кода (последнее достигается за счет применения оптимизаторов). На ассемблере сейчас реализуются очень специфические участки ядер операционных систем и системных библиотек. Более того, программирование на ассемблере было вытеснено и из такой традиционно ассемблерной области, как программирование микроконтроллеров. Большей частью прошивки для них также пишут на си. Тем не менее программирование на языке ассемблера очень часто применяется при написании программ, использующих возможности процессора, не реализуемые языками высокого уровня, а также при программировании всевозможных нестандартных программистских хитростей. Отдельные ассемблерные модули, как и ассемблерные вставки в текст на других языках, присутствуют и в ядрах операционных систем, и в системных библиотеках того же языка си и других языков высокого уровня. Сегодня едва ли кому придет в голову сумасшедшая мысль писать крупную программу на чистом ассемблере.
Так зачем же тратить время на его изучение? По ряду веских причин, и вот одна из них: ассемблер — это краеугольный камень, на котором покоится все бесконечное пространство программирования, начиная от рождения первого процессора. Каждый физик мечтает разгадать тайну строения вселенной, найти эти загадочные первичные неделимые (низкоуровневые) элементы, из которых она состоит, не удовлетворяясь лишь смутным о том представлением квантовой теории. Ассемблер же и есть та первичная материя, из которой состоит вселенная процессора. Он — тот инструмент, который дает человеку способность мыслить в терминах машинных команд. А подобное умение просто необходимо любому профессиональному программисту, даже если никогда в жизни он не напишет ни единой ассемблерной строчки. Нельзя отрицать того, что невозможно стать математиком, совершенно не имея понятия об элементарной арифметике. На каком бы языке вы ни писали программы, необходимо хотя бы в общих чертах понимать, что конкретно будет делать процессор, исполняя ваше высочайшее повеление. Если такого понимания нет, программист начинает бездумно применять все доступные операции, совершенно не ведая, что на самом деле он творит.
Вообще, профессиональный пользователь компьютера, системный ли администратор, или программист, может позволить себе что-то не знать, но ни в коем случае не может позволить не понимать сути происходящего, как устроена вычислительная система на всех ее уровнях, от электронных логических схем до громоздких прикладных программ. А непонимание чего-то влечет за собой ощущение в глубине подсознания некоей загадочности, непостижимого таинства, происходящего по мановению чьей-то волшебной палочки. Такое ощущение для профессионала недопустимо категорически. Он просто обязан быть уверен вплоть до глубинных слоев подсознания, что то устройство, с которым он имеет дело, ничего волшебного и непознаваемого собой не представляет.
Иными словами, до тех пор пока существуют процессоры, ассемблер будет необходим.
В этом отношении совершенно не важно, какую конкретно архитектуру и язык какого конкретного ассемблера изучать. Зная один язык ассемблера, ты с успехом можешь начать писать на любом другом, потратив лишь некоторое время на изучение справочной информации. Но самое главное в том, что, умея мыслить языком процессора, ты всегда будешь знать, что, для чего, почему и зачем происходит. А это уже не просто уровень программирования мышкой, а путь к созданию программного обеспечения, несущего печать великого мастерства.
Ассемблер — программирование или искусство?
Скажем так, все зависит от того, в чьих руках он находится. Ассемблер — это первичный элемент мира процессора, из сочетаний этих элементов складывается его душа, его самосознание. Подобно тому, как вся музыка, написанная в истории человечества, состоит из сочетаний семи нот, так и сочетание ассемблерных команд наполняет компьютерный мир цифровой жизнью. Кто знает лишь три аккорда — это «попса», кому же известна вся палитра — это классика.
Почему же наука так жаждет проникнуть в квантовые глубины и захватить в свои руки неуловимый первичный кирпичик материи? Чтобы получить над ней власть, изменять ее по своей воле, стать на уровень Творца Вселенной. В чьи руки попадет такая власть — это еще вопрос. В отличие от науки, в мире программирования тайн нет, нам известны кирпичики, его составляющие, а следовательно, и та власть над процессором, которую нам дает знание ассемблера.
Чтобы программирование на языке ассемблера поднялось на уровень искусства, нужно постичь его красоту, скрывающуюся за потоком единиц и нулей. Как и в любой отрасли человеческой деятельности, в программировании можно быть посредственностью, а можно стать Мастером. И то и другое отличает степень культуры, образования, труда и, главное, то, сколько души автор вкладывает в свое творение.
Ассемблер и терминатор
Не так давно Джеймс Кэмерон выпустил в свет 3D-версию второго «Терминатора», и в качестве интересного исторического факта можно отметить один любопытный момент из жизни киборга-убийцы.
Кадр из фильма «Терминатор»
Здесь мы видим «зрение» терминатора, а слева на нем отображается ассемблерный листинг. Судя по нему, знаменитый Уничтожитель работал на процессоре MOS Technology 6502 либо на MOS Technology 6510. Этот процессор впервые был разработан в 1975 году, использовался на компьютерах Apple и, помимо всего прочего, на знаменитых игровых приставках того времени Atari 2600 и Nintendo Entertainment System (у нас более известной как Dendy). Имел лишь три 8-разрядных регистра: А-аккумулятор и два индексных регистра X и Y. Такое малое их количество компенсировалось тем, что первые 256 байт оперативной памяти (так называемая нулевая страница) могли адресоваться специальным образом и фактически использовались в качестве 8-разрядных или 16-разрядных регистров. У данного процессора было 13 режимов адресации на всего 53 команды. У терминатора идет цепочка инструкций LDA-STA-LDA-STA. В семействе 6502 программы состояли чуть менее чем полностью из LDA/LDY/LDX/STA/STX/STY:
LDA — загрузить в аккумулятор LDY — загрузить в регистр Y LDX — загрузить в регистр X STA — сохранить из аккумулятора STX — сохранить из регистра X STY — сохранить из регистра Y
Чтение и запись в порты ввода-вывода также выполнялись этими командами, и программа терминатора имеет вполне осмысленный вид, а не представляет собой бестолковую фантазию сценариста: MOS Technology 6502 / Система команд.
Отрасли практического применения
Ранее упоминалось, что в наше время ассемблер почти вытеснен языками высокого уровня. Однако и по сей день ему находится применение. Приведем некоторые примеры.
- Разработка встроенного программного обеспечения. Это небольшие программы, не требующие значительного объема памяти на таких устройствах, как, например, телефоны, автомобильные системы зажигания, системы безопасности, видео- и звуковые карты, модемы и принтеры. Ассемблер для этого идеальный инструмент.
- В компьютерных игровых консолях для оптимизации и уменьшения объема кода и для быстродействия.
- Для использования в программе новых команд, доступных на новых процессорах. Компилятор высокого уровня хоть и оптимизирует код при компиляции, но практически никогда не способен генерировать инструкции из расширенных наборов команд типа AVX, CTV, XOP. Потому что команды в процессоры добавляют быстрее, чем в компиляторах появляется логика для генерации этих команд.
- Большая доля программ для графического процессора GPU пишется на ассемблере, наряду с языками высокого уровня HLSL или GLSL.
- Для написания кода, создание которого невозможно или затруднено на языках высокого уровня, например получение дампа памяти/стека. Даже когда аналог на языке высокого уровня возможен, преимущество языка ассемблера может быть значительным. Например, реализация подсчета среднего арифметического двух чисел с учетом переполнения для x86 процессоров занимает всего две команды (сложение с выставлением флага переноса и сдвиг с займом этого флага). Аналог на языке высокого уровня ((long) x + y) >> 1 либо может не работать в принципе, ведь sizeof(long) == sizeof(int) , либо при компиляции конвертируется в огромнейшее количество команд процессора.
- Написание вирусов и антивирусников. Единственный язык программирования для создания достойных инфекторов — CIH, Sality, Sinowal.
- И конечно же, нельзя не упомянуть оборотную сторону медали: взлом, крэкинг и более легальный вариант — reverse engineering. Знание ассемблера — это мощнейший инструмент в руках реверсера. Ни дизассемблирование, ни отладка программ без знаний о нем невозможны.
Вместо заключения
Мы продолжим погружаться в ассемблер в следующих статьях цикла. Темы этого цикла мы в целом определили, но если у тебя есть какие-нибудь идеи или пожелания — смело пиши в комменты, все учтем.
WWW
- Архитектура x86
- 80×86 CodeTable
- Сравнение компиляторов
- Краткая история ЭВМ
ASSEMBLER PROGRAMLAŞDIRMA DİLİ
Prosessoru (CPU) kompüterin “ürəyi” və “beyni” hesab edə bilərik.Kompüterin yaddaşından və giriş/çıxış portlarından məlumatın oxunması, məlumatlar üzərində müxtəlif hesab, müqaisə, sürüşmə,məntiq v.s. əməliyyatların aparılması, emal olunmuş məlumatların təkrar yaddaşa (portlara) yazılması, hər-cür kəsilmələrin qəbul olunması və cavab verilməsi, habelə fiziki yaddaşın müxtəlif məlumat və instruksiya sahələrinə edilən müraciətlərə nəzarətin təmin olunması v.s. işlər məhs CPU tərəfindən həyata keçirilir.
CPU -nun dilinə proqramlaşdırma dilləri arasında ən yaxın dil Assembler dilidir. Yüksək səviyyəli dildə proqram tərtib edərkən CPU -nun əhəmiyyəti demək olar ki hiss olunmur. Buna səbəb isə kompilyator proqramlarıdır. Kompilyator bizim yüksək səviyyəli dildə yazdığımız proqramları assembler dilinə, daha sonra isə maşın dilinə, yəni obyekt koda çevirir.
Assembler dilində isə proqram tərtib edərkən yazdığımız kod cüzi dəyişikliyə uğrayaraq (maşın dilinə çevrilərək) birbaşa CPU tərəfindən icra olunur. Bu kitab Assembler dilindən bəhs edir. Paralel olaraq CPU-nun arxitekturası, iş prinsipi, fiziki yaddaşın strukturu, CPU -nun yaddaşa müraciət metodları v.s. barədə nəzəri məlumatlar verilir və assembler dilində müvafiq proqram kodları ilə praktik nümunələr gətirilir.
Aşağıdakı düyməyə vuraraq resursu yükləyə bilərsiniz.
Comments are closed, but trackbacks and pingbacks are open.