Assembly Programlama X86: X86 Intel in ilk mikroişlemcilerinden olan 8086 ile ilgili programlama kurallarını ifade eden bir tanımlamadır. Intel in önemli özelliklerinden biri olan "geriye dönük yazılım uyumluluğu" böyle bir tanımlamanın oluşmasını sağlamış. X86 tabanlı sistemlerin mimarisi birbirine benzer. Birde X86 tabanlı olmayan sistemler vardır. Apple firmasının imac bilgisayarları, yada SUN Microsystems firmasının sistemleri X86 tabanlı mikroişlemciler kullanmadıkları için bu tür bilgisayarda çalışan programlar X86 tabanlı sistemlerde çalışmazlar. Şunu unutmayalım; dünyada yaklaşık %90 oranında X86 uyumlu PC sistemi kullanılıyor. Bu yüzden bu X86 assembly dili diğer assembly dillerinden daha geçerli bir programlama dilidir.
ASSEMBLY PROGRAMA DİLİ VE DİĞERLERİ? Assembly programlama dilini 3 kelime ile tanımlayacak olursak bunlar; GÜÇLÜ, HIZLI ve KISA olurdu. Bu demek oluyor ki aynı programı assembly dili ile ve C++ programlama dili ile yaparsak, iki programın boyutlarına baktığımızda assembly ile yazılan daha kısa olduğunu görürüz, aynı zamanda mikroişlemciyi ve hafızayı daha verimli kullandığı için assembly dilinde yazılan program diğerlerine nazaran daha güçlü olacaktır, son olarak assembly dilinde yazılan program diğerine oranla çok daha hızlı çalışır. Son zamanlarda mikroişlemcilerin hızları GHz ler (giga hertz - ciga herz diye okunur) mertebesine çıktığından assembly dilinin en büyük özelliği olan hızlılığı artık pek popüler değildir. Kullanıcıya, 2GHz hızındaki bir CPU da hemen hemen her uygulama aynı hızda çalışıyor gibi görünür. Buna rağmen C gibi yüksek düzey programlama dilleri ile hazırlanan büyük projelerde işlemciyi çok fazla meşgul edecek olan kod bölümleri assembly rutinleri çağrılarak yapılmaktadır.
NEDEN ASSEMBLY? X86 Assembly dilini öğrenmek kolaydır ama bu dilde proje hazırlamak insanı çileden çıkartabilir. Bu yüzden günümüzde X86 assembly dili yerine, daha çok endüstriyel alanda kullanılan microcontroller chip lerin assembly dili kullanılmaktadır. Bir programcı assembly dilinde büyük uygulamalar hazırlamaz (genellikle). Bunun nedeni üst düzey programlama dilleri olan delphi, C, C++, Pascal a göre daha dikkat gerektirir ve kod yazımı daha zordur. Programcı assembly dilinde proje hazırlayacaksa kullandığı sistemin mikroişlemcisini ve hafıza haritasının yanında, sistemin donanımında iyi bilmesi gerekir. Her şeyden önemlisi zamandır ki sadece assembly dili ile PC uygulamaları geliştirmek iğne ile kuyu açmaya benzer. Assembly alt düzey bir programlama dilidir yalnız alt düzey kelimesini yanlış anlamayın, bu kelime bu dilin işlemciye ve hafızaya olan yakınlığını belirtir, üst düzey diller ise daha çok programcıya yani insana yakındır.
Bu yüzden assembly dilinin özel kullanım alanları vardır. Kısaca bunları şöyle sıralayabiliriz. 1- Bilgisayar sistemini yakından tanımak için, 2- Device driver (cihaz sürücüleri) yazmak için, 3- Chip lere program yüklemek için (PIC, microcontroller vb.) 4- İşletim sistemlerinin yapımında (OS), 5- Şifre kırma ve Hacking işlemleri için, 6- Virüs programları yazmak için, 7- Elektronik tablo lama (Excel gibi) programlarında.
İLK X86 ASSEMBLY KODLARIMIZ PC platformunda doğrudan hafızaya sembolik kod kullanmadan yazacağımız bu program için DEBUG kullanılır. Ancak; 64 Bit işletim sistemlerinde inline assembly desteği olmadığı için DosBox ile çalışılır. Bunun için 1. Aşağıdaki programlar indirilir a) WINDOWS 64bit Assembly Tool.zip b) npp.6.5.installer.exe c) DOSBox0.74-win32-installer.exe 2. "WINDOWS 64bit Assembly Tool.zip" ve bunun içindeki "DEBUG125.zip" içindeki tüm dosyalar C'de oluşturulacak TASM dizinine çıkarılır. 3. "DOSBox0.74-win32-installer.exe" kurulur
4. DOSBox çalıştırılır ve DosBox penceresinde "Z:\>MOUNT F C:\TASM" satırı işletilir 5. "Z:\>F" F'YE GEÇİLİR VE Debug çalıştırılır 1- Başlat menüsünden çalıştır ı tıklayıp cmd programını çalıştırın. 2- Komut istemindeyken cd\ yazın ve root dizinine geçin burada kendinize md asm yazarak asm adında bir klasör oluşturun ve cd asm komutu ile klasörün içine girin. 3- Debug yazıp enter tuşuna basın. Resimdeki görüldüğü gibi debug programı çalıştırıldığında ekranın solunda bir - simgesi görülür.
4- E 0100 yazıp enter tuşuna basınız. 5- b4 yazıp boşluk tuşuna (space bar) basın, 09 yazıp boşluk tuşuna basın, ba 0b 01 cd 21 b4 4c cd 21 4d 45 52 48 41 42 41 20 41 53 53 45 4d 42 4c 59 24 değerleri içinde aynı işlemi tekrar edin :) Şayet arada bir yerlerde hata yaparsanız klavyeden enter tuşuna basıp 4. adımdan itibaren yeniden başlayın. İşlem bitince ekran görüntüsü aşağıdaki gibi olacaktır.
6- Klavyeden Enter tuşuna basın ve ekran görüntüsü 3. adımdaki gibi olunca g tuşuna basıp ardından son olarak enter tuşuna tekrar basın.
Yukarıdaki ekran çıktısında görüldüğü gibi konsol ekranına MERHABA ASSEMBLY yazdırdık ve debug programından çıkıp komut istemine geri döndük.debug programı Microsoft un tüm işletim sistemlerinde bulunan, hafıza ve CPU nun içindekileri görmemizi, düzenlememizi sağlayan bir programdır. Çok ilkel bir program olmasına rağmen beynimizin derinliklerine PC nin yapısını kazıyacak olan ve bir assembly programcısının bilgisayara ne şekilde bakması gerektiği konusunda yardımcı olacak yegane programlardandır.
Debug içinde kullanacağımız e (enter) ve g (go) komutlarını kullandık. e komutu hafızaya kod girişi yapmamızı sağlar ve g komutu ise bu programın çalıştırılmasını sağlar. E komutu ile hafızaya giriş yaptığımızı söyledik, komut isteminin en solunda (Segment Adresi)1521:0100(Ofset Adresi) gibi bazı rakamlar gördünüz, buradaki 1521 sizin bilgisayarınızda farklı olabilir, ama 0100 aynıdır. işte : simgesi ile ikiye ayrılmış bu 8 rakam hafızanın adresidir. Kodlar hafızaya yazılır, silinir, değiştirilir fakat bu süreçte değişmeyen tek şey hafıza adresidir. Adresler her zaman bizim onları doldurmamız veya erişmemiz için hali hazırda beklerler, bunu evinizin adresine benzetebilirsiniz.
Eve anne gelir baba gelir arkadaş gelir bazen tatilde boş kalır yani evin içindekiler değişkendir ama ama ev adresi her zaman sabit kalır, ta ki ev yıkılana kadar :) E komutu ile bu örnekte hafızanın 1521:0100 adresi ile 152F:011B adresleri arasını makine kodları ile doldurduk. Bunu yaparken kod yazacağımız adreste hali hazırda hangi kod un olduğunu görme gibi bir lükse de sahiptik, tesadüfen hepsi 00 idi.
Makine kodları CPU nun anlayacağı yegane kodlardır ve 2 lik (binary) yada 16 lık (hexadecimal) gösterimle ifade edilirler. Zamanla binary gösterim biraz fazla yer kapladığından hexadecimal gösterim benimsenmiştir. Hexadecimal gösterimin anlaşılması zor olduğundan daha sonra sembolik kodlar ile yazılan Assembly programlama dili(a dili) geliştirilmiştir. Assembly den sonra, kod yazması ve anlaşılması daha kolay olan B Programlama dilini geliştirmiş bu da yetmemiş C dilini geliştirmiştir.
X86 PC lerin hafızası byte adreslenebilir diye ifade edilir. Bu ifade her adrese en fazla 1 byte lık kod yazabileceğinizi anlatır. Bizde burada öyle yaptık zaten, toplam 28 hafıza konumuna (memory location) kod yazdık ve derleyip dosya haline getirince dosya boyutunun 28 byte olduğunu göreceksiniz. Debug ın g komutu ise meşhur fetch-decode-execute sürecini yani hafızadan al getir - kodunu çöz - icra et sürecini başlatır. İşte biz buna program çalışıyor diyoruz. Programı mikroişlemci (CPU) çalıştırır ve programcı tarafından kısmen kullanılabilir. Bu süreç programcıları fazla ilgilendirmez, daha çok donanımla alakalıdır.
KODLARIN PROGRAM DOSYASINA SAKLANMASI: Bu işlemi yapmak için öncelikle programda ne kadar kod kullandığımızı bilmemiz yani programın byte cinsinden uzunluğunu bilmemiz gerekir. Kodları kendi elimizle teker teker girdiğimizden uzunluğunun 28 byte olduğunu biliyoruz. Emin olmak için 5. adımdaki şekilden faydalanınız. Dosya oluşturma işlemi için ilk önce CPU nun CX kaydedicisine 28 in hex. karşılığı olan 1C yi yazacağız(rcx). Daha sonra debug ın N komutu ile dosyaya isim(n merhaba.com) vereceğiz ve son olarak W komutu ile harddiskimize kayıt(w) yapacağız. Son olarak debug tan çıkıp komut istemine geri döneceğiz(q) ve oluşturduğumuz programı çalıştıracağız.
Dır komutu ile dizine baktığımızda aşağıdaki gibi dosyamızı görürüz. Artık komut satırına merhaba yazarak bu dosyamızı çalıştırabiliriz.
C:\asm>debug merhaba.com Yazdığımızda merhaba.com program dosyası yüklenir, yani program dosyasını oluşturan makine kodları hafızaya yükleniyor. Assembly programlama dilinde ayrıntılar çok önemlidir ve her bir ayrıntı kendi çapında bir araştırma konusu olabilir. Örneğin burada yaptığınız program yükleme işlemini windows kullanıcıları, programın simgesine mouse ile tıklayarak yapıyorlar ve hatta bu şekilde program hafızaya yüklendikten sonra birde işletim sistemi tarafından çalıştırılıyor.
Program kodlarını görmek için d enter yazıyoruz. Debug ın d komutu "dump" anlamına gelip kodları ekrana yada kağıda dökmeye yarar. Bizde burada kodları ekranda aldık. Burada önemli olan husus 0100 offset adresi ile 010B offset adresleri arasında kalan makine kodlarından bazılarının komut kodları bazılarının ise data (veri) kodları olduğunu bilmektir. Ekrana yazdırılacak olan MERHABA ASSEMBLY burada data oluyor. Kodlar ise;
Kırmızı ile işaretli olan kısımdan öncekiler yani 0100h ile 010Ah adresleri arasında kalan kısım komut kodlarımızdır. Tabi ki bu şekli ile bize pek bir şey çağrıştırmıyorlar. Şimdi bu programın Makine Dili ve Assembly kodlarına karşılaştırmalı bakalım.
Yukarıdaki şekilde makine kodlarını yeşil assembly kodlarını kırmızı çerçevede görebilirsiniz. Tabi ki assembly dili bizlere daha yakın bir dildir. Assembly dilindeki bu gösterim aynı zamanda sembolik kodlar olarak ta bilinir. Kodları assembly dilinde görmek için debug ın "u" (unassembly) komutunu kullandık, u dan sonra gelen 0100 010A ise hafıza aralığıdır, yani biz burada 0100h- 010Ah offset adresleri arasınındaki makine kodlarını assembly dilinde görmüş olduk. Şimdi yukarıdaki şekilde gördüğümüz makine ve assembly kodlarını karşılaştıralım;
B409 MOV AH,09 Assembly dilindeki MOV AH,09 un makine dilindeki karşılığının B409 olduğu görülüyor ve assembly programcıları "B409" ile gösterilen makine dilindeki bu ifadeyi iki kısıma ayırırlar. Bunlar opcode ve operand alanlarıdır. B4=Opcode 09=Operand dır.
Buradaki B4 opcode u işlenecek olan asıl emirdir ve ancak mikroişlemci (CPU) tarafından kodu çözüldükten sonra 09 operandı komut işleme sürecine katılır. Burada sadece opcode ile operandı ayırmanız yeterlidir. Intel in opcode uzunluğu 1 yada 2 byte lıktır. 2 byte lık olan opcode ları 0F ile başlar. Operandlar ise herzaman opcode lardan sonra gelir ve X86 mimarisinde 1,2,4 veya 8 byte lık olabilir. Böylece ortaya karma karışık bir komut seti çıkar, bu yüzdende Intel in x86 ailesi CISC (Complex Instruction Set Computers) olarak anılırlar.
BA0B01 MOV DX,010B Burda 3 byte lık bir komut satırı, opcode 1 byte lık operand 2 byte lık. Ama şimdi kafamızda 2 tane soru işareti var, birincisi neden MOV komutunun makine dilinde 2 tane farklı opcode u var? (yani B4 ve BA dan bahsediyoruz ve aslında MOV komutunun makine kod karşılığı 2 den de fazladır) ve ikinci soru neden MOV DX,010B için operand kısmı makine dilinde 0B01 olarak ters bir biçimdedir? Burada Opcode:BA Operand:0B01 dir.
Bu soruların cevabını X86 uyumlu mikroişlemci mimarisini anlayarak bulabilirsiniz. İlk olarak B4 veya BA MOV un karşılığı gibi görünse de öyle değildir, B4= MOV AH ın BA=MOV DX in opcode karşılığıdır. Yani ikisi de farklı assembly ifadeleridir ve farklı opcode larının olması son derece normaldir. İkinci sorunun cevabı ise x86 ailesindeki işlemcilerin hafızaya erişme şekillerinin ters sıralı olmasından kaynaklanır. Tabi ki birde düz olarak byte ları yerleştirme olayı var, bunlar kitaplarda little endian byte ordering ve big endian byte ordering olarak geçer. Aslında byte düzenleri çok çok ileri seviyede önemli konulardır ayrıca opcode ları gösterirken seçilen bu hexadecimal değerlerde (B4 ve BA gibi) rastgele seçilmiş değerler değildir.
Little Endian Byte Ordering: Hafızaya yüklenecek olan byte lar düşük değerlikli kısmından itibaren yazılır. Örneğin 1234 gibi iki byte lık bir veriyi hafızaya 0100 offset adresinden itibaren yazdığınızı düşünelim, bu işlem bitip hafızaya baktığınızda göreceğiniz şey 0100=34, 0101=12 olacaktır. Burada debug ın "a" (assembly) komutunu vererek assembly dilinde program yazma moduna geçtik ve dw talimatı ile hafızaya 1234 değerini girdik ve hafızaya, önce bu sayının düşük değerlikli byte ı sonrada yüksek değerlikli byte ı yazıldı. Big Endian Byte Ordering: Hafızaya yüklenecek byte lar en yüksek değerlikli kısmından itibaren yazılır aynen bizim kağıt üstüne sayıları yazdığımız gibi.
Assembly Kodlarının İşleyişi: Yazdığımız programda iki tane iş yapılıyor. Birincisi ekrana bir dizi byte ın ASCII görünümünü yazdırmak, İkincisi işletim sistemine geri dönüş. İkincisi fonksiyon numarası 4C olan "Terminate Program : Bunun içinde işlemcinin AH kaydedicisine 4C yazılır ve 21. interrupt servisi çağrılır. Bu program DOS un en genel kesmelerinden (interrupt) 2 tanesini çağırdı; Birincisi 9. fonksiyon olan "Display String : Bunun için işlemcinin AH kaydedicisine 09, DX kaydedicisine karakterlerin başlangıç adresi yazılır ve 21. interrupt servisi çağrılır. Son karakterden sonra hafızaya bir $ işareti koyularak karakterlerin bittiği belirtilir.
Bu işlemlerde kullanılan registerların durumunuda debug programıyla görüntüleyebiliriz. Şimdide 10 defa Merhaba Assembly Yazdıralım. Tabi böyle bir işi yaptırmanın değişik yolları mevcut. Şimdi yazacağımız programda ilk programımızın ilk üç satırını 10 defa çalıştırıp sonra programı sonlandıracağız. C dilindeki for döngüsü gibi. Aşağıdaki assembly programını yazınız ve dosya olarak kaydediniz.
Burada CX kaydedicisine (0A) 16 =(10) 10 değerini sayaç olarak yüklüyoruz. LOOP komutu CX in değeri kadar operandı ile belirtilen adrese dallanır, burada dallanılacak adres 0103 offset adresidir ve bu işlem sayesinde ekrana 10 defa MERHABA ASSEMBLY yazılır. Daha sonra programımız fonksiyon:4c interrupt 21 ile sonlanıyor. Assembly modunda hafızaya bir dizi karakter girmek için DB (Define Byte) talimatını kullanıyoruz. Talimatlar derleyiciye verilir yani opcode olarak dönüştürülmezler. DB den sonra şayet byte ları karakter olarak girmek isterseniz " " arasına yazmalısınız. Bu işlemi karakterlerin hexadecimal kodlarını yazarak ta yapabilirsiniz bu sefer her bir karakterden sonra "," koymanız gerekir. Burada her iki teknikte kullanılmıştır.
ASCII kod tablosuna bakacak olursanız 0A=LF yani Line Feed (bir satır aşağı) 0D=CR yani Carriage Return (Enter/Kursör satır başına) olduğunu görürsünüz, zaten CR ve LF kontrol karakterleri olduğundan dolayı standart ASCII kod tablosunda A,C,P,Z gibi normal karakter olarak karşılıkları yoktur mecburen hex. kod karşılıklarını kullanmak zorundayız. INT 21 fonksiyon 9 ile yazdıracağınız karakter dizilerinin sonunda mutlaka 24=$ bulunmalı yoksa bu interrupt servisi hafızada 24 değerini bulana kadar ekrana yazma işlemine devam eder. Bu programın kodları ile oynayarak konuyu daha iyi kavrayabilirsiniz, örneğin 0D karakterini kodlardan çıkartın yada CX e yüklenen değeri değiştirin.
! Son olarak debug ile bu programı adım adım çalıştırabilirsiniz, bunun için program yazma işlemi bitince P (Proceed) komutunu kullanın. Debug tan çıkmadan bir daha programınızı çalıştırmak için "r ip" komutunu kullanarak ip kaydedicisini 0100 yapın, böylece programın başlangıç adresini doğru ayarlamış olursunuz.
Kaynak