Bu yazıda X86 işlemcilerine giriş yapıp Assembly programlama ile ilgili giriş bilgisi vermeye çalışacağım. X86 NEDĐR ? 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ış. Şöyle ki; 8086 mikroişlemcisi olan bir bilgisayar sisteminde hazırladığınız herhangi bir assembly programı X86 uyumlu tüm bilgisayarlarda çalışır. 80286, 386, 486 SX veya DX, Pentium, Pentium III, AMD nin 286, 386 işlemcilerinde veya Nexgen, Cyrix’in aynı tür işlemcilerinde ve diğerlerinde bu program çalışacaktır. Bu demek oluyor ki yukarıda saydığımız tüm mikroişlemciler ve uyumlu olan diğerlerinin ortak bir yönü var. Bu ortak yönler ortak program kodların olması ve mikroişlemcilerinin temel mimarileri birbirinin aynı olmasıdır. Yeni bir mikroişlemci üretildiğinde, eski mikroişlemcilere uyumlu olması büyük bir avantajdır. Öyle olmasaydı bugün dünyada %90 oranında PC, X86 uyumlu işlemci kullanıyor olmazdı. Bu yüzden mikroişlemci pazarının bu büyük dilimini birkaç firma paylaşıyor, aslan payını Intel, daha sonra AMD geliyor, Cyrix ve Nexgen gibi diğer mikroişlemci üreten firmaların isimlerini pek duymuyoruz. 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.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
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ı söyle sıralayabiliriz. 1234567-
Bilgisayar sistemini yakından tanımak için, Device driver (cihaz sürücüleri) yazmak için, Chip’lere program yüklemek için (PIC, microcontroller gibi...) Đşletim sistemlerinin yapımında (OS), Şifre kırma ve Hacking işlemleri için, Virüs programları yazmak için, Elektronik tablo’lama (Excel gibi) programlarında.
ĐLK X86 ASSEMBLY KODLARIMIZ PC platformunda doğrudan hafızaya sembolik kod kullanmadan yazacağımız bu programcık için herhangi bir editör ve derleyici download etmenize gerek yok. DOS veya Windows işletim sistemine sahip bir PC kullanmanız kafidir. Sırasıyla şunları yapın; 1- Başlat menüsünden çalıştır’ı tıklayıp cmd programını çalıştırın. Şayet Windows 98 kullanıyorsanız başlat menüsünden MSDOS komut istemini çalıştırmanız gerekiyor. Hala DOS işletim sistemini kullanıyorsanız bilgisayarınızın power butonuna basmanız yeterlidir :) yani halihazırda komut isteminde olan bir trex siniz.
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. Tabi ki klasör oluşturmak için bu kadar eziyet çekmenize gerek yok,
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
amacımız biraz nostalji. Maksat projemiz bir klasörde oluşsun.
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 ve bu programla harikalar yapabilirsiniz :)
4- E 0100 yazıp enter tuşuna basınız.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
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.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
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. Hala hevesiniz kırılmadıysa :) neler yaptığımızı tek-tek açıklayalım. 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. En fazla 2 makale sonra Debug programını kullanmayı bırakıp daha jan-jan lı debuger programları kullanacağız, şimdilik böyle idare edelim. Biraz önce Debug programının kendisi 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 CPU’ya şu sihirli kelimeyi söylememizi sağlar; "Ey CPU yazdığım bu kodları sırasıyla çalıştır bakayım". CPU programcının sadık kölesi olduğundan aynen denileni yapar. E komutu ile hafızaya giriş yaptığımızı söyledik, komut isteminin en solunda 152F:0100 gibi bazı rakamlar gördünüz, buradaki 152F 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 152F: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’dı. 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. Daha sonra hexadecimal gösterimin anlaşılması zor olduğundan sembolik kodlar ile yazılan Assembly programlama dili geliştirilmiştir. Đnsan oğlu rahatına pek
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
düşkün olduğundan :) daha sonra, kod yazması ve anlaşılması daha kolay olan B Programlama dilini geliştirmiş bu da yetmemiş C dilini geliştirmiştir. (Bu sıralamada Assembly programlama dilide A oluyor tabi ki) Programlama dilleri ABC diye devam derken C harfini çok seven programcılar buna ++ ve # gibi aksesuarlar takarak değişik varyasyonlar denemişler ve çokta başarılı olmuşlardır :) Neyse lafı fazla uzatmadan ve "ey programcı geçmişini bilmeyen geleceğinden bir haberdardır" diyerek konumuz geri dönelim. X86 PC’lerin hafızası byte adreslenebilir diye bir programcı ata sözü vardır. Bu söz 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 göreceksiniz biraz sonra programı derleyip dosya haline getirince dosya boyutu 28 byte olacak. 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 halk dilinde 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, bu yüzden ayrıntıya girmiyorum. Ama çok istek gelirse bu şarkıyıda sizler için söyleyebilirim :) BU KODLARI ÇOK SEVDĐYSENĐZ GELĐN BUNLARI SAKLAMAK ĐÇĐN BĐR PROGRAM DOSYASI OLUŞTURALIM 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 (CX ne yahu, kaydedicide ne demek kardeşim dediğinizi duyar gibiyim merak etmeyin bu terimler ileride hiç ağzınızdan düşmeyecek) 28’in hex. karşılığı olan 1C’yi yazacağız. Daha sonra debug’ın N komutu ile dosyaya isim vereceğiz ve son olarak W komutu ile harddiskimize kayıt yapacağız. Son olarak debug’tan çıkıp komut istemine geri döneceğiz ve oluşturduğumuz programı çalıştıracağız. Lütfen aşağıdaki şekli inceleyiniz. (Yalnız komut istemi penceresini kapattıysanız işiniz yaş 1.adıma geri dönmeniz gerekiyor çünkü kodlar hafızadan silinmiş olabilir)
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Meşhur DOS komutlarından olan "dir" ile tekrar bir nostalji yapıp dosyamızı görelim :)
Görüldüğü üzere 28 byte’lık merhaba.com adlı dosyamız oluşturuldu ve çalıştırılmaya hazır, o zaman hemen çalıştıralım.
Programımız konsol ekranına MERHABA ASSEMBLY yazdı ve işletim sistemi tarafından sonlandırıldı. Programımızı bir dosya haline getirdik, Microsoft’un işletim sistemlerinde 2 tür program dosyası mevcuttur ve bunlar dosya ismini takip eden EXE ve COM uzantıları (file extensions) ile tanınırlar. Bir burada COM olanından oluşturduk, endişelenmeye gerek yok EXE uzantılı dosya’da oluşturacağız ve COM ile EXE arasında ne gibi farklar var onları da göreceğiz fakat bunlar bizim için henüz çok ileri konular, sabretmek lazım :) Bu programı bir sonraki makalemize kadar silmeyin, çünkü bir sonraki makalemizde bu dosyadan faydalanıp sembolik kodlardan oluşan assembly diline geçiş yapacağız ve eksik kalan noktaları tamamlayacağız. Siz bu arada
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
boş durmayıp bu kodlar üzerinde oynamalar yapabilirsiniz özellikle 12. koddan itibaren son koda kadar olanları değiştirin (son kod hariç) çünkü bunlar MERHABA ASSEMBLY yazısının ASCII kod karşılığıdır. Bunun için ASCII kod tablosundan faydalanabilirsiniz. Başka bir programlama dili biliyorsanız aynı yazıyı yazan programı o programa dili ile yazıp dosya boyutunu buradaki ile karşılaştırınız. Ayrıca Debug programının diğer komutlarını görmek için işaretinin görüldüğü modda ? yazıp enter tuşuna basınız. Bir sonraki makaleye kadar esen kalın ve kendinize iyi davranın.
Geçen makalemizde debug programını tanımış ve makine kodları ile küçük bir program hazırlamıştık. Sonra bu programı yine debug ile bir dosya haline getirmiştik. Şimdi bu dosya ile biraz daha yakından ilgilenelim. Yine komut istemine geçiş yapıp debug programını şekildeki gibi çalıştıralım. Not:Geçen makalemizdeki dosyayı kaybettiyseniz buradan indirin ve C:\ dizininde asm adında bir klasör oluşturun ve bu dosyayı oraya kopyalayın.
Şekil 1 - Debug ile program yükleme
Ekranın solunda bir - işareti göreceksiniz ve başka hiçbir şey olmamış görünecek ama aslında merhaba.com adlı program dosyasını oluşturan makine kodlarını çoktan hafızaya yüklediniz bile. Makine kodlarını görmeden önce sizlere küçük bir tavsiye vermek istiyorum. 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. Şimdi aklınıza şu soru gelmeli; program simgesinin üzerine gelip mouse ile tık-tık yapıldığı andan itibaren, programın yüklenip çalıştırılmasına kadar sistemde neler ve nasıl yapılıyor? Öyle değil mi? Nelerin yapıldığını geçen
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
makalemizi takip ettiyseniz tahmin edebilirsiniz ama nasıl yapıldığına gelince o iş biraz sistem programcılığına giriyor yani bizim için biraz erken. Neyse biz konumuza geri dönelim, program şu anda hafızada ve 0100h offset adresinden itibaren yüklenmiş durumda. Hemen kodları görelim o zaman.
Şekil 2 - Debug’ın "d" komutu
Debug’ın d komutu "dump" anlamına gelip programlama aleminde kodları ekrana yada kağıda dökmek gibi bir anlamı vardır. Bizde burada kodları ekranda gösterdik. Neden 0100h adresinden itibaren hafızaya yerleşti? sorusunun yanıtı program dosyasının .com uzantılı olmasından kaynaklanır ve geçen makalemizde açıkladığımız gibi sonraki makalelerde ele alınacak bir konudur. Burada önemli olan husus sizin 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 bilmenizdir. Tabi ki ekrana yazdırılacak olan MERHABA ASSEMBLY burada data sınıfına giriyor peki komutlar nerede?
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Şekil 3 - Datalarımız...
Kırmızı ile işaretli olan kısımdan öncekiler (dikkat bu kısımdan sonraki kodlar bizi ilgilendirmiyor) yani 0100h ile 010Ah adresleri arasında kalan kısım naçizane komut kodlarımızdır. Tabi ki bu şekli ile bize pek bir şey çağrıştırmıyorlar şayet bunlar bizlerin anlayabileceği dile biraz yakın olsaydı anlardık.
Şekil 4 - Makine ve Assembly Dili Yan yana
Yukarıdaki şekilde makine kodlarını sarı 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ı
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
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. Opcode
Operand
B4
09
Tablo 1 : Opcode ve Operand
Buradaki B4 opcode’u işlenecek olan asıl emirdir ve ancak mikroişlemci (CPU) tarafından kodu çözüldükten sonra 09 operanıda komut işleme sürecine katılır. Burada sadece opcode ile operandı ayırmanız yeterlidir assembly dilindeki ifadeleri ne anlama geldiğini şimdilik önemsemeyin. Intel’in opcode uzunluğu 1 yada 2 byte’lıktır. 2 byte’lık olan opcode’ları 0F ile başlar. Biz şimdilik hep 1 byte’lık opcodelar ile çalışacağız. 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? Opcode
Operand
BA
0B 01
Tablo 2 : Opcode ve Operand
Bu sorların cevabı 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. Bu tür konuları öğrencilik yıllarımdayken
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
araştırmıştım, gerçekten epey zevkli konular, genellikle yüksek lisans ve doktora sınıflarında instruction anatomy (komut anatomisi) başlığı altında incelenen konulardır. Programcılık açısından ise sadece byte düzeni önemlidir. Bu yüzden burada sadece bun iki kavramı açıklayacağım. Little Endian Byte Ordering: Hafızda 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.
Şekil 5 - Little endian byte düzeni
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. Sizlere burada bir tanede big endian örneği vermek isterdim fakat şu anda elimde bir Apple PowerPC olmasına rağmen MAC OSX işletim sistemi için debugger yok :( Aşağıdaki tablo hangi sistemlerin hangi byte düzenini kullandığını gösteriyor. Đşletim Sistemi
Đşlemci Türü
Byte Düzeni
Digital Alpha AXP
Tru64 UNIX
littleendian
Hewlett Packard PARISC
HP-UX
bigendian
IBM RS/6000
AIX
bigendian
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Intel x86
Motorola PowerPC
Linux
littleendian
Windows
littleendian
Solaris x86
littleendian
Macintosh OS X
bigendian
SunOS
bigendian
Solaris
bigendian
Irix
bigendian
Sun SPARC
SGI R4000
Tablo 3 : Üreticilerin çoğu big-endian tercih ederken dünyada en çok kullanılan little-endian byte düzeni :)
CD21 INT 21 B44C MOV AH,4C CD21 INT 21 INT 21’de bir byte’lık opcode’u ve bir byte’lık operandı olan başka bir komut satırı. Bu arada MOV AH,4C nin opcode’u ile yukarıda açıkladığımız MOV AH,09’un opcode’larının aynı olduğuna dikkatinizi çekerim. Buraya kadar ; 1- Neo’nun Matrix’i gördüğü gibi bizlerde hafızaya baktığımızda orada gördüğümüz byte’ları nasıl yorumlayacağımızı gördük, bu yolda kendinizi geliştirmeye kalkarsanız sizde belki bir gün Matrix’i görebilirsiniz :) Yıllardan beri ben hafıza dökümlerine bakarım onlarda ekrandan doğru bana bakarlar ama program boyutu 10Kb’ı geçince insan halüsülasyonlar görmeye başlıyor onuda belirteyim :) 2- Makine kodları ile assembly kodları arasındaki bağıntıyı gördük, Borland gibi bir derleyici (compiler) yazan bir firma kurmak isterseniz, bu alanda en az bir doktora tezi vermeniz gerekir. Yani for (int i=0 ; i<100 ; i++) gibi bir ifadenin derleyici ile makine diline dönüşmesi zannettiğinizden daha karışıktır. Özetle buraya kadar derin konuların giriş kısımları anlatıldı. Şimdi sıra assembly kodlarına bir hacker gibi değil de bir programmer gözüyle bakmaya geldi. DEĞĐŞKENLERĐ DEKLARE ETMEK Örneğin 2 sayıyı toplayan program yazmayı düşünelim. Bunu 2 değişik şekilde yapabiliriz. Birincisi acil adresleme modu ile; MOV ADD
AL,5 AL,4
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Bu örnekte hiçbir değişken tanımlanmamıştır ve bu program parçası sadece 5 ile 4 ü toplar ve sonucu işlemcinin içindeki AL kaydedicisinde saklar. Tabi bu programın ne kadar kullanışlı olduğunu artık siz tahmin edin. Oysa bu sayıları birer değişken olarak tanımlasaydık daha esnek bir program hazırlamış olurduk. .DATA sayi1 DB 5 sayi2 DB 4 toplam DB ? . . . .CODE . . MOV AL,sayi1 ADD AL,sayi2 MOV toplam,AL Yukarıdaki program parçasında 3 adet bir byte’lık değişken tanımlanmıştır. Bunlar sayi1, sayi2 ve toplam değişkenleridir. Aslında bu yazdığımız satırlar assembler’a bir demeç şeklindedir. Assembler programımızı derlerken, hafızada bizim kullanmamız için 3 byte’lık alan ayıracak ve bu alanlardan birine 5 diğerine 4 değerlerini yerleştirecektir. Toplam değişkeninin değeri program çalıştığında önemli olacağından dolayı, onun değeri henüz atanmamış fakat şundan da emin olun ki o adreste muhakkak bir değer mevcuttur. Gördüğünüz gibi değişkenlerini isimleri, türleri ve değerleri mevcut. Değişken ismi (sayi1 gibi) aslında bir offset adresidir ve kullanımı seçimliktir. Şayet değişken tanımlarken isim kullanmazsanız assembler yine hafızada değişkenin türüne göre yer ayırır. Fakat böyle bir durumda değişkeni programınızın içinde kullanmak için adresini (ds:0000 gibi) bilmeniz gerekir. Çok sayıda değişken tanımlarken bu adresleri hesaplamak güç olacağından dolayı değişkenlere isim vermemek pek tavsiye edilmez. Değişkenin türü ise boyutu ile alaklıdır, örneğin DB ile değişkenimizin 1 byte’lık olacağını belirtiyoruz. Son olarak değişkenin değeri tabi ki bir rakam yada sayı olmak durumunda. Şayet başlangıçta değer önemli değilse ? operatörünü kullanabilirsiniz. Kaynak kodda değişkenleri bu şekilde yazarak assemblera hafızada yer tahsis etmesini söylemiş, bir başka deyişle değişken deklare etmiş oluyoruz. Đşaret Karmaşası 1 byte’lık bir hafıza alanına ne sığar? Toplam 256 adet farklı değer 1 byte ile tanımlanabilir. Bunlar onluk düzende 0-255 arası sayılar veya -128’den 127 ye kadar olan işaretli sayılar olabilir. Kafanız karışmasın bu değerleri aslında binary olarak düşünmek lazım, çünkü 0...255 ve -128...127 arası sayılar toplam 512 adet ediyor. Aslında, bunlar sadece 1’lerin ve 0’ların kombinasyonlarıdır. Yani 1 byte 0000 00002 ile 1111 11112 arasında değişen kombinasyonlardan oluşur. Günlük hayatımızda işaretli bir sayıyı tanımlamak ne kadarda kolay değil mi? Başına - işaretini getirdiğimiz zaman işlem tamamdır. Fakat bilgisayarlarda 1 ve 0 dan başka kullanacağınız bir değer yok. Đşaretli veya işaretsiz sayılar, harfler, metinler, resimler, mp3’ler ve Tomb Raider :) tamamen 1 ve 0’lardan oluşur.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Đşte bu 1 ve 0’ları program içinde yorumlamak önemlidir, ancak o zaman anlam kazanırlar. Aslında işlemci (CPU) sadece matematiksel, mantıksal ve veri taşıma işlemleri yapar. Bu bağlamda bizim için alt seviyede önemli olan 1 ve 0’lardır. O zaman işaretli ve işaretsiz sayılar neye göre belirlenecek? Boyutlar ve işlemci durum kaydedicisi (flag register) burada imdadımıza yetişiyor. Örneğin 1111 1111 değeri onluk düzende 255’mi yoksa -1’mi. 255’tir, çünkü 1111 11112 = 20+21+22+23+24+25+26+27 = 255 -1’dir, çünkü 1 ile toplandığında 1 byte’lık alanda 0000 00002 değeri elde edilir ve elde biti oluşur.
Şekil 1 - 1 byte’lık bir değer 1 byte’lık başka bir değerle toplanınca sonuç 1 byte’lık olmak zorundadır.
1 byte’lık bir değer 1 byte’lık başka bir değerle toplanınca sonuç bir byte’lık olmak zorundadır, çünkü ayrılan fiziksel alan bu kadardır. Bu işlemde artan 1 bitlik değer ilkokul matematiğinden bildiğimiz elde’dir ve bu elde işlemci durum kaydedicisinin C (carry) bitinde saklanır. Daha önce işlemci durum kaydedicisinin bitsel olarak önemli olduğundan bahsetmiştim. Bu kargaşa tamamen boyutların sınırlı olmasından kaynaklanıyor. Ayrıca bilgisayarın bu hesap mantığı ile pozitif bir sayı bir başka pozitif sayı ile toplandığında negatif bir değer de elde edilebilir. Bunu tam tersi de olabilir. Biz bunları ilerleyen makalelerimizde, işlemci durum kaydedicisini anlatırken işaret ve boyut taşması olarak inceleyeceğiz. Şimdilik siz matematiksel hesaplamalar yaparken alanlarını kısıtlı olduğunu göz ardı etmeyin. Yani 2 sayıyı toplayan program yerine, sonuç olarak maksimum 255 değerini gösteren program demek şu anda daha doğru olur :) BASĐT DEĞĐŞKENLER Byte Değişkenleri Bir byte boyutunda değerleri deklare etmek istiyorsak genellikle DB direktifini kullanıyoruz. DB Đngilizce’de Define Byte yani byte tanımla anlamına gelir. Bu direktif ile hafızada 1 byte’lık bir alan tahsis eder ve bu alana adresi ile erişebilirsiniz. MASM ile DB direktifi yerine byte yada sbyte direktiflerini de kullanabilirsiniz, fakat sonuçta assembler’ın yapacağı iş aynıdır. i j k
db byte sbyte
0 255 -1
;MASM için alternatif direktif ;MASM için alternatif direktif
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Yukarıdaki üç tanımlama farklı gibi gözükse de assembly dili yazımı değişkenleri tanımlarken boyut dışında bir sınır koymaz. Örneğin k değişkenine sonradan "mov k, 125" komutu ile pozitif bir değer atayabiliriz. Yapılamayacak olan ise "mov i, 300" veya "mov j, -150" gibi 1 byte’a sığmayacak değerleri bu değişkenlere atamaktır. DB direktifi ile; Ayrılan Hafıza Alanı : 1 Byte Tanımlanabilecek Değer Aralığı: -128...255 Word Değişkenler DW 8086’dan 80286 işlemcisine kadar offset adresleri, işaretli ve işaretsiz tamsayılar tanımlamak için kullanılan en geniş veri tipiydi. -32768...65535 arasındaki tam sayları DW direktifi ile deklare edebiliriz. MASM için word ve sword direktifleri de aynı işi görür. Word değişkenler hafızada 16 bit yani 2 byte’lık alan kaplarlar. sayi1
dw
?
sayi2
dw
?
isayi1
dw
?
sayi_0
dw
0
sayi_eksi
dw
-1
sayi_enbuyuk dw
65535
pointer
sayi1
dw
Özetle DW direktifi ile; Ayrılan Hafıza Alanı : 2 Byte Tanımlanabilecek Değer Aralığı: -32768...65535 Double Word Değişkenler 4 byte’lık değişkenler tanımlamak için kullanılır. DD direktifi ile bu türdeki değişkeleri tanımlayabilirsiniz. MASM ile dword, sdword direktiflerini de kullanabilirsiniz. sayi1
dd
?
sayi2
dd
?
isayi1
dd
?
sayi_0
dd
0
sayi_eksi
dd
sayi_enbuyuk dd
-1 4000000000
pointer
sayi2
dd
DD direktifi ile; Ayrılan Hafıza Alanı : 4 Byte Tanımlanabilecek Değer Aralığı: -2,147,483,648...4,294,967,295
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
6, 8 ve 10 Byte’lık Değişkenler DF direktifi ile 6, DQ direktifi ile 8 ve DT direktifi ile 10 byte’lık değişkenler tanımlanır. Fakat bu direktifler aslında kayar noktalı BCD (ikilik düzende kodlanmış onluk sayılar) sayılar için kullanılır. Bu 3 direktifin asıl kullanım amaçları aşağıda açıklanıyor. DF: 80386 ve üstü işlemcilerde, 32 bitlik korumalı modda, 48 bitlik pointer (indeksleme amacıyla, offset adreslerini göstermek için kullanılan bir işaretçi) olarak kullanılır. Bu direktif ile 6 byte’lık değişkenlerin tanımlanması mümkün olsa da 80386 ve sonrası işlemcilerde pointer olarak kullanılır. DQ: 64 bitlik kayar noktalı sayları 64 bitlik tamsayıları deklare etmek için kullanılır. Daha çok kayar noktalı sayıları deklare etmek için kullanılır, çünkü x86 ailesinin henüz 64 bitlik genel amaçlı bir kaydedicisi yoktur. Yani bu direktif ile 64 bitlik bir tam sayı deklare ettiğinizde, bu sayı ile doğrudan işlem yapamazsınız çünkü en büyük kaydedici 32 bitliktir. DT: Daha hassas (80 bitlik) kayar noktalı sayılar ve 10 byte’lık BCD değerleri deklare etmek için kullanılır. Yukarıdaki direktiflerle ilgili ayrıntılı bilgileri kayar noktalı sayılarla işlemler başlığı altında ilerleyen makalelerimizde vereceğim. Pointer Veri Tipleri Pointer işaretçi anlamına gelip adresleme modlarında gördüğümüz indeksli adresleme ile alakalıdır. x86 uyumlu işlemciler iki tür pointer’ı desteklerler. Bunlar near (yakın) ve far (uzak) pointer’lardır. Buradaki yakın ve uzak kavramı referans alınan adresin segmenti (64Kb.) aşmaması yada aşması durumunu ifade eder. Near pointer’ler 16 bitlik’tir ve bir segmentin offsetlerine erişmek için kullanılırlar. p bir değişken olsun, p=1000h için aşağıdaki kodları inceleyelim. mov mov
bx, p ax, [bx]
;BX’e p pointerını yükle. ;p’nin gösterdiği konumdaki veriyi AX’e getir.
Örnekte AX kaydedicisinin değeri pointer’ın işaret ettiği adresten alınacak 2 byte’lık değer ile değişecektir. Bir başka deyişle DS:1000h ve DS:1001h adresindeki değerler AX’e yüklenecek. Bu işlem aynı segment içinde olduğundan dolayı, pointer near yani yakın bölgedeki bir adresi göstermiş oldu. Aslında mov ax,ds:1000h komutu da aynı işi yapardı fakat pointer değişkeni kullanılmadığından sonradan pointer değerini değiştirip aynı komut satırı ile hafızanın başka bir bölgesine erişmek mümkün olmazdı. Far (uzak) pointer’lar ile hafızanın istediğiniz bölgesine erişip işlem yapabilirsiniz. Bunun için pointer deklare edilirken DD direktifinin kullanılması gerekir, yani pointer 32 bitlik olmalıdır. BX, BP, SI ve DI kaydedicilerini erişeceğiniz uzak offsetler için herhangi bir segment kaydedicisini de (ki bu genellikle ES olur) erişmek istediğiniz segment için kullanabilirsiniz. Đsterseniz bunu bir örnek ile açıklamaya çalışalım.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
5555:3333 adresine erişip, buraya FFh değerini kopyalamaya çalışacağız. Bu işlemi yapan program aşağıdaki gibidir. Kaynak kodları buradan download edebilirsiniz.
Şekil 2 - Uzak adreslere erişmek için 32 bitlik pointer’lar kullanılır.
Bu örnekte pointer 32 bitlik’tir ve erişeceğimiz segment:offset adresini gösterir. LES komutu ES kaydedicisine pointer değişkeninin yüksek değerlikli 2 byte’ını (5555h) yüklerken, operandında gösterilen kaydediciye de (bu örnekte BX) düşük değerlikli 2 byte’ını (3333h) yükler. MOV ES:[BX], AL komutu ile de 5555:3333 adresine AL deki FFh değeri kopyalanır. Aşağıdaki şekiller ile bu programın pointer kullanarak nasıl uzak adreslere eriştiğini görebilirsiniz.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Şekil 3 - LES BX, P komutu işlenmeden önce ES ve BX’in durumu
Şekil 4 - Pointer değişkeni ilgili kaydedicilere yüklendikten sonra
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Şekil 5 - 5555:3333 adresine FF değeri yüklendikten sonra
ÇOKLU DEĞĐŞKENLER Bu tür değişkenler birden fazla parçadan oluşur. Tek boyutlu ve çok boyutlu diziler (arrays) ve yapılar (structures) bu tür değişkenlerdir. Bu makalede sadece tek boyutlu dizileri anlatmaya çalışacağım, çok boyutlu diziler ve yapıları başka bir makalede güzel örneklerle, amacına uygun bir şekilde anlatacağım. Diziler Dizilerlerde diğer değişkenler gibi hafızaya yerleştirilmiş verilerden oluşur. Ardarda gelen byte, word veya double word değişkenler dizileri oluşturabilir. Daha önceki makalelerimizde "Merhaba Assembly" yazısını ekrana yazdırmıştık, işte bu karakterlerde hafızada dizi olarak saklanır. Şu anda yazdığım karakterlerde hafızada bir dizi veri olarak saklanacak. Dizi kavramının yanında getirdiği bazı terimler vardır, bunlar indeks, taban adresi, dizi elemanı ve eleman tipi’dir. Burada dikkat edilmesi gereken en önemli nokta diziyi oluşturacak elemanların aynı türden olma koşuludur. Diğer terimleride isterseniz aşağıdaki şekille açıklamaya çalışalım.
Şekil 6 - Diziler ile ilgili terimler.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Dizinin tüm elemanlarının aynı türden olması gerektiğini söylemiştik, bununla beraber dizinin ilk elemanının adresi taban adresidir. Dizinin elemanlarına erişmek için bu adres referans olarak alınabilir. Son olarak dizinin ilk elemanından itibaren adresler artarak devam eder ve bu adreslerdeki her elemana indeks değerleri ile erişilir. Dizinin herhangi bir elemanına erişmek için kullanılacak klasik formül; Eleman_Adresi = Taban_Adresi + ((Indeks - Đlk_Index) x Eleman_Boyutu) Bu formülde ilk_indeks 0 ise göz ardı edilebilir. Dizilerin Deklare Edilmesi Başlangıçta elemanların değerleri önemli değilse; Dizi DB 100 DUP (?) Dizi DW 100 DUP (?) byte’lık alan kaplar.
;byte türünden 100 elemanlı bir dizi ;word türünden olduğundan dolayı hafızada 200
Buradak DUP direktifi ingilizcedeki duplicate (diğerinin aynısı) kelimesinin karşılığıdır. Elemanlar aynı olacaksa; Dizi
DB
100
DUP (12h)
; Hafızaya 100 adet 12h yerleştirir.
Elemanlar farklı ise; Dizi DB 32, 45, 67, 3F, 0, 7, 11 dizi. Yazi DB ’Merhaba Assembly’ ascii kod karşılıkları saklanır.
;7 tane byte türünden elemandan oluşan bir ;16 elemanlı bir karakter dizisi, hafızada
Unutulmaması gerekir ki taban adresi dizinin başında yazdığımız etikettir, örneğin yukarıda ’Merhaba Assembly’ karakterlerinden oluşan byte türündeki dizinin taban adresi Yazi’dır. Tek Boyutlu Dizilerin Elamanlarına Erişmek WordDizi
DW
1, 45, 1234h, 567Ah, 99, 105Eh
Yukarıdaki dizinin 2. elemanına erişmek demek o elemanın adresini bulmak ile aynı anlama gelir. Bunun için formülümüz vardı; Eleman_Adresi = Taban_Adresi + ((Indeks - Đlk_Index) x Eleman_Boyutu) Bu formülü word tipindeki dizimize uygulayacak olursak; Eleman_Adresi = WordDizi + ((2 - 0) x 2) yada; Eleman_Adresi = WordDizi + 2 olarak bulunur.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Tabi ki bu işlemi bir program ile yapmak lazım. Aynı işi yapan program parçası aşağıda verilmiştir. mov add mov
bx, 2 ; indeks değeri BX’e yükleniyor, bx, bx ; 2*bx işlemi yapılıyor, ax, WordDizi [bx] ; AX’e dizinin 2. elemanı (1234h) yükleniyor.
Dizi elemanlarına erişmek için çok değişik teknikler mevcut, fakat bunları ilerleyen makalelerimizde açıklayacağım. Bazen assembly programcıları dizilerini deklare ederken, dizinin ilk elemanı olarak indeks değerini koyarlar. Tabi ki bu durumun doğruluğu görecelidir. Örneğin byte türünden bir dizi için bu teknikle en fazla 255 elemandan oluşan bir dizi deklare edebilirsiniz. Daha fazla eleman deklare etmek için indeks değerinin boyutunu arttırmak gerekir ama bu durumda da dizi elemanlarının aynı türden olması şartını göz ardı etmemek gerekir. Diziler usta programcıların vazgeçemediği veri yapılarıdır. Önemli olan dizinin elemanlarına erişmek için kullanılacak olan fonksiyonu assembly dilinde yazabilme kabiliyetidir. Örneğin; Yildizlar
DB
10 DUP
(’*’)
dizisinden aşağıdaki üçgeni ekrana yazdırmak gibi. * ** *** **** Alın size ödev, bir daha ki makaleye kadar bu programı yazmaya çalışın, böylece hem adresleme modlarını hem de tek boyutlu dizileri kavramış olursunuz. Sakın zor demeyin elin oğlu dizilerden matrix türetip, sonra ona felsefi bir boyut ekleyip filim yapıyor :)
Assembly Kodlarına Bir Assembly Programcısı Gibi Bakmak Yazdığımız programda iki tane iş yapılıyor. Birincisi ekrana bir dizi byte’ın ASCII görünümünü yazdırmak, ikincisi bilgisayarı tekrar kullanıcının ellerine bırakmak yani işletim sistemine geri dönüş.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Şekil 6 - Programımızın parçaları
Yukarıdaki şekilde algoritmanızın assembly koduna dönüşmüş halini görüyorsunuz. Tamam, geçen hafta bu programı yazarken algoritma falan hazırlamadık ama önce ekrana yazdır sonra çık mantığını için de algoritma yazılmaz herhalde :) Bu program DOS’un meşhur kesmelerinden (interrupt) 2 tanesini çağırdı aslında, birincisi 9. fonksiyon olan "Display String", ikincisi fonksiyon numarası 4C olan "Terminate Program". Bunları söyledikten sonra ne desem kafanız karışacak bu yüzden bu olayı daha iyi anlamanız için aşağıdaki diyalogu hazırladım. Buradaki acemi assembly programcı (AAP) azıcık assembly bilgisi olan birini, tecrübeli assembly programcısı (TAP) yeterli derecede bilgisi olan bir programcıyı ima ediyor. Acemi programcının kafasının içinden geçenler; AAP- Assembly dilinde ekrana bir şey yazdıracağım! AAP- Hafızaya dataları girer sonrada bunları ekran kartına gönderirim nasıl olsa ekran kartı daha sonraki işlemleri kendisi halleder, AAP- Ama gönderme işlemini nasıl yapacağım, hımmm ben en iyisi bir bilene sorayım. AAP- Merhaba TAP, sana bir sorum olacak ekrana MERHABA ASSEMBLY yazdırmak istiyorum ama ekran kartına byte’ları nasıl göndereceğimi bilmiyorum, bana anlatırmısın lütfen? TAP- Hangi işletim sistemini kullanıyorsun? AAP- Windows! TAP- Byte’ları ekran kartına göndermene gerek yok, onu senin yerine işletim sistemi yapar zaten. AAP- Peki nasıl olacak? TAP- INT 21’in 9. fonksiyonu senin için birebir, işlemcinin AH kaydedicisine 09, DX kaydedicisine karakterlerin başlangıç adresini yaz, sonrada 21. interrupt servisini çağır. Ha bu arada son karakterden sonra hafızaya bir $ işareti koy ki interrupt servisi yazılacak karakterlerin bittiğini anlasın. Anladın mı? AAP- Anlar gibi oldum? Ama ben bu işi kendim yapamaz mıyım?
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
TAP- Yaparsın tabi ama çok ekmek yemen lazım :) AAP- Nasıl yani?(bu arada TAP’ın gülüşüne de kızar tabi) TAP- Bak AAP kardeş, Windows gibi işletim sistemini yazan programcılar senin gibi programcıların ekrana bir şey yazmak isteyeceğini, veya klavyeden bir bilgi alacağını yada yazıcıdan çıktı almak isteyeceğini düşünerek bazı küçük programları yazıp işletim sistemlerine gömmüşlerdir. Sende bu hizmetlerden faydalanır ve Amerika’yı yeniden keşfetmeye gerek duymazsın. AAP- Evet çok akıllıca düşünmüşler doğrusu... TAP- Đşletim sistemi de bu demektir zaten AAP, programcıya, kullanıcıya bilgisayarı kolayca kullandırabilmek. AAP- Teşekkür ederim TAP sonra görüşürüz. TAP- (AAP’nin sonra tekrar gelip başka interrupt servislerini de soracağından endişelenerek) Hey AAP, bak buradan diğer interrupt servislerine de bakabilirsin, hani lazım olur diye söylüyorum... AAP- Teşekkürler TAP. Daha sonra AAP bilgisayarının başına gider yukarıdaki şekildeki programın ilk üç satırını yazar ve çalıştırır, tabi ki ekranda MERHABA ASSEMBLY görünür ama komut istemindeki penceresini kilitlenir, çünkü ekrana yazdırma işleminden sonra programı sonlandırmamıştır. Bunun üzerine programı sonlandırmak için tekrar interrupt servislerini araştırmaya koyulur ve interrupt 21’in 4C fonksiyonunu keşfeder. MOV AH,4C ve INT 21 satırlarını da programına ekledikten sonra programı kusursuz çalışır. Interruplar hakkında şimdilik bu bilgiler umarım sizi tatmin etmiştir. Đlerleyen makalelerimizde interruptları daha yakından ele alacağız, bu konular gerçektende çok zevkli. Yukarıdaki diyalog ta kaydediciler (register) diye bir kavram geçti, bunları şimdilik mikroişlemci içindeki hafıza konumlarına (memory location) benzeyen yapılar olarak düşünün. Kaydedicilerin listesini görmek için debug programını kullanabilirsiniz.
Şekil 7 - Kaydedicileri debug ile görmek için r (registers) komutunu kullanabilirsiniz.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
X86 kaydedicilerinin AX, BX, CX, DX, SP, BP... diye adları vardır ve bunların hepsinin değişik işlevleri mevcuttur. Program yazarken elbette bunları kullanacağız ama şimdilik üzümünü yiyin bağını sormayın, çünkü bağın yerini söylersem ve sizde gider o bağa bakarsanız iştahınız kaçabilir. Kafalar fazla karışmadan yeni bir uygulama yapalım.
10 defa Merhaba Assembly Yazdırıyoruz Tabi böyle bir işi yaptırmanın değişik yolları mevcut ( bir .bat uzantılı script dosyası hazırlayıp ilk programımızı 10 defa çalıştırmak bunlara dahil değil tabi :) ) Ş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 işlemleri takip edin.
Şekil 8 - 10 defa alt alta MERHABA ASSEMBLY yazan program
Programı yukarıdaki gibi hafızaya girdikten sonra bir önceki makalemizden faydalanıp bu programı dosya haline getirin, byte’ları saymada sizler için pratik olur. 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. Buradaki string’e dikkat, debug ile assembly modunda hafızaya bir dizi karakter girmek için DB (Define Byte) talimatını kullanıyoruz. Talimatlar (daha sonra ayrıntılı bir şekilde göreceğiz) derleyiciye verilir yani opcode olarak dönüştürülmezler, aynı C’deki int, double, string veri tipleri gibi. 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 (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 zorunda kaldık. INT 21 fonksiyon 9 ile yazdıracağınız karakter dizilerinin sonunda mutlaka 24=$ bulunmalı yoksa
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
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 T (Trace) komutu ile programınız içinden çağrılan interrupt servislerine de girebilirsiniz (ama çıkabilirmisiniz orasını bilemem) 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.
Şekil 9 - Adım adım programı işlemek
Bir sonraki makalemizde sayı düzenleri ile ilgili birkaç önemli noktayı, ve assembler konularını işleyeceğiz. Yorumlarınızı ve önerilerinizi bekliyorum....
Assembly programcısı sisteme nasıl bakıyor? Mikroişlemci bir çok karmaşık birimden oluşur, neyse ki programcılar donanımsal olarak sistemleri fazla yakından bilmek zorunda değillerdir. Bir assembly programcısı sadece mikroişlemci içindeki birkaç kaydediciyle ilgilenir. Bunun dışındaki birimler genelde okullarda bilgilendirmek amacıyla verilir, örneğin CPU içinde bir ALU biriminin olduğunu bir çok programcı bilir ama bu birimin çalışması ile ilgilenmezler. Bir assembly programcısı keskin bakışlarını mikroişlemcinin içinden çekip :) sistemin tamamına baktığında bile, donanımların ve donanımları birbirine bağlayan birimlerin çalışması ile ilgili fazla birşey göremez yada görmesine gerek yoktur. Programcılık ve elektroniğin sınır noktası budur aslında. Assembly programcıları kaydediciler, hafıza veya sistemi oluşturan birimlerde dolaşan elektron akımlarını 1’ler ve sıfırlar olarak görürler ve genelde bu 1 ve 0’lardan enaz 8 tanesini yan yana gördüklerinde bir şeyler anlamaya veya yorumlamaya başlarlar.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
X ışınlı gözlerimiz PC üzerinde?
Şekil 1 - PC’nin kabaca iç organizasyonu
Bir sisteme bilgisayar diyebilmeniz için o sistemin en az 1 CPU, memory (RAMROM/Hafıza) ve I/O (Input-Output/Giriş-Çıkış) birimin olması gerekir. Elbette bu birimle birbirlerine yollar (bus) ile bağlı olmalı ve sistemin açılabilmesi için hafızasında bir firmware programının yüklü olması gerekir. Yukarıdaki şekilde bu birimleri görüyorsunuz. Dikkat ederseniz bütün yollar CPU’ya çıkıyor, diğer birimler arsında doğrudan bir bağlantı yok, bunun böyle olması gerekir yoksa işler kontrolden çıkabilir. Aslında her modern bilgisayarda DMA (Direct Memory Access/Doğrudan Bellek Erişimi) adında bir transfer yapısı mevcuttur fakat bu özellik kullanıcı tarafından kapatılabildiğinden dolayı birimler arasındaki en temel bağlantı yukarıdaki şekildeki gibidir. Ayrıca DMA özelliği kullanıldığında CPU veri transferinden elini ayağını tamamen değil kısmen çeker, CD-ROM kilitlenmelerinde Ctrl+Alt+Del kombinasyonun zorda olsa çalışması bundan dolayıdır. Ne demişler kontrolsüz güç, güç değildir :) Konuyu fazla dağıtmadan isterseniz X ışınlı gözlerimizle CPU’ya zoom yapalım :)
X ışınlı gözlerimiz CPU üzerinde?
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Şekil 2 - Mikroişlemcinin kabaca iç yapısı
Sistemi içindeki birimleri birbirine bağlayan yollar, mikroişlemcinin içinde de devam eder. BUS dediğimiz bu yapılar birçok iletkenden oluşur. Kontrol yolunda R/W (oku/yaz) gibi kontrol sinyalleri, veri yolundan işlenecek veriler gidip gelir. Adres yolu iste tek yönlüdür ve CPU’dan diğer birimlere doğru sinyal akışı olur, örneğin hafızaya bir veri yerleştirilmeden önce adres yolundan verinin yerleştirileceği hafıza adresinin bilgisi gönderilir. Bunu daha iyi anlamak için aşağıdaki animasyonu izleyebilirsiniz yada animasyonu offline izlemek için download edebilirsiniz. En temel 3 birimden biri olan ALU aritmetik ve mantıksal işlemleri yapan bir elektronik devredir. BIU ise komut kodlarının icrası ile ilgilenir yani emirleri icra eden birim olarak basitçe tanımlanabilir. Kontrol ünitesi ise sistemdeki birimleri harekete geçirmek için kontrol sinyalleri üretir. Bu üç birime programcının doğrudan müdahalesi söz konusu değildir. Programcı sadece CPU içindeki kaydedicilere erişebilir.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Şekil 3 - Kaydediciler işlemcideki, aynı zamanda tüm sistemdeki en hızlı depolama birimleridir. Bu şekil 32 bitlik işlemciler (P4 serisi gibi) için kaydedici yapısını gösteriyor.
Kaydedicileri tam olarak anlayabilmeniz için onları kullanmanız gerekir, yani burada ne kadar anlatsak boş bu yüzden kısaca yukarıdaki şekilde gördüklerinizi açıklayıp hemen bir uygulama yapalım. Genel Amaçlı Kaydediciler Bu kaydedicilerin 8,16 ve 32 bitlik kullanımı mümkündür. Örneğin AL ve AH Accumulator’ün 8 bitlik kullanımını AX 16 bitlik EAX ise 32 bitlik kullanımını simgeler. Genel amaçlı kaydedicilerin hepsi verileri geçici olarak üzerlerinde barındırabilir fakat bazı x86 komutları buradaki kaydedicilere özeldir. Mesela loop komutu ile CX kaydedicisinin değeri azalır. Accumulator (EAX, AX, AH, AL): En sık kullanacağınız kaydedicidir. Çok genel kullanım alanına sahiptir, daha önceki makalelerimizde yazdığımız kodlara bakarsanız çok değişik amaçlarla kullanıldığını görebilirsiniz. Bu kaydedici birçok giriş/çıkış işleminde ve aritmetik işlemlerde (çarpma, bölme ve taşıma gibi) kullanılır. Ayrıca bazı komutlar işlenmeden önce accumulator’den parametre alır veya bu kaydediciye işlemin sonucunu kaydeder. Base (EBX, BX, BH, BL): Accumulator gibi genel amaçlı ve hafıza erişiminde indexleri göstermede kullanılır. Bir başka kullanım alanıda hesaplamalardır.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Counter (ECX, CX, CH, CL): Özel amaçlar ve hesaplamalarda kullanılacağı gibi genellikle bu kaydediciyi sayıcı olarak kullanılır, daha önce loop komutunun CX kaydedicisini otomatik olarak değiştirdiğini söylemiştik. Data (EDX, DX, DH, DL): Bazı giriş/çıkış komutlarında bu kaydedicinin kullanılması gerekir, ayrıca çarpma ve bölme işlemlerinde accumulator ile birlikte büyük sonuçları bu kaydediciden okuruz. Segment Kaydedicileri Segment kaydedicilerinin hepsi 16 bitliktir ve hafızanın segment olarak adlandırılan kısımlarını adreslemede kullanılır. Code Segment Kaydedicisi (CS): DOS işletim sisteminde programları oluşturan kodlar code segment’e yüklenir. CS kaydedicisi ise IP kaydedicisi ile birlikte programın çalışma sürecinde, programın oluşturan kodların adreslerini gösterirler. Data Segment Kaydedicisi (DS): .exe türündeki bir programda kullanılacak olan veriler data segment denilen hafıza bölümünde tutulur. DS kaydedicisi ise bu bölgedeki verilerin konumlarını gösterir. Stack Segment Kaydedicisi (SS): Tüm programlar stack segment denilen bir hafıza alanını geçici depolama alanı olarak kullanmak zorundadırlar (örneğin dallanma işlemlerinde). SS kaydedicisi ise SP kaydedicisi ile birlikte bu verilerin adreslerini referans eder. Extra Segment Kaydedicisi (ES): Bazı string işlemlerinde DI kaydedicisi ile birlikte karakterlerin bulunduğu hafıza adreslerini tutarlar. FS ve GS Kaydedicileri: 80386 ve sonrası CPU’larda bulunurlar ve diğer segment kaydedicilerinin yetersiz kaldığı durumlarda kullanılırlar. Özel kaydediciler IP ve EIP kaydedicileri: IP 16 bitlik DOS programlarının EIP ise 32 bitlik programların işlenmesi sürecinde, işlenecek olan bir sonraki komutun offset adresini gösterir. FLAG ve EFLAG kaydedicileri: Flag kaydedicisi 16 Eflag kaydedicisi ise 32 bitten oluşur. Bildiğiniz gibi mikroişlemci matematiksel işlem yapar, bu kaydedicilerde her işlemden sonra o işleme özel sonuçları gösterirler. Đşlemci durum kaydedicisi olarakta bilinen bu kaydediciler sonucun sıfır, pozitif veya negatif olduğunu veya işlemin sonucunda elde üretilip üretilmediği gibi birçok önemli veriyi bitsel olarak programcıya bildirirler. Index Kaydedicileri Bu kaydedicilerin E ile başlayanlar 32 bitlik programlarda, diğerleride 16 bitlik programlarda kullanılır. Hepsi de verilerin offset adreslerini tutmada kullanılır. SP ve BP, SS kaydedicisi ile birlikte SI ve DI, DS ve ES kaydedicileri ile birlikte hafıza adreslerine erişmek için kullanılır. Burada açıklamadığım diğer kaydediciler: X86 ailesindeki işlemcilerde başka kaydedicilerde mevcuttur. Bunlardan bazılarını programcı kullanamaz bazıları
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
ise ayrıcalıklı mod, korumalı mod diye adlandırılan özel çalışma modlarında anlam kazanır hatta bu tür çalışma modlarında yukarıda saydığımız kaydedicilerin birçoğu bambaşka amaçlar için kullanılırlar. Đşlemciyi kısaca anlatmaya çalıştım, sıra hafızada.
HAFIZA Şimdi hafızanın fiziksel ve mantıksal yapısını anlatacağım, fiziksel yapısı ile programcı fazla ilgilenmez hafızanın mantıksal yapısı ise tüm programcılara hemen hemen aynı görünür. Çok karışık bir konu olmasına karşın basit bir anlatımla bu konuyu kavramanızı umuyorum. Hafızanın Fiziksel Yapısı
Şekil 4 - Hafızanı fiziksel yapısı.
Yukarıdaki gibi bir hafıza yapısı, 16 bitlik bir veri yolu bulunan işlemciler için uygundur, örneğin 8086 işlemcisi için. Şayet veri yolu 32 bitlik ise daha fazla bank gerekir, özetle data bus’ın (veri yolu) genişliği kullanılacak olan hafıza chip’ini belirler. Hafıza denilince boyutu 8 bit olan bir dizi hücre ve bu hücrelerin numarası olan adresler akla gelir. Hafıza chip’leri veri, adres ve kontrol yollarına bağlıdır. Hafızada bulunan veriler hexadecimal formatta gösterilirler, bu binary formata en uygun sayı sistemidir. x86 hafızası ne kadar byte olarak adreslense de programcı hafızadan tek bir komut ile 8, 16 veya 32 bitlik bir değer işleyebilir. Böyle bir durumda hafızanın fiziksel yapısını bilmek işe yarayabilir. Örneğin 0. adresten itibaren 16 bitlik (2 byte veya word) bir değerin işlemci kaydedicilerine herhangi bir işlem için çekileceğini varsayalım. X86 ailesi işlemciler, hafızadan verileri düşük değerlikli kısmından itibaren okurlar (little endian). Bu örnekte hafızaya erişim çift numaralı adresten yapıldığı için ABFFh verisi data bus’a doğru bir şekilde
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
yerleşecektir ve en hızlı hafıza erişimi bu şekilde yapılır. Birde tek numaralı bir adresten örneğin 7. adresten okuma yapıldığını varsayalım. Bu durumda okunması gereken değer 4676h olmasına rağmen data bus’a 7646h verisi yerleştirilir gibi görünüyor değil mi? Hal böyle olunca işlemci önce Odd Bank’tan daha sonra Even Bank’tan okuma yapar ve bu byte’ları sıraya koyar, bu tür bir hafıza erişimi daha uzun zaman alır. Özetle çift numaralı adreslerden 1 byte’tan fazla bir değere erişilirse buna sıralı erişim (Aligned Memory Access), tek numaralı adreslerden okuma yapılırsa buna sırasız hafıza erişimi (Unaligned Memory Access) denir. Sıralı hafıza erişimi sırasızdan 2 kat daha hızlı olduğundan programlamada buna dikkat edilmelidir. Bu tür ayarlamalar assembly dilinde direktiflerle yapılır. Hafızanın Mantıksal Yapısı: Bir mikroişlemcinin ne kadarlık bir hafızaya erişebileceği adres yolu ile doğru orantılıdır. Adres yolu 16 bit olan bir mikroişlemci 216 yani 65536 adet hafıza konumuna erişebilir. 8086 işlemcisi 20 bitlik bir adres yoluna sahiptir ve adresleyebileceği alan 220=1048576 yani 1 MB. a denk gelir. 80286 işlemcisinin adres yolu 24 bitlik’ti ve 224= 16777216 hücreyi, başka bir deyişle 16MB. lık bir hafıza alanını adresleyebilirdir. Günümüzde kullanılan mikroişlemcilerin adres yolu çoğunlukla 32 bitliktir, buda 4 Milyar küsür hafıza konumuna denk gelir. Yavaş yavaş 64 bit devri başlıyor, AMD 64 bitlik işlemcilerini çoktan piyasaya sürdü 264 adet hafıza konumu geleceğin programlarını çalıştırmak için epey yeterli. Her ne kadar mikroişlemci böyle büyük hafıza alanlarını desteklese de böyle bir alanı kullanabilmemiz hafızayı nasıl organize edileceğine bağlıdır. Hafıza organizasyonuna yapılan bu yaklaşıma hafızanın mantıksal yapısı diyebiliriz. Hafızanın mantıksal yapısı çalışma moduna göre değişiklik gösterir ve çalışma modu işletim sistemleri tarafından belirlenir. X86 uyumlu tüm PC’ler Real Mod denilen çalışma modunda açılırlar, daha sonra işletim sistemine göre hafıza farklı bir şekil alabilir. Örneğin Linux yada Windows işletim sistemi yükleniyorsa hafıza Protected Mod (korumalı mod) denilen yapıya bürünür. DOS işletim sistemi yükleniyorsa (neydi o günler :)) hafıza real mod’da kalır. Birde korumalı mod altında DOS programlarını çalıştırmak ve korumalı mod ile real mod programları arasında geçiş yapmak için windows işletim sisteminin Virtual Protected Mod’u vardır. Tüm bu modlar tek başına bir makale konusu olduğundan ve şu an için biraz ağır konular olduğundan fazla ayrıntıya girmiyorum. Şimdi sadece en basit ve programlarımızda kullanacağımız mod olan Real Mode Adressing yeni gerçek modda hafıza’yı inceleyeceğiz. Real mod’da hafıza 64Kb’lık segmentlerden oluşur ve bu segment içindeki her byte’a erişmek için offset adresleri kullanılır. Bu durumda hafızanın tamamı bir kitap, segmentler sayfalar ve adreslenebilen her byte ise satır olara düşünülebilir. Protected mod çok daha karmaşıktır. 80286 ve sonrası işlemciler protected mod’a geçiş yapabilir. Sayfalama (paging), sanal bellek (virtual memory) ve multitasking (çok görevlilik) korumalı modun getirdiği büyük avantajlardır. Mikroişlemci hafızanın herhangi bir konumuna erişirken segment adresini segment kaydedicilerinden, offset adresini de şayet erişilecek olan bir komut kodu ise IP kaydedicisinden alır. Erişilecek olan veri (değişken, karakter vs.) ise verinin türüne göre offset adresini tutacak olan kaydedici değişir, örneğin erişilecek veri stack’ta (yığın bellekte diyebilirsiniz) ise buradaki verinin offset adresini SP (stack pointer) tutar.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Segment kaydedicileri 16 bitliktir, real mod’da offset adresleri index kaydedicilerinin 16 bitlik kısımlarında (SP,BP,SI,DI) veya 16 bitlik olan IP kaydedicisinde tutulur. Bu durum xxxx:xxxx formatında gösterilir. Örneğin 3456:76A3 real modda bir hafıza adresini gösterir, burada 3456 segment adresi iken 76A3 ofset adresidir. 3456:76A3 gibi bir adres gösteriminde 3456’yı sayfa numarası olarak düşünürsek bu sayfada 0000-FFFF arasında 65536 adet offset adresi (satır) mevcuttur diyebiliriz ve bu konumların her biri 1 byte’a denk gelir. Hafızanın gerçek yapısını göz önüne alırsak örneğin 8086 işlemcisi en fazla 1MB. lık hafızayı adresleyebilir fakat xxxx:xxxx şeklinde bir gösterim 4GB’lık bir alana denk gelir (FFFF kere FFFF). Hal böyle olunca işlemci real modda çalışırken gerçek adresi bulmak için bir hesaplama yapar çünkü gerçekte hafıza real mod’da 00000H-FFFFFH arasında anlamlıdır.
Şekil 5 - Programcı hafızayı adresleri olan byte tablosu olarak düşünür
Real mod’da gerçek adres şu formülle bulunur; 045F:0032 adresi için, segment adresi 4 bit sola kaydırılır veya başka bir deyişle adresin sonuna 4 adet 0 getirilir (0000)2=(0)16 böylece segment adresi 045F0 olur, sonra buna offset adresi eklenir ve gerçek adres bulunur. 045F0h+0032h=04622h gerçek hafıza adresidir.
Gerçek modda bilgisayarınız Pentium 4 bile olsa hafızanın sadece 1MB. lık alanını adresleyebilirsiniz, ayrıca sadece kaydedicilerin 16 bitlik kısımlarını kullanabilirsiniz. Bu son model ferrari’nizi otobanda 1. viteste sürmeye benzer. Şimdi’de hafıza ve kaydedicileri daha iyi anlamak için örnek program hazırlayalım. TITLE Kaydediciler ve Hafıza (regmem.asm) ;########################################
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
;# Bu program hafızadaki 2 adet byte’ı toplar ve sonucu # ;# tekrar hafızaya kaydeder. # ;# Son Güncelleme: 05/03/05 # ;# Yazan --> Eren ERENER # ;######################################## .MODEL .STACK .DATA Sayi1 Sayi2 Sonuc
SMALL 32
DB DB DB
5 6 ?
.CODE ANA
PROC
MOV MOV
AX, @DATA DS, AX
MOV ADD MOV
AL, Sayi1 AL, Sayi2 Sonuc, AL
MOV INT
AH,4CH 21H
ANA
;Data segment ;ayarlanıyor. ;AL’ye 5 değeri yüklendi. ;AL’deki 5 ile 6 toplandı sonuç tekrar AL’de. ;toplama işlemini sonucu hafızaya yüklendi. ;DOS’a ;dönüş
ENDP END ANA
Bir programın çalışma süreci genel olarak şöyledir, program önce hafızaya yüklenir ve mikroişlemci ilk komuttan itibaren programı atır-satır icra etmeye başlar. Bu program hafızaya yüklenirken hafızada mantıksal olarak 3 tane segment oluşturacak 1.si 32 byte’lık stacak segment bölgesi 2.’si 3 byte’lık data segment bölgesi ve 3.sü kodlarımın kapladığı alan kadar code segment bölgesi. Đşlemci kaydedicileride programın yüklenme sürecinde ilk değerlerini alırlar. Örneğin CS kaydedicisi code segment bölgesinin segment adresini ve IP kaydediciside bu bölgede bulunan kodların ilkinin offset adresinin değerini alır. Bu olayları en güzel uygulama yaparak anlayabiliriz, bunun için sizlere bir video hazırladım. Şimdi kendinize bir kahve hazırlayın ve keyifle bu videoyu izleyin. Kafanıza takılan bir soru olursa bana mail atabilirsiniz, bu arada olumluolumsuz eleştirilerinizi bekliyorum. Bir sonraki makalede görüşmek üzere hoşça kalın.
ADRESLEME MODLARI Bildiğiniz gibi programları oluşturan kodlar ve veriler hafızaya yüklendikten sonra işlemci tarafından satır-satır icra edilirler. Ayrıca CPU tüm giriş çıkış işlemlerini de hafızaya erişerek yapar. Bazen hafızadan doğrudan bir kod ya da veri alır, işler. Bazen hafızaya bir veri gönderdiğinizde birde bakmışsınız bu bir
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
yazıcıdan belge olarak çıkmış vs. Đşte bilgisayarın donanım ve yazılım düzeyinde yaptığı bunca çeşitli iş için CPU hafızaya değişik yollardan erişme ihtiyacı duyar. Sizlerde programlarınızı yazarken CPU’nun hafızaya nasıl erişeceğini yazdığınız kodlarla belirtmek zorundasınız. Assembly dilinin ilk basamağı olan adresleme modları da bu konuları kapsıyor. Her mikroişlemci üreticisi bir mikro işlemci piyasaya sürdüğünde, komut setini ve adresleme modlarınıda yayınlar. Programcılar da bu belgelere göre programlarını yazarlar. Intel 8086 işlemcisini piyasaya sürdüğünde oldukça kullanışlı bir dizi adresleme modu sağladı. Intel’in şu ana kadar ürettiği işlemcilerde bu adresleme modlarını kullanabilirsiniz. Daha sonraları bir devrim niteliğinde olan 80386 işlemcisi ile ek adresleme modlarıda geldi. Bu özel adresleme modları sadece 386 ve sonrası işlemcilerde kullanılabilir. Tabi ki bu ek adresleme modları ile assembly programlama dili daha esnek bir yapıya bürünmüştü. Bizde bu makalemizde adresleme modlarını 386 öncesi ve sonrası olarak ikiye ayıracağız. Şu ana kadar yaptığımız programlarda çok fazla komut örneği görmediniz ama makalelerimizi takip ettiyseniz MOV komutunu yakından tanımanız lazım. MOV komutu assembly programlama dilinde en çok kullanılan komutların başında gelir. Çünkü bir program çalışırken genelde hafızaya yazar yada okur. MOV komutu da bu iş için biçilmiş kaftandır ve bu kadar çok kullanıldığından dolayı bir çok adresleme modunu destekler, yani bu komut ile hafızaya çok değişik yollardan erişebilirsiniz. Bu yüzden bu makalede adresleme modlarını anlatırken MOV komutunu örnek alacağım. MOV komutunun genel kullanım şekli; MOV hedef, kaynak
8086 ADRESLEME MODLARI: 8086 Kaydedici Adresleme: Adından anlaşılacağı gibi kaydediciden kaydediciye yapılan işlemlerde bu adresleme modları kullanılır. En hızlı adresleme modu’dur, çünkü işlem hafızada değil işlemcinin içinde gerçekleşir. Genel amaçlı ve indeks kaydedicilerde kaydedici adresleme modları: mov mov mov mov mov mov
ax, bx dl, al si, dx sp, bp dh, cl ax, ax
; BX teki değeri AX’e kopyalar ; AL teki değeri DL’ye kopyalar ; DX teki değeri SI’ya kopyalar ; SP deki değeri BP’ye kopyalar ; CL deki değeri DH’a kopyalar ; Bu da doğru bir kullanımdır!
Kaydedici adreslemede en çok dikkat etmeniz gereken husus hedef ve kaynağın boyutlarıdır. Örneğin 16 bitlik bir kaydediciden 8 bitlik bir kaydediciye taşıma yapılamaz! mov
al, bx
;yanlış kullanım, derleme anında assembler hata verir.
Küçük boyuttaki kaynaktan büyük boyuttaki hedefe de kopyalama yapılamaz.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
mov
cx, al
; Yanlış kullanım, AL’ile CX eşit boyutta değil.
Bunlara ek olarak segment kaydedicilerinin kullanımında dikkat edilmesi gereken noktalar vardır. 1- Segment kaydedicileri arasında bir transfer işlemi ancak genel amaçlı bir kaydedici vasıtasıyla yapılabilir. mov ds,cs ; doğru kullanılmayan segment kaydedicisi şeklinde assembler hata verir! Bu işi yapmak için, mov mov
ax,cs ds,ax
komut satırları kullanılabilir.
2- CS ve IP kaydedicilerinin değeri kaydedici adresleme ile değiştirilemez mov cs, ax ; kaynak genel amaçlı kaydedici olmasına rağmen bu işlem mümkün değildir! Segment kaydedicileri programın icrası aşamasında hafızanın segment olarak adlandırılan bölümlerinin adreslerini tuttuklarından, bu kaydedicileri verilerinizi saklamak veya taşımak için kullanmanızı tavsiye etmem, kullanılmamalıdırlar. Bunun yerine genel amaçlı kaydedicileri kullanmanız daha uygun olur.
8086 Hafıza Adresleme Modları a- Acil Adresleme ( Immediate Addressing ) Herhangi bir genel amaçlı veya indeks kaydedicisine doğrudan bir değer yükleye bilirsiniz. Yüklenecek olan veri kod segmentten alınacağından bu tür kullanımları şahsen ben pek tavsiye etmem. Đyi bir program organizasyonu için, veriler hafızanın ayrı bir bölümünde (mesela data segmentte) değişkenler veya sabitler olarak belirtilmelidir. mov
al, 17
; AL’ye 11h yüklenir.
b- Direkt Adresleme (Displacement Only Addressing ) Acil adreslemenin doğru kullanılmış şeklidir. Bu adreslemede segment:ofset adresi kullanılarak hafızaya erişilir.
mov mov mov mov (2 byte)
al, ds:12 ds:12, al ax, ds:12 ds:12, ax
; ; ; ;
ds:000C adresinden 1 byte AL’ye kopyalanır. AL’nin içeriği ds:000C adresine kopyalanır. ds:000C adresinden 2 byte AX’ye kopyalanır. AX’nin içeriği ds:000C adresinden itibaren kopyalanır
Aslında kaynak kod hazırlanırken genelde bu şekilde bir kod yazımı yapılmaz. Değişkenler ve sabitler sembolik kelimelerle ifade edildiğinden buna gerek yoktur. Ne var ki debugger programlarında da sembolik değişken isimlerini değil
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
adresleri görürüz. Aşağıdaki kod parçasını inceleyin; .data sayi1
db 5
.code mov ax, @data mov ds, ax mov mov
al, sayi al, ds:0000
; 5 değerini al’ye yükler. ; aynı işi yapar yani 5’i AL’ye yükler. ; veriler tanımlanırken sayi1 db 5 şeklinde değil de ; sadece db 5 yazılsaydı, bu komutu kullanmak zorunda
kalırdık.
c- Kaydedici Dolaylı Adresleme ( Register Indirect Addressing ) Adının kaydedici olduğuna aldanmayın. Burada operand olarak kullanılan kaydedici köşeli parantez içine alınır ve bu andan itibaren bir offset adresi oluverir. mov al, [bx] ; hafızadan AL’ye 1 byte taşınır. Alınacak verinin offset adresi BX’in değeridir. mov mov mov mov
al, al, al, al,
[bx] [si] [di] [bp]
Yukarıdaki 4 örnekte AL’ye kopyalanacak verilerin offset adresleri ilgili kaydedicinin içindeki değerdir. Segment adresleri ise ilk üçünün DS sonuncusunun SS’dir. BP indeks kaydedicisi yalnız başına hafıza adreslemede kullanılırsa, daima stack segmentin (SS) offset adreslerini gösterir. Fakat BP kaydedicisi de dahil olmak üzere bu adresleme segment kaydedicileri de belirtilerek yapılırsa, o zaman ilgili segment:offset adresine erişilmiş olunur. BX=0000, BP=0001, SI=0002, DI=0003 olduğunu varsayalım; mov mov mov mov mov mov mov mov
al, al, al, al, al, al, al, al,
cs:[bx] ; AL’ye CS:0000’dan kopyalama yapılır [bx] ; AL’ye DS:0000’dan kopyalama yapılır ds:[bp] ; AL’ye DS:0001’den kopyalama yapılır [bp] ; AL’ye SS:0001’den kopyalama yapılır ss:[si] ; AL’ye SS:0002’den kopyalama yapılır [si] ; AL’ye DS:0002’den kopyalama yapılır es:[di] ; AL’ye ES:0003’den kopyalama yapılır [di] ; AL’ye DS:0002’dan kopyalama yapılır
d- Đndeksli adresleme ( Indexed Addressing ) Kaydedici dolaylı adreslemenin operandına sabit bir değer eklenmiş halidir. Kullanım şekli; mov mov mov
al, disp[bx] al, disp[bp] al, disp[si]
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
mov
al, disp[di]
Not : disp kısaltması Đngilizcede displacement kelimesinin karşılığıdır. Buradaki anlamı ise referans alınan ofset adresidir. Komut setlerinde adresleme modları açıklanırken disp veya mem kısaltması ile çok sık karşılaşacağınızdan, komutları yazarken Đngilizce ifadeler kullanmak durumunda kaldım. Mesela BX=2000h olsun, mov dl, 20h[bx] şeklinde bir komut kullanıldığında, DS:2020h adresindeki 1 byte’lık değer dl’ye kopyalanacaktır. Aynı şekilde BP=3030h olduğunu varsayalım, mov dh,1020h[bp] gibi bir komut ile, SS:4050h adresindeki 1 byte’lık değer dh’a kopyalanır. Bu adresleme modunda da BP segment kaydedicisi daima SS’in ofsetlerini gösterir. Fakat kaydedici dolaylı adreslemede de olduğu gibi bizzat segment kaydedicisinide belirterek bu adresleme modunu kullanabiliriz. Bu durumda ofset adresleri komutta belirtilen segmentin ofseti olur. mov al, ss:disp[bx] burada SS’in ofseti olmuş. mov al, es:disp[bp] burada ES’in ofseti olmuş. mov al, cs:disp[si] burada CS’in ofseti olmuş. mov al, ss:disp[di] burada SS’in ofseti olmuş.
; BX normalde DS’nin ofsetlerini gösterirken ; BP normalde SS’nin ofsetlerini gösterirken ; SI normalde DS’nin ofsetlerini gösterirken ; DI normalde DS’nin ofsetlerini gösterirken
e- Taban Đndeksli adresleme ( Based Indexed Addressing ) Bu adresleme modu da kaydedici dolaylı adreslemeye çok benzer. Kullanım formatı aşağıdaki gibidir; mov mov mov mov
al, al, al, al,
[bx][si] [bx][di] [bp][si] [bp][di]
BX’in 0500h SI’nın 0360h olduğunu varsayalım, mov al,[bx][si] gibi bir komut işlenince AL’ye kopyalanacak veri DS:0860 adresinden alınır. Aynı şekilde; BP=1598h DI=1004 ve mov ax,[bp+di] gibi bir komut işleniyorsa; AX, SS:259Ch ve SS:259Dh adreslerindeki veri ile yüklenir. SI ve DI kaydedicileri için Intel’in özel komutları vardır, bu yüzden bu kaydediciler genellikle programlamada indeks değerlerini tutar, arttırır veya azaltırlar. f- Taban Đndeksli artı direkt adresleme (Based Indexed Plus Displacement Addressing) Bu adresleme modu taban indeksli adreslemeye 8 yada 16 bitlik sabit bir değerin eklenmiş halidir.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
mov mov mov mov
al, al, al, al,
disp[bx][si] disp[bx+di] [bp+si+disp] [bp][di][disp]
BP = 1000h, BX= 2000h, SI= 0120h, DI = 5 olduğunu varsayalım. mov al,10h[bx+si] mov ch,125h[bp+di] mov bx,cs:2[bx][di] yüklenir
; AL’ye DS:2130 adresindeki veri yüklenir ; CH’a SS:112A adresindeki veri yüklenir ; CS:2007 adresinden itibaten 2 byte’lık veri
TASM ve MASM ‘ın adresleme modları için esnekliği. TASM ve MASM assembler’ları indeksli, taban indeksli, ve taban indeksli artı direkt adresleme için değişik yazım şekillerini desteklerler; Đndeksli adresleme için; disp[bx] = [bx][disp] = [bx+disp] = [disp][bx] = [disp+bx] Taban indeksli adresleme için; [bx][si] = [bx+si] = [si][bx] = [si+bx] ; Taban Đndeksli artı direkt adresleme için; disp[bx][si] = disp[bx+si] = [disp+bx+si] = [disp+bx][si] = disp[si][bx] = [disp+si][bx] = [disp+si+bx] = [si+disp+bx] = [bx+disp+si] Yukarıda yazılan bu 3 adresleme modundaki operandlar aynı işi yaparlar. MASM ve TASM "[ ]" sembollerine "+" operatörü gibi davranır. ( disp[bx][si] = disp[bx+si] örneğinde olduğu gibi ) 8086 Adresleme Hafıza Adresleme Modlarını Hatırlamak Đçin Kolay Bir Yol: 8086 işlemcisi için toplam 17 adet adresleme modu mevcuttur. Bunlar disp, [bx], [bp], [si], [di], disp[bx], disp[bp], disp[si], disp[di], [bx][si], [bx][di], [bp][si], [bp][di], disp[bx][si], disp [bx][di], disp[bp][si], and disp[bp][di] adresleme modlarıdır. Aşağıdaki şekil, bu 17 adresleme modunu ezberlemeniz yerine kolayca hatırlamanıza yardımcı olacaktır.
Şekil 1 - 8086 adresleme modları için yardımcı şekil.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Bu şekil ile 17 adresleme modunu kolayca görebilirsiniz. Her sütundaki elemanları teker-teker seçebilirsiniz veya herhangi bir sütunu geçip diğer iki sütunun birleşimi ile doğru bir adresleme modu yakalayabilirsiniz. Örnekler; Bir tek eleman seçin; disp, [bx], [bp], [si], [di] 3. sütunu yok sayın ve diğer elemanların birleşimini çıkartın disp[bx], disp[bp] 2. sütunu yok sayın ve diğer elemanların birleşimini çıkartın disp[si], disp[di] 1. sütunu yok sayın ve diğer elemanların birleşimini çıkartın [bx][si], [bx][di], [bp][si], [bp][di] Son olarak tüm sütunların birleşimi ile aşağıdaki geçerli adresleme modlarını kolayca çıkartabilirsiniz. disp[bx][si], disp [bx][di], disp[bp][si], disp[bp][di]. Not:Hafıza adresleme modlarında şayet hesaplanan etkin adres 0FFFFh değerinden büyük olursa, CPU bu hesaplama sonucu oluşan taşmayı göz ardı eder ve FFFFh’e ekleneni 0 dan itibaren ekler. Örneğin BX=10h olsun ve mov al,0FFFFh[bx] komutu işlensin. Bu durunda AL kaydedicisine ds:1000Fh adresindeki veri değil ds:000Fh adresindeki veri yüklenir. (FFFFh+1h=0000, FFFFh+2h=0001,….. FFFFh+16h=000Fh)
Sonuç: 8086 adresleme modlarını 2 bölümde inceledik, ilk olarak kaydedici adresleme modlarını anlatmaya çalıştım. Kaydedici adresleme de segment kaydedicilerini kendi amaçları dışında kullanmanız tavsiye edilmez, bu iş için yeterince genel amaçlı ve indeks kaydedicisi var zaten. Karışık olan kısım ise hafıza adresleme modlarıdır. Hafıza adresleme modlarında belirtilen operand daima bir offset adresini işaret eder, buna etkin adres hesaplama da denir. Hafıza adresleme modlarını unutmamanız için sizlere kolay bir yol göstermeye çalıştım. Adresleme modlarının isimlerinden ziyade kullanım formatları önemlidir. Her komut her adresleme modunu desteklemez. Hangi komutun hangi adresleme modunda kullanılacağı komut setlerinden faydalanılarak bulunur. Bugüne kadar gerek internetten gerekse Türkçe assembly kitaplarından yaptığım araştırmalarda, Türkçe olarak yayınlanmış ayrıntılı bir komut seti bulamadım bu yüzden Đngilizce komut setlerinden faydalanıyorum. “8086 instruction set” veya “x86 instruction set” anahtar kelimelerini internetten aratacak olursanız karşınıza yığınla komut seti gelecektir. Genelde komut seti kılavuzları aşağıdaki tablo gibidirler.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
MOV - Move Byte or Word Usage: MOV dest,src
Operands Clocks
Size Bytes
286 386 486 reg, reg 2 2 1 2 mem, reg 3 2 1 2-4 reg, mem 5 4 1 2-4 mem, 3 2 1 3-6 imm reg, imm 2 2 1 2-3 segreg, 2 2 3 2 reg16 segreg, 5 5 9 2-4 mem16 ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... Tablo 1 - MOV komutu için örnek tablo
Bu tabloda birinci sütun MOV komutu ile kullanılacak operandın hangi adresleme modlarını desteklediğini gösteriyor. Örneğin mem,imm (displacement,immediate) acil adresleme yi desteklediğini gösterir. reg,reg (register,register) kaydedici adreslemenin yapılabileceğini, segreg,reg16 (segment register, register 16 bit ) bir segment kaydedicisine 16 bitlik bir kaydediciden kopyalama işleminin yapılabileceğini gösterir. “Clocks” sütunu bu komutların işlemci tarafından kaç adımda işlendiğini (işlemcinin tipine ve hızına göre bu zaman birimi değişebilir), “size bytes” sütunu ise hafızada makine kodlarının byte olarak kapladığı alanı gösterir. Çoğu zaman assembly programcıları programlarının, işlenme sürecindeki nano saniyeleri ve hafızada kapladığı alanını byte düzeyinde önemi yoksa bu kısımlarla ilgilenmezler. Yani 1. derecede “operands” sütunu önemlidir ve ilgili komutu bu sütunda belirtilen formatların dışında kullanamazsınız. Son olarak x86 uyumlu hiçbir işlemci mem,mem şeklinde yani hafızadan hafızaya doğrudan bir adresleme modunu desteklemez, aslında bu güne kadar bunu destekleyen hiç bir işlemci görmedim, zaten böyle bir adresleme modunun desteklenmesi durumunda o sistem için yapılan programların çökme olasılığı çok büyüktür.
80386 ADRESLEME MODLARI 80386 Kaydedici Adresleme (80386 Register Addressing) 80386 den itibaren, günümüzde kullandığımız Pentium işlemcilerin 32 bitlik kaydedicileri vardır. 8086 adresleme modlarına ek olarak bu işlemcilere de kaydedici adresleme yaparken bu 32 bitlik kaydedicileri de kullanabilirsiniz. Bu kaydediciler geçen makalemizde bahsettiğimiz; eax, ebx, ecx, edx, esi, edi, ebp, ve esp kaydedicileridir.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
kullanım formatı; mov
eax, ebx
Not: Şayet programınızda 32 bitlik kaydedicileri kullanacaksanız kaynak dosyası hazırlarken .386 talimatını kullanmanız gerekir.
80386 Hafıza Adresleme (80386 Memory Addressing) a- 80386 Kaydedici Dolaylı Adresleme ( Register Indirect Addressing ) 16 bitlik Gerçek moda (Real Mode) 80386 ve üstü işlemcilerde programlama yapmanız offset adreslerinin 32 bit olacağı anlamına gelmez. Segmentlerin her zaman gerçek moda 64Kb. olduğunu unutmamak gerekir, bu yüzden offset adresleri gerçek modda 0...0FFFFh arasını gösterir. Yani 64Kb.’ı geçecek ofset adreslerini gerçek moda değil ancak korumalı moda (Protected mode) kullanabilirsiniz. Fakat gerçek moda 32 bitlik kaydedicileri hesaplamalarda kullanmanızda bir engel yoktur. 80386 kaydedici dolaylı adreslemede kullanabileceğiniz tüm geçerli adresleme formatı aşağıdaki gibidir. mov mov mov mov mov mov mov mov
al, al, al, al, al, al, al, al,
[eax] [ebx] [ecx] [edx] [esi] [edi] [ebp] [esp]
; DS in ofsetleri için ; DS in ofsetleri için ; DS in ofsetleri için ; DS in ofsetleri için ; DS in ofsetleri için ; DS in ofsetleri için ; SS in ofsetleri için ; SS in ofsetleri için
b- 80386 Đndeksli, Taban/Đndeksli, ve Taban/Đndeksli/Direkt Adresleme modları ( 80386 Indexed, Base/Indexed, and Base/Indexed/Disp Addressing ) 80386 Đndeksli adresleme modu 32 bitlik bir kaydedici ile sabit bir değerin birleşiminden meydana gelir. Taban indeksli adresleme modu iki adet 32 bitlik kaydedicinin birleşiminden ve taban indeksli artı direkt adresleme modu ise bir sabit değer ile iki tane 32 bitlik kaydedicinin birleşiminden meydana gelir. Hiçbir zaman unutmamalısınız ki bu adresleme modları ne kadar 32 bitlik olsa da, şayet 16 bitlik gerçek moda kullanılırlarsa erişebilecekleri ofset adresleri 16 bit’i geçemez. 80386 taban indeksli hafıza adresleme modlarında taban adres ve indeks adresi diye iki tane terim kullanılır. Taban adresini ilk operand iken indeks adresini son operand gösterir. Bu kural kaydedici ismi ne olursa olsun geçerlidir. Fakat 8086 indeksli veya taban indeksli hafıza adresleme modlarında, taban adresini sadece BX veya sabit bir değer gösterebilirken, indeks adreslerinide SI, DI, BP ve BX gösterebiliyordu. 80386 ve sonrası işlemcilerde bulunan bu esneklik şüphesiz ki programcıların işini kolaylaştırır. Aşağıda 80386 indeksli hafıza adresleme modları için birkaç örnek görülüyor; mov mov mov mov
al, al, al, al,
disp[eax] [ebx+disp] [ecx][disp] disp[edx]
;Đndeksli adresleme ;modları.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
mov mov mov mov
al, al, al, al,
disp[esi] disp[edi] disp[ebp] disp[esp]
;SS in ofsetleri için ;için.
Aşağıdaki örneklerde ise 80386 taban indeksli adresleme modları için çeşitli örnekler görünüyor. Bu örneklerde ilk kaydedici taban adresini gösterirken, ikinci kaydedici ise indeks adresini gösterir. Tabi ki sonuçta bu iki kaydedicinin içindeki değerler toplanarak etkin olan hafıza adresini işaret edecekler. Kaydedicilerin 32 bitlik olduğunu düşünecek olursanız 4GB’lık hafızanın istediğiniz bir konumuna erişebilineceğini de görebilirsiniz (tabi ki korumalı modda). Burada taban adresi olarak esp ve ebp kullanıldığında SS’deki bir ofset adresini göstereceğini unutmayın. Bununla birlikte indeks adreslerini gösteren kaydedicilerin türü verinin hangi segmentten alınacağına etkisi olmaz. mov mov mov mov mov mov mov mov
al, al, al, al, al, al, al, al,
[eax][ebx] [ebx+ebx] [ecx][edx] [edx][ebp] [esi][edi] [edi][esi] [ebp+ebx] [esp][ecx]
;Taban indeksli adresleme ;modları. ;DS deki ofsetleri gösterirler.
;SS deki ofsetleri gösterirler. ;SS deki ofsetleri gösterirler.
Yukarıdaki adresleme modlarına sabit bir değer eklerseniz 80386 taban indeksli artı direkt adresleme yapmış olursunuz. mov mov mov mov mov mov mov mov
al, al, al, al, al, al, al, al,
disp[eax][ebx] disp[ebx+ebx] [ecx+edx+disp] disp[edx+ebp] [esi][edi][disp] [edi][disp][esi] disp[ebp+ebx] [esp+ecx][disp]
;Taban indeksli artı direkt adresleme ;modları. ;DS deki ofsetleri gösterirler.
;SS deki ofsetleri gösterirler. ;SS deki ofsetleri gösterirler.
80386 indeksli adresleme modlarında sadece tek bir kısıtlama söz konusudur; esp kaydedicisini indeks kaydedicisi olarak kullanamazsınız ama esp nin taban kaydedicisi olarak kullanılmasının bir kısıtlaması yoktur.
c- 80386 Ölçekli Đndeksli Adresleme Modu (80386 Scaled Indexed Addressing Modes) Programcılıkla az çok uğraşan arkadaşlar diziler konusu bilirler. Yukarıda anlattığım 3 adresleme modu ile dizilerin elemanlarına rahatça erişebilirsiniz. Fakat özellikle dizi işlemler için bir adresleme modu arıyorsanız 80386 ölçekli indeksli adresleme modunu kullanmak daha akıllıca olur. Bu adresleme modu ile indeks kaydedicisini 1, 2, 4 veya 8 ile çarparak dizilerin elemanlarına erişmede daha esnek bir yapı sağlar. Kullanımı; disp[index*n] [base][index*n]
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
veya disp[base][index*n] "base" ve "index" 80386’nın herhangi bir genel amaçlı kaydedicisi olabilir. "n" ise 1, 2, 4 ve 8 değerlerini alabilir. ebx = 1000h ve esi = 4, için aşağıdaki örnekleri inceleyelim; mov mov mov
al,8[ebx][esi*4] ;ds:1018h daki veriyi dan AL’ye kopyalar. al,1000h[ebx][ebx*2] ;ds:4000h daki veriyi dan AL’ye kopyalar. al,1000h[esi*8] ;ds:1020h deki veriyi dan AL’ye kopyalar.
80386 Đndeksli, Taban/Đndeksli, ve Taban/Đndeksli/Direkt Adresleme modlarını da 80386 Ölçekli Đndeksli Adresleme Modunun n değerinin 1 olduğu adresleme modları olarak düşünebilirsiniz mov mov mov
al, 2[ebx][esi*1] = mov al, 2[ebx][esi] al, [ebx][esi*1] = mov al, [ebx][esi] al, 2[esi*1] = mov al, 2[esi]
MASM ve TASM 80386’nın tüm bu hafıza adresleme modlarının yazımında değişik varyasyonları kabul eder. Aşağıdaki tüm operandlar aynı işi yaparlar. disp[bx][si*2], [bx+disp][si*2], [bx+si*2+disp], [si*2+bx][disp], disp[si*2][bx], [si*2+disp][bx], [disp+bx][si*2]
Sonuç: 80386 adresleme modları 8086 adresleme modlarına nazaran programcıya daha çok olanak sağladığından daha çok tercih edilirler. Benim şahsi görüşüm X86 uyumlu PC’ler gerçek gücünü 80386 işlemcisi ile birlikte gelen bu olanaklardan almışlardır. Zaten CPU tarihinde 80386 bir devrin kapanıp diğer bir devrin başladığı nokta olarak kabul edilir. Ne var ki 8086 adresleme modları 80386 adresleme modlarına nazaran daha hızlı çalışırlar. Bu yüzden söz konusu olan hız ise (mikro saniyelerden bahsediyoruz ki bunlar bazen birleşip dakikalar oluyor) adresleme modu seçilirken çok dikkat edilmesi gerekir. 80386 taban indeksli ve taban indeksli artı direkt adresleme modları, ölçeği 1 olan (n=1) 80386 Ölçekli Đndeksli Adresleme Modu olarak düşünülebilir ve bu hafıza adresleme modlarında gösterilen ilk kaydedici taban ikinci kaydedici indeks adresini gösterir. Taban adresini gösteren kaydedici ebp veya esp ise bu adresleme SS’deki bir ofset adresine diğer kaydediciler taban adresi olarak seçildiğinde DS deki bir ofset adreslenmiş olunur. Đlk yazılan kaydedici ölçekli olarak gösterilirse ("*n") bu bir taban adresi olmaktan çıkar bir indeks adresi olur. Ayrıca operanda segment kaydedicisini de göstererek adresleme modunu zorlayabilirsiniz.
[ebx][ebp] [ebp][ebx] [ebp*1][ebx] [ebx][ebp*1] [ebp][ebx*1]
;DS’nin ofsetini gösterir. ;SS’nin ofsetini gösterir. ;DS’nin ofsetini gösterir. ;DS’nin ofsetini gösterir. ;SS’nin ofsetini gösterir.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
[ebx*1][ebp] es:[ebx][ebp*1]
;SS’nin ofsetini gösterir. ;Zorlanmış,ES’nin ofsetini gösterir.
Şimdi sıra uygulamada. Bu uygulama ile hem adresleme modlarını hem de Turbo Debugger programının kullanımını açıklamaya çalışacağım. TITLE Adresleme Modları (admod.asm) ;######################################## ;# Bu program 8086 ve 80386 adresleme modlarının # ;# iyi kavranması için yazılmıştır. # ;# Son Güncelleme: 17/04/05 # ;# Yazan --> Eren ERENER # ;######################################## .MODEL .STACK .DATA
SMALL 32
VerilerByte DB 5h, 17h, 8Dh, 0AFh VerilerWord DW 1234h, 7h, 0C01Dh VerilerDoubleWord DD 3DF0178Ah, 11223344h, 12345678h .CODE ANA
PROC
MOV MOV
AX, @DATA DS, AX
MOV MOV
AX, 5566h BX, AX
; Data segment ; ayarlanıyor.
; Acil adresleme. ; Kaydedici adresleme.
;---------------------------------------;8086 Hafıza Adresleme Modlarına Örnekler ;---------------------------------------MOV
DX, DS:0000h
; Direkt adresleme.
MOV BX, 0000h MOV AL, [BX] ; Kaydedici dolaylı adresleme, DS:0000h adresindeki 5h değeri AL’ye kopyalanır. MOV AL, 3[BX] ; Đndeksli adresleme, DS:0003h adresindeki AFh AL’ye yüklenir MOV AL, 3[VerilerByte] ; Yukarıdaki ile aynı işi yapar, ; zaten assembler VerilerByte değişken ismini ds:0000h olarak çevirecektir.
MOV SI, 0 MOV AL, [BX][SI] değer DS:0000h’daki 05h’tır. INC SI
; SI = 0 oldu. ; zaten BX=0’dı, şu anda AL’ye kopyalanacak ; SI’yı 1 arttırdık,
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
MOV DS:0001h’daki INC INC MOV DS:0003h’daki
AL, [BX][SI] 17h’tır. SI SI AL, [BX][SI] AFh’tır.
; şimdi AL’ye kopyalanacak değer ; SI’yı, ; 2 arttırdık, şimdi SI=3 oldu. ; şimdi AL’ye kopyalanacak değer
; Burada BX taban SI indeks kaydedicisi oldu. MOV AL, [BP][SI] ; DS değil SS ’in offsetlerini adresler, şu anda Stack Segment’in içinde ne olduğunu bilmiyoruz!!! ;Taban indeksli artı direkt adresleme MOV SI, 0 ; SI=0 oldu. MOV AX, 4[bx+si] ; BX’i değiştirmediğimizden hala sıfır ve AX’e getirilecek 2 byte’lık veri ds:0004h adresinden alınacak, ; çünkü 4+0+0 = 4’tür. Bu adres VerilerWord değişkenin adresidir ve bu adreste 1234h vardır. INC SI ; SI, INC SI ; 2 kere arttırılıyor ve, MOV AX, 4[bx+si] ; VerilerWord dizisinin 2. elemanına erişiliyor. ; çünkü 4+0+2 = 6’dır. Bu adreste 0007h vardır. ;---------------------------------------;80386 Hafıza Adresleme Modlarına Örnekler ;---------------------------------------.386 ; 80386 adresleme modlarını ve 32 bitlik kaydedicileri kullanmak için bu direktifin yazılması gerekir!!! MOV ESI, 0 SUB EBX, EBX çıkartma komutu,
; Đndeks’i 1 olarak ayarlandı. ; ebx = 0 yapıldı. SUB (Subtract) yani ; burada "MOV ebx, 0" ile aynı işi fakat daha
hızlı yapar. MOV ile VerilerWord INC MOV ile VerilerWord
AX, 4[ebx][esi*2] ; 80386 ölçekli indeksli adresleme modu dizisinin 1. elemanı (1234h) AX’e kopyalandı. ESI AX, 4[ebx][esi*2] ; 80386 ölçekli indeksli adresleme modu dizisinin 2. elemanı (0007h) AX’e kopyalandı.
LEA EAX, VerilerDoubleWord ; LEA komutu (Load Effective Address) VerilerDoubleWord dizisinin başlangıç adresini EAX’e kopyalıyor, ; 80386 kaydedici adresleme SUB EBX, EBX ; EBX = 0 oldu. ; 80386 taban indeksli adresleme. MOV ECX, [EBX][EAX] ; ve bu dizideki ilk eleman (3DF0178Ah) ECX’e kopyalanıyor. MOV INT
AH,4CH 21H
ANA
ENDP
;DOS’a ;dönüş
END ANA
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Yukarıdaki program sadece 8086 ve 80386 adresleme modlarına örnek olması için hazırlanmıştır. Hafızadaki veriler nasıl erişilir sorusuna yanıt arayanların bu programı dikkatle ve satır-satır incelemesini tavsiye ederim. Sıra bu programı derledikten sonra Turbo Debugger ile açıp hafızada ve işlemcide hangi olayların olduğunu incelemeye geldi. Turbo Debugger programının 16 ve 32 bitlik hafızayı incelemek için iki ayrı versiyonu var şu anda gerçek modda çalışan programlar hazırladığımızdan dolayı 16 bitlik sürümünü kullanacağız. Programı buradan indirebilirsiniz. Đndireceğiniz bu dosya Turbo Debugger, Turbo Assembler’ı, Turbo Linker paketini içermekte. Ayrıca bu pakete, içinizde belki hala DOS veya Windows 95 kullanan vardır diye bir mouse programı ekledim. Şayet durum böyleyse önce konsola "mouse" yazdıktan sonra Turbo Debugger’ı çalıştırın. Turbo Debugger Microsoft’un debug’ı ile aynı işi yapar fakat daha kullanışlı bir kullanıcı ara yüzüne sahiptir. Turbo Debugger ile program dosyalarınızı açtıktan sonra bir çok işlem yapabilirsiniz. Not: Turbo Debugger’ı pencere modunda çalıştırırsanız GDI (Graphics Device Interface) kaynaklarını sömürürcesine kullandığından bilgisayarınız yavaşlayabilir. Bu yüzden tam ekran modunda başlatmanızı öneririm. Programı pencere modunda başlattıktan sonra konsol penceresinin ayarlarından tam ekran yapmanızın da bir faydası olmaz. Bu yüzden önce komut istemini açın, sonra tam ekran yapın ve Turbo Debugger’i çalıştırın. Turbo Debugger kullanımına ilişkin birkaç ipucu: Đlk olarak konsola "td" yazarak programı çalıştırın. Turbo Debugger’ın ekranına ulaşacaksınız.
Şekil 2 - Turbo Debugger ekranı.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Turbo Debugger ekranı 5 bölümden oluşuyor. Şekildeki 1, 2 ve 3 nolu kısımlar hafızayı incelemek için kullanılırlar. 4. kısım mikroişlemci kaydedicilerini ve 5. kısımdada yine mikroişlemci kaydedicilerinden olan flag register’ı bitsel olarak görebilirsiniz. Genellikle 1. bölge programı oluşturan kodları, 2. bölgede verilerin incelenmesi için kullanılır. 3. bölgeyi yığın hafızayı gözlemlemek için kullanabilirsiniz. Turbo Debugger açıldıktan sonra "File" menüsünden derlediğiniz program dosyasını açıp kodlarını inceleyebilirsiniz. Program dosyanızı açtıktan sonra "Program has no symbol table" diye bir mesaj gelebilir. Bu mesaj penceresinde "OK" e tıklayıp çalışmaya başlayabilirsiniz.
Şekil 3 - Turbo Debugger ile dosya açma.
Ayrıca menülerden sırasıyla View/Another/File seçeneklerini tıklayarak başka bir dosyayı diğer bir pencerede açabilirsiniz. (Mesela kaynak dosyasını)
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Şekil 4 - Turbo Debugger ile aynı ekranda kaynak dosyanızı da görebilirsiniz.
Şayet bu pencerede yazı yerine hex kodları görürseniz, pencerenin üzerine sağ tıklayıp açılan menüden "Display As" ı seçin. Programı çalıştırma seçeneklerini "Run" menüsünden seçebilirsiniz. F8 tuşu programı adım adım işletmek için kullanılır, F7 ise adım-adım program işlenirken döngüleri incelemenizi sağlar. Program adım-adım işlenirken mavi bir çubuk o anda hangi komutun işleneceğini gösterir. Aynı ekranda işlemcinin kaydedicilerinin nasıl değiştiğini gözlemleyebilirsiniz. Değişikliğe uğrayan kaydediciler beyaz görünür. Programın tekrar çalıştırmak için "Run" menüsünden "Program Reset" seçeneğini seçebilirsiniz.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Şekil 2 - Program adım-adım işleniyor.
Verilerinizi görmek için 2. pencereye sağ tıklayıp "goto" yu seçin. Açılan pencereye görmek istediğiniz adresi yazın.
Şekil 2 - Turbo Debugger ile hafıza pencerelerini kullanma.
Yalnız .exe tipindeki program dosyalarında verilerinizi görebilmek için mov ax,@data ve mov ds,ax satırlarının işlenmiş olması gerekir. Programınız satır-satır işlenme sürecinde, adresleme yapılan bir komutun işlenmesi anında kod penceresinin sağ üst köşesinde hafızanın hangi bölgesine erişildiği görülebilir. Bu makalemizdeki programı incelerken bu özelliği sıkça kullanmanız gerekecek.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Şekil 2 - Turbo Debugger ile adresleme modlarının işlevleri rahatça görülebilir.
Mesela yukarıdaki şekilde mov al, [BX] komutu işleniyor ve kaydedici dolaylı hafıza adresleme yapılıyor. Burada AL kaydedicisine kopyalanacak olan veriyi "ds:0000 = 05" şeklinde görebilirsiniz. 32 bitlik kaydedicileri görmek içinse kaydedicileri gösteren kısıma (4. kısım) sağ tıklayıp "Registers 32 bit" i seçebilirsiniz. Turbo Debugger’ın daha bir çok özelliği var ama şu anda bizlere bu kadarı yeter. Şimdi sizler Turbo Debugger ile bu makaledeki programı inceleyerek pratik yapın, ayrıca bu güne kadar yazdığımız programları da Turbo Debugger ile inceleyerek programı kullanmasını daha iyi kavrayın. Bir sonraki makalemizde veri tipleri ve x86 komutlarını inceleyeceğiz. Daha sonra prosedür ve makrolarıda gördükten sonra nihayet kendi programlarınızı yazabilir seviyeye geleceksiniz. Genelde sizlerden makalelerin daha sık aralıklarla yayınlanması ile ilgili mailler alıyorum. Fakat bir makale hazırlamak en az 1 haftamı alıyor ve üniversitedeki derslerden ve işlerden dolayı çok az vaktim oluyor. Bu yüzden makalelerimin yayınlanma aralığı birden 20 güne sıçrayabiliyor. Hal böyleyken sizlerin arayı soğutmamak için başka kaynaklardan faydalanmasını tavsiye ediyorum. Bu şekilde çalışan bazı arkadaşlar internetten kaynak kod bulup, derleyip takıldıkları yerlerde bana mail atıyorlar, bunlara cevap vermesi daha az zamanımı alıyor ve sizler içinde çok faydalı olacağından eminim. Şayet Đngilizce okuduğunuzu anlıyorsanız sizlere Art of Assembly’yi tavsiye ederim. Đnternetten okuyabileceğiniz bedava bir kitap. Mesela bu makaleyi yazarken bu kaynaktan çok yararlandım ve sizlere de tavsiye ederim
80x86 KOMUT SETĐ (Bölüm 1) X86 tabanlı mikroişlemcilerin icra ettiği makine kodları sabit olmasına rağmen, programlama dillerinin komut ve ifadeleri farklı olabilir. Assembly programlama dilininde diğer programlama dillerinde olduğu gibi bir dizi komutu vardır. Bu komutlar genelde mnemonik’ler (nivmonik diye okunur) şeklindedir. Örneğin LEA mnemoniği Load Effective Adres kelimelerinin kısaltılmış şeklidir. Bu makalemizde x86 Assembly programlama dilinin komutlarını anlatmaya çalışacağım ve bu makalede açıklanan komutları öğrendiğinizde kendi başınıza program yazabilir hale geleceksiniz. Aslında 80386 ve sonrası mikroişlemciler için daha birçok komut mevcuttur ve bu komutlar assembly dilinde program yazma işini kolaylaştırır. Bu komutları ilerleyen makalelerimizde açıklamaya çalışacağım. 80x86 komutları genelde 8 grup altında incelenir. 1) Veri taşıma komutları mov, lea, les , push, pop, pushf, popf 2) Dönüştürme komutları cbw, cwd, xlat 3) Aritmetic komutlar
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
add, inc sub, dec, cmp, neg, mul, imul, div, idiv 4) Mantıksal, kaydırma, çevirme ve bitsel işlemler için komutlar and, or, xor, not, shl, shr, rcl, rcr 5) I/O (Giriş/Çıkış) komutları in, out 6) Karakter dizi (String) komutları movs, stos, lods 7) Program akış kontrol komutları jmp, call, ret, Jxx (şartlı dallanma komutları) 8) Diğer komutlar clc, stc, cmc Veri Taşıma Komutları Veri taşıma komutları bir değeri bir yerden başka bir yere taşımaya yarar. mov, xchg, lds, lea, les, lfs, lgs, lss, push, pusha, pushad, pushf, pushfd, pop, popa, popad, popf, popfd, lahf, ve sahf komutları veri taşıma komutlarıdır. MOV komutu Bu komutun kullanım şekilleri aşağıdaki gibidir. mov mov mov mov mov mov mov mov mov mov mov
reg, reg mem, reg reg, mem mem, immediate data reg, immediate data ax/al, mem mem, ax/al segreg, mem16 segreg, reg16 mem16, segreg reg16, segreg
MOV komutu assembly dilinde çok kullanılan bir komuttur. Yukarıdaki kullanım şekilleri için Đngilizce ifadeler kullanılmıştır, çünkü internetten erişebileceğiniz komut seti referanslarında hep bunlar karşınıza çıkacak. Tüm komutlar için geçerli olan bu Đngilizce ifadelerin Türkçe karşılıkları aşağıdaki gibidir.
reg : register à kaydedici, mem : memory à hafıza (RAM-ROM veya Giriş/Çıkış portları olabilir) immediate data à acil adresleme ile kullanılan direkt veri
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
segreg : segment register à segment kaydedicisi mem16 : memory 16 bit à 16 bitlik hafıza alanı (dw direktifi ile tanımlanan veriler) reg16 : register 16 bit à 16 bitlik kaydedici (AX, BX .. gibi)
MOV komutunu kullanırken yapamayacağınız iki şey vardır, bunlardan birincisi “mem, mem” tipinde bir kullanımdır. Yani hafızanın bir konumunda diğer bir konumuna doğrudan taşıma yapamazsınız. Bu işlemi yapmak için taşınacak veri önce mikroişlemci kaydedicilerinden birine getirilmelidir. MOV sayi1, sayi2
;yanlış kullanım
Yukarıdaki gibi bir komut satırı yazarsanız, derleyiciniz hata mesajı verir. Böyle bir işlemi yapmak için genel amaçlı bir kaydediciyi kullanmanız gerekir. MOV MOV
AX, sayi1 sayi2, AX
;sayi1 ve sayi2 değişkenlerinin word türünden olduğunu varsayıyoruz.
MOV komutu ile yapamayacağınız ikinci şey ise segment kaydedicilerine doğrudan bir veri taşımaktır. Yani acil adresleme modunu segment kaydedicilerine uygulayamazsınız. MOV
DS, 1525h ; bu kullanım hatalıdır.
Segment kaydedicilerine bir değer yükleyebilmek için genellikle genel amaçlı kaydedicileri kullanılır. Ayrıca segment kaydedicilerine ancak 16 bitlik boyutunda değerler yüklenebileceğinden genel amaçlı kaydedicilerin 8 bitlik kısımlar değil 16 bitlik kısımları kullanılabilir. MOV MOV
AX, 1525h DS, AX
Bunların dışında operandların boyutları eşit olmak zorundadır. MOV AX, toplam ; burada toplam değişkeninin boyutu kesinlikle word tipinde yani iki byte uzunluğunda olmalıdır. Şayet acil adresleme kullanarak bir veri taşıyorsanız işlemci operandın boyutunu kaydediciye uyarlar. MOV
AX, 15h ; Bu komut işlenince AX’in içinde 0015h değerini görürsünüz.
Dikkat edilemesi gereken diğer bir husus ise hafıza operandlarıdır. Örneğin MOV [BX], 5 gibi bir komut ile hafızaya neyin yükleneceği belli değildir (Burada BX kaydedicisine değil hafızaya taşıma yapıldığına dikkat edin) MOV [BX], 5 gibi bir komutla acaba hafızaya byte boyutunda bir 5 değerimi (05) yoksa word boyutunda bir 5 değerimi (0005) yüklenecek? Bunu kodlarınızda belirtmeniz gerekir. Derleyici bu komut satırına hata verir. Doğru kullanım aşağıdaki gibi olmalıdır. mov mov mov
byte ptr [bx], 5 word ptr [bx], 5 dword ptr [bx], 5
(*)
(*) 80386 ve sonrası işlemcilerde kullanılabilir
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
mov byte ptr [bx], 5 satırını açıklayalım. Burada BX=0000 olduğunu varsayalım, böyle bir durumda ds:0000 adresine bir taşıma işlemi gerçekleşeceltir. Ama bu adresten itibaren 05’mi yoksa 0005’mi yoksa 00000005’mi taşınacaktır? Đşte bunu ptr operatörü belirler. mov byte ptr [bx], 5 komut satırı için ds:0000 adresine 1 byte’lık bir veri yani 05 taşınır. Şayet operatör byte ptr değilde word ptr olsaydı o zaman ds:0000 ve ds:0001 adreslerine dırasıyla 05 ve 00 değerleri taşınacaktı. XCHG komutu xchg (exchange) komutu operandlarındaki değerleri yer değiştirir. 80x86 ailesi için dört değişik kullanım şekli vardır; xchg xchg xchg xchg
reg, mem reg, reg ax, reg16 eax, reg32
(*)
(*) 80386 ve sonrası işlemcilerde kullanılabilir LDS, LES, LFS, LGS, ve LSS komutları Bu komutlar 32 bitlik bir hafıza bölgesindeki değeri bir segment kaydedicisine ve bir genel amaçlı kaydediciye bir defada yükler. Kullanım formatı aşağıdaki gibidir; LxS
hedef, kaynak
Bu komutları aşağıdaki gibi kullanabilirsiniz; lds les lfs lgs lss
reg16, mem32 reg16, mem32 reg16, mem32 reg16, mem32 reg16, mem32
(*) (*) (*)
(*) 80386 ve sonrası işlemcilerde kullanılabilir Reg16 genel amaçlı herhangi bir kaydedici olabilir mem32 ise double word boyutunda bir veri olmalıdır, bunu “dd” direktifi ile oluşturabilirsiniz. Daha önce bu komutlardan biri olan LES komutu için “X86 Assembly Dilinde Değişken Bildirimi -1” adlı makalede çok güzel bir örnek vermiştim. Erişmek istediğimiz adresin segment ve ofset bölümlerini birleştirerek bir değişken oluşturuyor sonrada bunu program çalışırken istediğimiz gibi kullanıyorduk. LEA Komutu LEA (Load Effective Address – Etkin Adresi Yükle) sadece offset adreslerini hedef operandına yükleyen bir pointer gibi düşünebilirsiniz. Genel kullanım formatı lea
dest, source
şeklindedir. lea
reg16, mem
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
lea
reg32, mem
(*)
(*) 80386 ve sonrası işlemcilerde kullanılabilir. MOV komutunu da LEA komutu yerine kullanabilirsiniz, fakat hangi komutu kullanacağınıza adresleme moduna göre seçmelisiniz. Bazen MOV komutu LEA dan daha hızlı çalışabilir, tüm bu bilgilere herhangi bir intel komut setinden faydalanarak bakabilirsiniz. Daha önceki makalelerimizde ekrana bir karakter dizisini yazdırmıştık, bunun için kaynak kodumuzda ekrana yazdılıralacak olan veriyi aşağıdaki gibi tanımlamıştık; Dizi
DB "Merhaba Assembly",0Ah,0Dh,24h
Daha sonra bu dizinin adresini DX kaydedicisine yüklememiz gerektiğinde aşağıdaki komutu kullanmıştık; MOV DX,OFFSET Dizi Bu komutun yaptığı işi LEA kullanarakta yapabiliriz; LEA
DX, Dizi
PUSH ve POP komutları 80x86 push ve pop komutları Stack Memory (Yığın hafıza bölgesi) ile ilgili işlemlerde kullanılır. Yğın hafıza bölgesini sizler .exe tipi program hazırlarken .Stack direktifi ile belirliyorsunuz. Đşte bu bölge genellikle programdaki dallanma veya altrutinlerin çalışması sırasında, dönüş adreslerinin ve bayrak kaydedicisinin durumlarını saklamak için kullanılır. Push komutu bu yığın olarak adlandırılan hafıza bölgesine verileri iterken, pop komutuda bu bölgeden veri almada kullanılır. push reg16 pop reg16 push reg32 (**) pop reg32 (**) push segreg pop segreg (CS hariç) push memory pop memory push immediate_data (*) pusha (*) popa (*) pushad (**) popad (**) pushf popf pushfd (**) popfd (**) enter imm, imm (*) leave (*) (*) 80286 ve sonrası işlemcilerde kullanılabilir (**) 80386 ve sonrası işlemcilerde kullanılabilir
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Push ve pop komutları kullanıldığında yığın hafıza bölgesinin işaretçisi olan SP kaydedicisi değişir. Tabiî ki bu yığına itilen veya yığından çekilen değerin boyutuna bağlıdır. Bu komutlar 2 veya 4 byte’lık değerler ile işlem yaptığından yığına 2 bytelık bir değer itildiğinde (mesela bu AX kaydedicisinin içeriği olabilir) SP’nin değeri 2 byte azalır. Şayet yığına 4 byte’lık değer itilirse SP’nin değeri 4 azalır. Unutulmaması gereken önemli bir hususta yığın hafıza bölgesine itilen en son değerin çekilecek olan ilk değer olmasıdır. Tabiî ki yığına birden fazla word ya da doubleword itildiyse aralardaki değerler ile işlem yapmak adresleme modlarıyla mümkündür fakat bu SP’de herhangibir değişiklik yapmaz. Yığın hafıza bölgesi ile ilgili unutulmaması gereken üç önemli kural vardır. Segment kaydedicilerinden olan SS yığın hafıza bölgesinin segment adresini gösterir. Yığına bir şeyler itildikçe SP azalır eökildikçe artar. SS:SP her zaman yığının tepesi olarak tabir edilen noktayı gösterir. LAHF ve SAHF Komutları Bu komutlar bayrakları AH kaydedicisine yükler veya AH’a yüklenen bayrak kaydedicilerinin durumlarını kayar nokta kaydedicisine ( floating point register ) yükler. Bu komutlar 8086 zamanından kalma ve günümüzdeki modern assembly programlarında pek kullanılmayan komutlardır. Genişletme Đşlemleri Bazen byte boyutundaki bir değeri word boyutuna veya word boyutundaki bir değeri doubleword boyutuna genişletmek gerekebilir. Bu gibi durunlarda aşağıdaki komutlar kullanılır. movzx
hedef, kaynak
;Hedef kaynağın iki katı büyüklüğünde
movsx
hedef, kaynak
;Hedef kaynağın iki katı büyüklüğünde
olmalıdır. olmalıdır. cbw cwd cwde cdq bswap xlat
reg32
MOVZX, MOVSX, CBW, CWD, CWDE, ve CDQ Komutları cbw (convert byte to word) AL kaydedicisinin 1 byte’lık içeriğini AX’e genişletir. Şayet AL’deki değer pozitifse AH’ın tüm bitleri ‘0’ değerini alır. AL’deki değer negatifse AH’ın tüm bitleri ‘1’ olur. cbw cwd (convert word to double word) komutu AX’in değerini DX:AX’e genişletir. CBW komutundaki kurallar bu komut içinde geçerlidir. cwd Bu komut 80386 ve sonrası işlemcilere özeldir. CWD komutunda olduğu gibi word boyutundaki bir değeri double word boyutuna genişletmede kullanılır. CWD AX’i DX:AX’e genişletirken bu komut AX’i EAX’e genişletir.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
cwde cdq komutu EAX kaydedicisindeki 32 bit’lik değeri EDX:EAX ‘e genişletir. Bu komutda 80386 ve sonrası işlemlerde kullanılır. cdq Örnekler: ; AL’ deki 8 bitlik değeri 32 bitlik dx:ax’e genişletmek için cbw cwd ; AL’ deki 8 bitlik değeri 32 bitlik eax’e genişletmek için cbw cwde ; AL’ deki 8 bitlik değeri 64 bitlik edx:eax’e genişletmek için cbw cwde cdq movsx komutuda yukarıdaki komutlara benzer iş yapar, kullanım formatları aşağıdaki gibidir. movsx movsx movsx movsx movsx movsx
reg16, reg16, reg32, reg32, reg32, reg32,
mem8 reg8 mem8 reg8 mem16 reg16
movsx movsx movsx
ax, al eax, ax eax, al
Örnekler: ;CBW komutunun yaptığı işi yapar. ;CWDE komutunun yaptığı işi yapar. ;CBW ve CWDE komutlarının birlikte yaptığı işi
yapar. movzx komutu movsx komutu gibi kullanılır fakat negatif değerleri genişletmek için kullanılmaz, çünkü movzx komutu genişletme işleminde sadece bitleri ‘0’ yapabilir. Bu komutun sonundaki zx harfleri Đngilizcede zero extend yani sıfır ile genişlet gibi bir anlam taşır. Tüm bu genişletme komutları genellikle aritmetik işlemlerde ve özellikle bölme komutlarında kullanılır. BSWAP Komutu Bildiğiniz gibi x86 hafızası little endian yapıya sahiptir, bununla beraber big endian hafızaya sahip bilgisayarlarda çok sayıda mevcuttur. Örneğin Apple’ın Machintosh bilgisayarları big andian hafıza yapısına sahiptir. BSWAP komutu bu farklı hafıza sistemlerine sahip olan bilgisayarlar arasında veri haberleşmesi
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
yapılması için kullanılır. BSWAP operandında belirtilen 32 bitlik kaydedicinin içindeki değeri byte-byte ters çevirir. Düşük değerlikli sekiz biri en yüksek değerlikli bölgeye, 8-15 arasındak bitleri 16-23 arasına, 16-23 arasındaki bitleri 8-15 arasına ve son olarak 24-31 arasındaki bitleride 0-7 arasına yerleştirir. BU komut sadece 80486 ve sonrası işlemcilerde kullanılabilir. Kullanım formatı aşağıdaki gibidir. bswap reg32 XLAT Komutu Genellikle tablo olarak tasarlanan dizilere erişmek için kullanılır. AL kaydedicisine tablonun elemanlarından birini yükler. Bu komutu aşağıdaki örneğe bakarak daha iyi anlayabilirsiniz. Tablo DB
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F
Bu tablonun 11. elemanını AL’ye yüklemek istersek; MOV AL, 0Ah ; Đndeks değeri (0 dan 0Ah’a kadar 11 tane değer var) LEA BX, Tablo ; BX’e (taban kaydedicisi) Tablonun ofset adresi yükleniyor XLAT ; Tablonun 11. elemanına erişilip ASCII karakterin hex karşılığı AL’ye yükleniyor (AL=41h) Şimdi yukarıda açıkladığımız komutlardan birkaçını kullanarak bir program yazalım ve daha sonra da inceleyelim. Öncelikle Push ve Pop komutlarını DS kaydedicisinin değerini belirlemek için kullanabilirim. .exe tipi programlarda DS kaydedicisinin değerini belirlemek için bu güne kadar hep aşağıdaki iki satırı kullandık, MOV AX, @data MOV DS, AX Yığın komutlarınıda veri taşımak için kullanabileceğimizden, MOV PUSH POP
AX, @DATA AX DS
Yukarıdaki üç satırda DATA segmentin adresini önce AX kaydedicisine yükledik, sonra bu kaydedicideki değeri yığına ittik son olarakta yığından bu değeri çekip DS kaydedicisine yükledik. Kullandığım sistemin saat bilgisini ekrana yazdırmak istiyorum. Tabiî ki bu gibi işlemler için hali hazırda DOS’un kesmeleri mevcuttur. Fonksiyon 2Ch sistem zamanı ile ilgili bilgileri işlemcinin kaydedicilerine getirir. MOV AH,2Ch INT 21h Bu komutlar işlenince CX ve DX kaydedicileri saat bilgisi ile yüklenirler, CH = saat CL = dakika DH = saniye DL = salise
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Kaydedicilerin içindeki değerlerin binary olduğunu ve debugger programlarında bu değerlerin hexadecimal formatta göründüğünü unutmayalım. Biz bu programda kullanıcı ekranına desimal formatta değerler yazdıracağımızdan ya çevirme işlemi yapacağız yada tablo oluşturup hex bilgilerin karşılığına tablodan bakacağız. Çevirme işlemleri için çarpma, bölme gibi komutları bilmeniz gerekir ama henüz o komutlarla ilgili örnekler çözmedik. Bunun yerine biz hex-decimal tablolar oluşturup bu tablolardan desimal değerleri bulalım ve ekrana yazdıralım. Fakat tabloların sınırlarını belirlemek için elimizdeki bilgiyi iyi tanımamız gerekir. En büyük saat bilgisi 23:59 yani 0 ile 59 elemanlarını kapsayan bir tablo oluşturmam yeterli. SaatTablo DB"00$","01$","02$","03$","04$","05$","06$","07$","08$","09$" DB "10$","11$","12$","13$","14$","15$","16$","17$","18$","19$" DB "20$","21$","22$","23$","24$","25$","26$","27$","28$","29$" DB "30$","31$","32$","33$","34$","35$","36$","37$","38$","39$" DB "40$","41$","42$","43$","44$","45$","46$","47$","48$","49$" DB "50$","51$","52$","53$","54$","55$","56$","57$","58$","59$" Yukarıdaki tablo bize 60 elamanlı gibi görünebilir fakat hafızada 60*3=180 byte yer kaplar, çünkü x86 hafızası byte adreslenebilir ve her rakam veya karakter hafızada 1 byte’lık yer kaplar. Diyelim ki CH’taki saat bilgisi 0Ah, bu saat 10 demek. Tabloda “10$” olan kısım acaba tablonun hangi adresidir? Sayacak olursanız 30. elemanın 1 31. elemanın 0 yani 30 elemandan itibaren 10 değerinin mevcut olduğunu görürsünüz. Tabloda her saat değeri için 3 byte’lık değer ayrılmıştır ve bu programda CH veya CL deki değerler yardımıyla indeks adresi hesaplanırken 3 ile çarpmak gerekir. CH’ta saat 10’u temsilen 0Ah değeri bulunuyorsa 0Ah*3 = 1Eh = 30 hesabı yapılmalıdır. Tüm bu işlemleri yapıp ekrana yazdırılıcak değeride DX kaydedicisine yüklemeliyiz. Çünkü ekranda bir karakter dizisini yazdırmak için INT 21h’ın 9. fonksiyonu karakter dizisinin başladığı adresi DX kaydedicisinde bulunmasını ister. Tüm bu işlemleri yapan kod satırları aşağıdaki gibidir. MOVAL, CH ;Şimdi saat bilgisi, MULUc ;3 ile çarpılarak tablodaki desimal karşılığı bulundu. MOVDI, AX ;Bu adres indeks olarak düşünüldüğünden DI ya yüklendi. LEA DX, SaatTablo[DI] ;Ve BX’e saat bilgisinin desimal karşılığı olan tablo konumu yüklendi. MUL komutunu önümüzdeki makalelerde inceleyeceğiz, çok fazla kullanım formatı mevcuttur. Bizim için burada, sadece CH veya CL deki değerleri 3 ile çarpsın yeter. Fakat MUL komutu sadece AL deki değeri bir değişken yada AL kaydedicisi ile çarpabildiğinden önce CH yada CL deki bilgileri AL’ye taşımamız
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
gerekiyor. Daha sonra 3 değeri ile çarpıp tablodan adresi buluyoruz. Tabiî ki Uc bir değişken ve data segmentinde tanımlı olmalıdır. Uc
DB
3
Al deki değer 3 ile çarpıldığında sonuç 16 bitlik AX kaydedicisinde saklanır. Bu noktadan sonra bu adresi DX kaydedisine yükleyip ekranda string yazdırma kesmesini çağırırsak işlem tamam olacaktı ama, SaatTablo taban adresine göre hesapladığımız indeks adresi halen AX kaydedicisinin içinde ve LEA DX, SaatTablo[AX] gibi bir komut satırı kullanamam çünkü böyle bir adresleme modu mevcut değil. Indeks olarak kullanılacak adresler 8086 assembly dilinde SI, DI veya BX kaydedicilerinin birinde olmalıdır, bu yüzden bizde hesapladığımız indeks değerini DI ya yükledik. Son olarak ekranda bir string yazdıracağımızdan DX kaydedicisinin içine yazdıracağımız stringin başlangıç adresini atarak ve daha önce de kullandığımız INT 21h fonksiyon 9h ı kullanabiliriz. Bununla beraber “:” karakterinide saat bilgisini yazdırırken saat ile dakika arasına yerleştirelim, tabiî ki bu bilgide data segmentinde bir adreste saklı kalsın. Ayirac DB‘:’ Birde saat kelimesini data segmentte tanımlayalım, Saat
DB
‘Saat $’
Şimdi kabataslak ne yapacağım belli oldu, programımın algoritmasını da yazdıktan sonra kodlarımı yazmaya başlayabilirim. 1-
Ekrana “Saat” yazdır
2-
Sistem saatini ilgili kaydedicilere yükle
3-
Tablodan saat bilgisinin desimal karşılığını bul
4-
Ekrana yazdır
5-
: karakterini ekrana yazdır
6-
Sistem dakikasının karşılığını bul
7-
Ekrana yazdır
8-
Dur
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Şekil 1 - Saat bilgisini ekranda gösteren program.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Şekil 2 - Programın ekran çıktısı.
Elbette bu program daha kısa veya pratik bir şekilde yapılabilirdi. Bunun için komut bilgimizi genişletmeliyiz. Ayrıca değişken bildirimlerini de şu ana kadar tam olarak anlatmadım. Bu konularla ilgili makalelerimiz yolda fakat şu anda sizlerin mevcut bilgilerinizle bu programa birde salise kısmını ekleyebilmeniz gerekir. 80x86 KOMUT SETĐ (Bölüm 2) Aritmetik ve mantık (lojik) işlemler mikroişlemcinin ALU (Arithmetic Lojic Unit) denen kısmında yapılır. ALU bir dizi elektronik toplama, çıkarma ve mantık devrelerinden oluşmuştur. Bu devrelerin çalışma mantıkları ise sayma temelinden geçer. Bizlerde ilkokul sıralarında temel işlemleri parmaklarımızla sayarak yapardık. 3 ile 5’i toplarken 3’ün üzerine 5 tane parmak sayardık. Mikroişlemcide her saykılda (saat darbesinde) ALU’da bir sayma işlemi yapar. Bu saat darbesi ne kadar hızlı olursa işlemler o kadar hızlı gerçekleşir. Örneğin 1 GHz. hızında bir işlemci saniyede 1 milyar elektronik darbe üretebilir ve bu saniyede milyonlarca işlem yapabileceği anlamına gelir. Aritmetik komutların genel kullanım formatları aşağıdaki gibidir. Bu komutları kullanırken de adresleme modlarına dikkat etmemiz gerektiğini unutmayalım.
ADD ve ADC komutları: Toplama ve elde ile toplama komutlarıdır. ADD komutu işlemci durum kaydedicisinin C bitini hesaba katmazken ADC toplama işlemini C bitinide dahil ederek yapar. MOV AX, 5 ADD AX, 6 Bu işlemden sonra AX kaydedicisinde 11’in karşılığı olan 000Bh değeri görülür. MOV AX, 5 ADC AX, 6 Bu işlemden sonra şayet C=0 ise sonuç 000Bh C=1 ise sonuç 000Ch olacaktır. x := y + z + t işlemini; MOV AX, Y ADD AX, Z ADD AX, T MOV X, AX şeklinde yapabilirsiniz. Tabiki bu x,y,z,t’ler birer hafıza konumu veya kaydedici olabilir.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
x := x + z işlemini düşünelim. x ve z hafızadaki birer değer olsun yani değişkenlerimiz. Bunu en hızlı şekilde işlemciye nasıl hesaplatabiliriz? 1.yol MOV AX, X MOV BX, Z ADD AX, BX MOV X, AX Yukarıdaki şekilde bu işlemi yapabilrsiniz ama bu çokta iyi bir yol değildir. 2.yol MOV AX, X ADD AX, Z MOV X, AX Bu yol daha iyi gibi görünsede bundan daha iyi çalışacak kodlar aşağıdaki gibidir. 3. yol MOV AX, Z ADD X, AX Adresleme modlarını akıllı bir şekilde kullanabilirseniz çok hızlı çalışan programlar hazırlayabilirsiniz. Yukarıdaki üç program parçası aynı işi yapmasına rağmen en hızlı çalışanı 3. südür. Günümüzde kullanıcıya daha yakın ve program yazması daha kolay olan üst seviye programlama dillerine göre assembly dilinin en büyük avantajı budur. ADD ve ADC komutları işlemcinin bayrak kaydedicindeki bitlere şöyle etki ederler. 1. Đşaretli sayılarla işlem yaparken işaret taşmalarını göstermesi amacıyla V (overflow) bitine. Çünkü bazen negatif bir sayı ile negatif başka bir sayı toplanır ve pozitif bir sonuç elde edilebilir. 2. Đşaretsiz sayılarda işlem yaparken boyut taşmalarını göstermesi amacıyla C bitini. Örneğin 2 byte’lık bir değişken olan FFFFh (65535) ile 1 i toplarsınız sonuçta 0 elde edersiniz. Sonucun alana sığmadığı bu gibi durumlarda programcıyı haberdar etmesi amacıyla C=1 olur. 3. Şayet sonuç negatif bir değerse S=1 değilse S=0 olur. 4. 0 sonucu bir çok yerde önemli olabilir. Bu yüzden toplama komutları ile işlem yapıldığında sonuç 0 olursa Z=1 aksi halde Z=0 olur. 5. Binary değerler ile desimal sayıları kodlamak gerekebilir (BCD). Bu durumda düşük değerlikli 4 bitte taşma olursa bayrak kaydedicisinin A biti 1 olur. Örneğin 0000 1111b değerine 1 eklendiğinde sonuç 0001 0000b olacaktır ve düşük değerlikli 4 bitteki en büyük sayı olan 1111 birden 0 a düşecektir. Bu yüzden A=1 olur ve işlemci programcıyı bu
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
durumdan haberdar eder. 6. Elde edilen sonuçtaki binary 1 ler çift sayıda olursa, örneğin 0000 0101 değerinde 2 tane 1 vardır bu durumda P=1 olur.
INC Komutu: ADD X, 1 gibi çalışır. X kaydedici veya hafıza alanı olabilir. Kısaca hedefi 1 arttırır. Döngülerde çok kullanılan bir komuttur. Bu yüzden çok önemlidir. INC komutunun 1,2 veya 4 bytelık operandı olabilir. Yani bu komutu aşağıdaki formatlarda kullanabilirsiniz. INC AL ; 1 bytelık kaydedici INC AX ; 2 bytelık kaydedici INC EAX ; 4 bytelık kaydedici INC HAFIZAADRESI ; Byte word veya doubleword boyutundaki değişkenler olabilir. INC komutu genelde ADD mem,1 veya ADD reg,1 formatına tercih edilir çünkü daha hızlıdır, buna rağmen peşpeşe 1 den fazla inc komutu kullanmak gerekirse komut setinin incelenmesinde fayda vardır. Çünkü bu işi ADD reg,2 veya ADD mem,2 şeklindede yapabilirsiniz ve bu durumda sadece 1 adet komut satırı yazarsınız. Ayrıca INC bayrak kaydedicisinin C bitine etki etmez. Bu yüzden dizi işlemleri için çok uygun bir komuttur. Oysa ADD ve ADC C bitine etki ederler ve büyük dizilerde bu komutlar kullanılırsa dizinin içindeki elemanları işaret etme işleminde bazen yanlış sonuç gösterebilirler.
XADD Komutu: 80486 ve sonrası işlemciler için geçerli bir toplama komutudur. Bu komutu aşağıdaki örnek ile daha iyi anlayacağınız kanaatindeyim. ; AX = 07h ve BX = 03h olsun XADD AX, BX
; Bu komuttan sonra
; AX = 0Ah yeni toplama işleminin sonucunu gösterir, ; ve BX = 07h olur, yani AX’teki kaybolan operand buraya taşınır. Çıkartma işlemini yapan komutlar: SUB ve SBB komutları: SUB (Subtract) yani çıkartma SBB ise borç ile çıkart (SuBtract with Borrow) anlamına gelir. Her iki çıkartma işlemi bir çıkartma sonucu üretmenin yanında bayrak kaydedicisinin C bitinide etkilerler. Bu komutların genel kullanım formatları aşağıdaki gibidir; sub sub sub sub
reg, reg reg, mem mem, reg reg, immediate data
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
sub mem, immediate data sub eax/ax/al, immediate data Bu komutların ne yaptığını örnekler ile daha iyi anlayabiliriz; MOV AX, 0Ah MOV BX, 04h SUB AX, BX Yukarıdaki komutlar ile işlemci 0Ah-04h işlemini yapar ve 6 sonucunu AX kaydedicisinin yani hedef kaydedicide saklar. Bu işlemde büyük değerden küçük değer çıkartıldığından C bitinin durumunda bir değişiklik olmaz. MOV AX, 04h MOV BX, 0Ah SUB AX, BX Yukarıdaki komutlar işlenince 04h-0Ah işlemi yapılır ve 2 byte’lık AX kaydedicisinin içinde FFFAh sonucu görülür. Bu işlemde ayrıca C biti set edilir ve C=1 olur. Programcı C’nin bu durumunu göz önünde bulundurmalıdır. Çünkü sonucu işaretsiz bir tamsayı gibi değerlendirirse yanılır. Böyle bir sonuç elde edildiğinde sonucun tümleyeni alını ve 1 eklenir. 1111 1111 1111 1010 ; FFFAh’ın binary karşılığı 0000 0000 0000 0101 ; tümleyeni 0000 0000 0000 0110 ; 1 fazlası yani 6 Ayrıca bu sonuç incelenirken, kaydedicideki FFFAh değerinin 15. biti 1 olduğundan sonuç negatif olarak değerlendirilmeli ve yukarıdaki işlem yapılarak sonucun gerçek değeri hesaplanınca; -6 değerine ulaşabilirsiniz. Aslında FFFAh sonucunun sağlamasını yaparsanız, yani çıkana bu sonucu eklerseniz; FFFAh 000Ah ------0004h yukarıdaki 4h sonucuna erişirsiniz. SUB komutunun kullanımını SUB hedef, kaynak şeklinde genellersek; hedef = hedef - kaynak; SBB komutu ise; hedef = hedef - kaynak - C işlemlerini yapar. Çıkartma komutları toplama komutlarında da olduğu gibi bayrak kaydedicisinin, Z, S, V, A, P ve C bitini etkilerler. Tabiki bu etkilenen bayraklar yapılan işleme göre programcı tarafından değerlendirilmelidir. Çıkartma Aslında Toplamadır!
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
3 - 4 aslında 3 + (-4) değil midir? Bu tür basit bilgileri unutmamak bazen sizin işinizi kolaylaştırabilir. Aşağıdaki örneği inceleyelim. x = x - y -z işlemini yapmak için; MOV AX, X SUB AX, Y ; x-y işlemi yapılıyor, sonucu AX’e yükleniyor. SUB AX, Z ; x - y - z işlmi yapılmış oluyor MOV X, AX ; sonuç x’e yüklenerek x = x - y - z işlemi yapılmış oluyor. Fakat bu işlem aslında x = x - (y + z) değilmidir? MOV AX, Y ADD AX, Z ; y - z işlemi yapılıyor SUB X, AX ; x - y - z işlemi yapılıp sonuç x’e yükleniyor. DEC komutu: Decrement yani azalt anlamına gelir. hedef operandını 1 eksiltir, başka bir deyişle -1 ekler. Kullanım formatları aşağıdaki gibidir. DEC reg DEC mem DEC reg16 C biti hariç çıkartma komutların etkilediği bayrakları etkileyen bir komuttur. INC komutu gibi genelde döngülerde her iterasyondan sonra sayacı azaltmak için kullanılır. CMP komutu: SUB komutu ile aynı işi yapar fakat çıkarma işleminin sonucunu herhangi bir kaydediciye yüklemez. Bu komut genelde şartlı dallanma komutlarından önce bayrakları etkilemek için kullanılır. CMP’nin anlamı "compare" yani karşılaştır demektir. Bakın neleri karşılaştırabiliyoruz; genel kullanım formatları, cmp cmp cmp cmp cmp cmp
reg, reg reg, mem mem, reg reg, immediate data mem, immediate data eax/ax/al, immediate data
Bu komut A, C, O, P, S ve Z bayraklarını etkiler. Programcı etkilenen bu bayrakları göreceli olarak yorumlayabilir, Şöyle ki; A ara elde biti yani işlem yapılırken 3. bite gelindiğinde eldenin olup olmadığı hakkında bilgi verir ve P işlem sonucundaki değeri binary olarak düşündüğümüzde 1’ler tekmi yoksa çift mi durumunu gösterir. A ve P bayraklarından ziyade programcılar Z, C, O ve S bitlerinin durumları ile ilgilenirler. Bu bayrakları değerlendirirken de işlemlerin işaretli yada işaretsiz sayılar ile yapıldığının bilinmesi büyük önem taşır.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
1- Z bayrağı sayılar ister işaretli ister işaretsiz olsun eşitlik yada eşit olmama durumunu gösterir. mov ax,5 mov bx,5 CMP ax,bx ; Z=1 yani operandlar eşit. 2- C bayrağı işaretsiz sayılarda; C=1 ise 2. operand 1.operand dan büyük demektir. C=0 ise 1. operand büyüktür. C bayrağının işaretli sayılarda bize verdiği sonuçların bir anlamı yoktur. 3- S ve O bayrakları işaretsiz sayılarda anlamsız olurken işaretli sayılarda 2 değişik durumu gösterirler. Bunlar; a- S=0, O=1 veya S=1, O=0 ise 2. operand 1. operand’tan büyüktür. b- S=0, O=0 veya S=1, O=1 ise 1. operand 2. operand’tan büyüktür. Şartlı Dallanma Komutları: Đngilizce karşılığı Conditional Jump Instructions’dır. Bu tür komutlar işlendikten sonra program ya normal akışına yani komutları satır-satır işlemeye devam eder ya da normal akışından sapıp başka bir adresteki komutu işler. Karar alma mekanizmaları bu komutlar ile yapıldığından çok önemli komutlar olduğunu sanırım tahmin edebilirsiniz. Şartlı dallanma komutlarının ilk harfi J ile başlar ve takip eden 1,2 yada 3 harf şartı gösterir. Bu tür komutları bundan sonra JXXX komutları olarak kullanacağım. JXXX komutlarının da CMP komutları gibi işaretli ve işaretsiz değerler için farklı anlamları vardır. Tüm bu anlamlar ve komutları aşağıdaki 3 tabloda özetleyebiliriz.
Bayrakların Durumunu Test Etmek Đçin Jxxx Komutları Komut
JC JNC JZ JNZ JS JNS JO JNO JP JPE JNP JPO
Açıklama
Jump if carry (carry (taşıma) bayrağı 1 ise) Jump if no carry (carry (taşıma) bayrağı 0 ise) Jump if zero (zero (sıfır) bayrağı 1 ise) Jump if not zero (zero (sıfır) bayrağı 0 ise) Jump if sign (sign (işaret) bayrağı 1 ise) Jump if no sign (sign (işaret) bayrağı 1 ise) Jump if overflow (overflow (taşma) bayrağı 1 ise) Jump if no Overflow (overflow (taşma) bayrağı 0 ise) Jump if parity (parity (eşlik) bayrağı 1 ise) Jump if parity even (sonuçtaki 1’ler çift ise) Jump if no parity (parity (eşlik) bayrağı 0 ise) Jump if parity odd (sonuçtaki 1’ler tek ise)
Şart
Carry = 1 Carry = 0 Zero = 1 Zero = 0 Sign = 1 Sign = 0 Ovrflw=1 Ovrflw=0 Parity = 1 Parity = 1 Parity = 0 Parity = 0
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Eş Komut
JB, JNAE JNB, JAE JE JNE JPE JP JPO JNP
Karşıt Komut
JNC JC JNZ JZ JNS JS JNO JO JNP JPO JP JPE
Đşaretsiz Sayılarda Jxxx Komutları Komut
Açıklama
Şart
JA
Jump if above (>) (Yukarısındaysa) Jump if not below or equal (not <=) JNBE (Aşağısında değil yada eşit değilse) JAE Jump if above or equal (>=) (Yukarısında veya eşitse) JNB Jump if not below (not <) (Aşağısında değilse) JB Jump if below (<) (Aşağısındaysa) Jump if not above or equal (not >=) JNAE (Yukarısında değil veya eşit değilse) JBE
Jump if below or equal (<=) (Aşağısında veya eşitse)
JNA
Jump if not above (not >) (Yukarısında değilse)
JE JNE
Jump if equal (=) (Eşitse) Jump if not equal (!=) (Eşit Değilse)
Eş Komut
Karşıt Komut
Carry=0, Zero=0
JNBE
JNA
Carry=0, Zero=0
JA
JBE
Carry = 0 Carry = 0 Carry = 1
JNC, JNB JNAE JNC, JAE JB JC, JNAE JNB
Carry = 1
JC, JB
JAE
JNA
JNBE
JBE
JA
JZ JNZ
JNE JE
Carry = 1 or Zero = 1 Carry = 1 or Zero = 1 Zero = 1 Zero = 0
Đşaretli Sayılarda Jxxx Komutları Komut
JG
Açıklama
Jump if greater (>) (Büyükse)
Jump if not less than or equal (not <=) (Düşük değilse yada eşit değilse) Jump if greater than or equal (>=) JGE (Büyükse veya eşitse) JNL Jump if not less than (not <) (Düşük değilse) JL Jump if less than (<) (Düşükse) Jump if not greater or equal (not >=) JNGE (Büyük değilse veya eşit değilse) Jump if less than or equal (<=) (Düşükse veya JLE eşitse) JNLE
JNG
Jump if not greater than (not >) (Büyük değilse)
JE JNE
Jump if equal (=) (Eşitse) Jump if not equal (!=) (Eşit değilse)
Şart
Sign = Ovrflw or Zero=0 Sign = Ovrflw or Zero=0
Eş Komut
Karşıt Komut
JNLE
JNG
JG
JLE
Sign = Ovrflw
JNL
JGE
Sign = Ovrflw Sign Ovrflw
JGE JNGE
JL JNL
Sign Ovrflw
JL
JGE
JNG
JNLE
JLE
JG
JZ JNZ
JNE JE
Sign Ovrflw or Zero = 1 Sign Ovrflw or Zero = 1 Zero = 1 Zero = 0
Tabiki yukarıda kullandığım büyük, küçük, yukarısında veya aşağısında kelimeleri sayıların değerleri açısındandır. Tablolarda aslında aynı işi yapan hatta makine düzeyinde aynı kodu üreten komutlar vardır. Bunlara örnek olarak JA ve JNBE’yi örnek verebiliriz. Assembler her iki komut içinde aynı makine kodunu üretir, fakat programcıyı assembly kodlarını yazarken birazcık olsun rahatlatması için Intel bu tür yazımı uygun görmüştür. Tüm programlama dillerinde kullanılan if, for, while vb. deyimler aslında şartlı dallanma komutları ile düzenlenmiş bir kaç assembly komutuna benzetilebilir. Hatta işi biraz daha netleştirelim C dilinde kullandığımız "if" deyimi aslında bir adet cmp ve bir adet Jxxx komutundan oluşur, xxx kısmı ise şarta göre değişir. Aşağıdaki örnekle bu olayı daha iyi anlayabilirsiniz.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Şekil 1 - C Dili ile yazılmış bir program.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Şekil 2 - Programın assembly kodlarına baktığımızda if deyimine karşılık CMP ve JLE komutlarını görüyoruz.
1. şekilde C dilinde yazılmış basit bir program görüyorsunuz. Bu programda tamsayı türünden 2 adet değişken (a ve b) ve karakter türünden 1 adet değişken mevcut (sonuc). IF deyimi ile şayet a, b’den büyükse (ki zaten öyle) sonuç değişkenine büyüğü temsilen b karakteri aksi durumda küçüğü temsilen k karakteri yüklenecek. Şimdi burada bizi ilgilendiren C dilinde IF acaba assembly’de neye karşılık geliyor? Şekil 2 ye baktığınızda bu sorunun cevabını hemen görüyorsunuz. IF aslında CMP ve JLE komutlarının bir kombinasyonuymuş. JLE yerine başka bir şartlı dallanma komutu gelebilirdi, bu tamamen IF ile kullandığınız şarta bağlıdır, bu programda IF (a>b) kullanılmıştı. Assembly karşılığında a’nın değeri olan 5 önce eax kaydedicisine yükleniyor ve sonra hafızadaki b değişkeni değeri ile yani 4 ile karşılaştırılıyor.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
JLE satırına dikkat edin, JLE’nin anlamı less or equal yani düşük yada eşitse. Yani a’nın değeri olan 5 b’nin değeri olan 4’ten düşük yada eşitse, program 00411A3E adresinden devam edip, önce eax’e k karakterini yükleyip (daha doğrusu k’nın ASCII kod karşılığını yükleyecek), bunu hafızadaki "sonuc" adlı yere (sonuc da aslında bir adres :)) kaydedecektir. Tabi 5 4’ten büyüktür. Hal böyle olunca bu programda JLE komutu işlendikten sonra bir dallanma söz konusu değildir. Yani program normal akışını devam ettirecek ve eax’e "b" karakterini ardından da sonuc adlı adrese kopyalayacaktır. Bu programın assembly kodlarını daha önce görmediğimiz bir biçimde birazcık farklı görmeniz doğaldır. Bunun nedeni Visual Studio.NET’in C derleyicisinin 32 bitlik olduğundandır. Yani bu güne kadar biz 16 bitlik TASM veya MASM’ı kullandık. Bu yüzden eax kaydedicisini göremedik veya adresleri buradaki gibi 32 bitlik değil de hep 16 bitlik offsetler halinde gördük. Bu yüzden assembly kodları biraz farklı. 32 bitlik kodlama bizim için henüz çok erken bir kavram, bu yüzden bundan sonraki makalelerimizde 16 bitlik kodlamaya devam edeceğiz.
80x86 KOMUT SETĐ (Bölüm 3) NEG komutu NEG negatif kelimesinin kısaltmasıdır. Tek operandı vardır. Kullanım formatı aşağıdaki gibidir. neg neg
reg mem
yani operandı herhangi bir kaydedici veya hafıza adresi olabilir. Yaptığı iş operandın değerinin negatifini almaktır. Daha doğru bir deyişle operandını 0’dan çıkartır. Binary düzende düşünecek olursanız 1’lein yerine sıfır 0’ların yerine 1 getirir ve bu sonuca 1 ekler. mov neg
al,0fh ax
yukarıdaki işlemden sonra AX’in içeriği F1h olacaktır. 0Fh = 0000 1111 tersi = 1111 0000 1111 0000 +1 = 1111 0001 = F1h Bu komutun ne amaçla kullanılabileceğini makalenin sonlarına doğru anlayacaksınız. MUL ve IMUL Komutları MUL çarpma IMUL ise işareti dikkate alarak çarpma işlemlerini yapar. Kullanım formatı aşağıdaki gibidir. Đşaretsiz çarpma: mul mul
reg mem
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Đşaretli çarpma: imul imul imul imul imul imul imul
reg mem reg, reg, imm (*) reg, mem, imm (*) reg, imm (*) reg, reg (**) reg, mem (**)
*- Sadece 286 ve sonrası işlemcilerde. **- Sadece 386 ve sonrası işlemcilerde.
IMUL komutunun 286 ve 386 dan sonraki kullanım formatlarına bakacak olursanız, bu güne kadar gördüğümüz formatlardan biraz farklı olduğunu görürsünüz. Bu kullanım şekilleri programcıların kafasını biraz karıştırmakla beraber tek bir komut satırı ile çabucak çarpma işlemini yapmasını sağlar. Nede olsa Intel karmaşık komut setini benimsemiştir. Çarpma komutları bayt-bayt, word-word veya Doubleword-Doubleword çarpma yapabilir. Tabi ki Doubleword çarpım için 386 ve sonrası işlemci kullanmanız gerekiyor, çünkü 32 bitlik kaydediciler 386 ile birlikte gelmiştir. Ayrıca çarpma işlemi toplamadan daha büyük sonuçlar çıkarabilir. Yani 2 basamaklı bir değeri başka bir 2 basamaklı değer ile toplarsanız sonuç en fazla 3 basamaklı çıkarken çarpmada bu 4 basamağa kadar çıkabilir. Daha fazla basamaklı sayıların çarpımında sonuç çarpılan veya çarpandan çok daha fazla basamaklı çıkabilir. Bu gerçeği göz önüne alarak işlemci tasarımcıları sonucu her zaman çarpan ve çarpılanın boyutundan daha büyük bir kaydedicide saklama yoluna gitmişlerdir. Bunları aşağıdaki şekil ve açıklamalarla daha iyi anlayacaksınız. Byte Çarpma
27h ile 17h’ı çarpmak için; mov mov mul
al, 27h dl, 17h dl
komutlarını kullanabilirsiniz. Burada "mul dl" komutu ile DL*AL işlemi yani bu kaydedicilerdeki değerler olan 17h ve 27h sayıları çarpılır. Peki sonuç nerede? Yukarıdaki şekle baktığınızda sonucun AX içinde olacağını görebilirsiniz. Bu çarpma işleminden sonra AX’te 0381h değeri görülür.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Pratik olarak 8 bitlik bir değerin karesini almak için; mov mul
al,sayi al
komutlarını kullanabilirsiniz. Word Çarpma
Bu tür bir çarpma işleminde operand AX ile çarpılır ve sonuç DX-AX kaydedicilerinden okunur. DX’te daha önce ne olduğu önemli değildir çünkü çarpmadan sonra buraya sonucun yüksek değerlikli byte’ı yerleşir. Sonucun düşük değerlikli byte’ı ise AX kaydedicisinde saklanır. Burada dikkat ederseniz çarpma işlemiyle birlikte AX kaydedicisindeki "çarpılan" da kaybedilecektir. Benim tavsiyem bu tür çarpmalarda çarpan ve çarpılanı birer değişken olarak programınızın data segmentinde tanımlamanızdır. Aşağıdaki örnekleri inceleyin, 100h ile 2345h değerlerini çarpalım; mov mov mul
ax, 2345h bx, 100h bx
bu işlemden sonra DX=0023h ve AX=4500h olur. Yani asıl sonuç olan 234500h değerinin yüksek değerlikli word’u DX’te düşük değerlikli kısmıda AX’te görülür. Fakat çarpılan değer yani 2345h bu işlemden sonra kaybolacaktır. Şayet bu çarpılan değer sizin için önemliyse; carpilan db 2345h .. .. .. mov ax, carpilan mov bx,100h mul bx böylece 2345h değeri daima "carpılan" ismi ile hafızada korunur. Aynı şeyi tabi ki çarpan için yani 100h değeri içinde yapabilirsiniz. carpilan carpan .. ..
db db
2345h 100h
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
.. mov mov mul
ax, carpilan bx, carpan bx
Double Word Çarpma Double word boyutundaki verilerin çarpımında da word dekine benzer bir yapı kullanılır. Çarpılan değer EAX kaydedicisine yerleştirilip, MUL veya IMUL komutunun peşinden gelen operand ile bu değer çarpılır. Daha sonra elde edilen sonucun yüksek değerlikli doubleword’u EDX’te düşük değerlikli doubleword’ü ise EAX’te saklanır. Yani sonuç 64 bitliktir.
carpilan dd 12345678h carpan dd 34522344h .. .. .386 .. .. mov ax, carpilan mov bx, carpan mul bx Yukarıdaki örnekte sonuç olarak işlemci 03B878D610295FE0h değerini hesaplar. Bu çarpma işleminden sonra EDX=03B878D6h ve EAX=10295FE0h olur. Buradaki .386 32 bitlik kaydedicileri kullanmak için assembler’a verilen bir direktif (talimat) dır. EDX ve EAX gibi 32 kaydedicilerin 32 bitlik alanlarını kullanmak için nu talimatı vermeniz gerekir. 32 bitlik programlama, 16 bitlik programlama nedir bunlar? Şimdilik sadece 32 bitlik programların 16 bitliklere göre daha avantajlı olduğunu görebilirsiniz. Çünkü 32 bitlik programlama ile kaydedici boyutlarımı 2 katına çıkıyor ve bir kaydedicide hesaplayabileceğimiz değerlerde aynı oranda artıyor, bu işlemi 16 bitlik bir programlama ile de halledebilmemize rağmen 2 katı daha fazla komut yazmamız gerekir. MUL komutu bayrak kaydedicisinin C ve O bitlerini etkiler. Bu bayraklar beraber değerlendirildiğinde aşağıdaki sonuçlar çıkartılır. 1- Byte boyutundaki bir operand AL ile çarpılırsa sonuç AX’te görülür. AH=0 ise C ve O sıfır olur, aksi durumlarda bu bayraklar set (1) olur. 2- Word çarpmada C ve O sıfır ise DX’te sıfır demektir, aksi durumlarda bu bayraklar set olur.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
3- Double word çarpmada ise C ve O sıfır ise EDX’te sıfır demektir, aksi durumlarda bu bayraklar yine set olur. Yukarıdaki üç durum soldaki sıfırların çarmada bir değeri olmadığından, sonucu optimize etmenize yardımcı olacaktır. IMUL ile Diğer Çarpma Formatları IMUL (Integer Multiplication) komutu ile yukarıdaki MUL komutu için verilmiş kalıpları kullanabilirsiniz, bununla beraber IMUL komutuna özel çok operandlı kullanım formatlarıda mevcuttur. Tüm kullanım formatları bu makalenin başında verildiği gibidir fakat kaydedicilerin 8 16 ve 32 bitlik durumları da göz önüne almamız gerekir. Şimdi henüz açıklamadığımız 286 ve 386 sonrası işlemcilerde kullanılabilen komut formatlarını aşağıdaki örneklerle inceleyelim. imul operand1, operand2, imm imul imul imul imul imul imul imul imul imul imul imul imul
reg16, reg16, reg16, reg16, reg16, reg16, reg32, reg32, reg32, reg32, reg32, reg32,
;Genel kullanım formatı
reg16, imm8 reg16, imm16 mem16, imm8 mem16, imm16 imm8 imm6 reg32, imm8 (*) reg32, imm32 (*) mem32, imm8 (*) mem32, imm32 (*) imm8 (*) imm32 (*)
* Sadece 80386 ve sonrası işlemcilerde kullanılabilir Yukarıdaki komut formatlarının 3 operandlı olanları operand1 := operand2 x imm ve 2 operandlı olanlarıda operand1 := operand1 x imm şeklinde çalışır. Her zaman son kullanılacak olan operandın "imm" yani sayısal bir değer olduğuna dikkatinizi çekerim. mov imul
bx, 4 ; BX = 0004h ax, bx, 3 ; AX = 4 * 3 = 000Ch
Bu komutlar ile 8x8 bit çarpım söz konusu değildir, imm8 olarak yukarıda gördüğünüz operand sadece komutunun makine kodunun olmasını sağlar. Ayrıca bu çarpma işlemlerinde sonucun boyutu operandta belirtilen kaydedicilerin boyutuyla aynıdır, yani makalenin başında anlattığımız mul komutu gibi sonuç operandın 2 katı olmaz. Bu durumda sonucun hedef kaydediciye sığmaması durumuna karşı C ve O bitleri birlikte kontrol edilmelidir, bu durumlara Intel’in komut setinden bakabilirsiniz.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Bununla beraber bu formattaki çarpma komutları Z bitini her zaman doğru bir şekilde etkilemeyebilir, şayet sonucun sıfır olup olmadığı sizin için önemli ise ancak sonucu sıfır ile karşılaştırdıktan sonra Z bitini kontrol etmelisiniz. Aynı şekilde sonucun işaretini öğrenmek için işaret bayrağı yerine C ve O bitlerinin sıfır olup olmadığı kontrol edilmelidir. IMUL komutu için Intel’in 80286 ve sonrası işlemcilere koyduğu bu adresleme biçimleri çok boyutlu diziler ile yapılan işlemleri hatırı sayılır biçimde kolaylaştırmıştır. Bu konuya çok boyutlu dizileri ve karmaşık veri yapılarını anlatırken bir daha değinmeyi düşünüyorum. DIV ve IDIV Komutları DIV division yani bölme kelimesinin kısaltmasıdır. Bölme işlemi çarpmanın tersine bölünen’e göre küçük sonuç üretir, bu yüzden bu komutları kullanırken bölünen’in boyutu bölen’in boyutunun iki katı olmak zorundadır, an azından Intel bu komutlar için böyle bir form öngörmüştür. Bu yüzden bölünenin boyutu en az word türünde olmalıdır, çünkü x86 Assembly dilinde en küçük veri tipi byte’dır. Bu durumda byte türünden bir değeri bölmek isterseniz bunu CBW komutu ile word boyutuna dönüştürmeniz gerekir, hatırlarsanız bu tür komutları daha önceki makalelerimizde açıklamıştık. Div ve idiv komutlarının genel kullanım formatları aşağıdaki gibidir. div div idiv idiv
reg mem reg mem
; Đşaretsiz çarpma
; Đşaretli çarpma
Word’u Byte’a Bölmek
Örnek: 10h:3h işlemini yapmak istiyoruz, bu değerlerin ikisinin de byte türünden olduğunu varsayalım; mov mov cbw div
al, 10h bl, 03h ; bl yerine başka bir kaydedicide olabilir! ; 10h şimdi 0010h ve AX’te bl ; ax, bl’ye bölündü
bu işlemden sonra AX, 0105h olur. AH’taki 01h kalan ve AL’deki 05h ise bölümdür. Nitekim 16’nın (yani 10h) 3’e bölümü ile de bu sonuç üretilir. Bu tür bölme işleminde elde edebileceğiniz en büyük bölüm 255 (FFh işaretsiz değerler için) yada 127 (7Fh işaretli değerler için) değerleridir. Doubleword’u Word’e Bölmek
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Bu tür bölme işleminde elde edebileceğiniz en büyük bölüm 32767 (FFFFh işaretsiz değerler için) yada 16383 (7FFFFh işaretli değerler için) değerleridir. Quadword’u Doubleword’e Bölmek
Peki ya sonuç tam değilse? Mesela 11h’ı (yani 17’yi) 3’e bölerseniz sonuç normalde 5,666... şeklinde olur, bu işlemi div komutu ile yaptığınızda ise AX’te 0205h değerini görürsünüz, yani bölüm 5 ve kalan 2. Özetle 17’nin içinde 5 tane 3 ve 1 tane’de 2 vardır ve div ve idiv komutları tamsayı bölme işlemlerini gerçekleştirebilir. Ondalıklı bölme işlemleri için floating point kaydedicileri kullanılır ve bu kaydediciler matematik işlemcisinin içindedir. 80486 DX işlemcisine kadar matematik işlemci normal işlemcinin yanına opsiyonel olarak konulurdu mesela işlemci 80386 ise matematik işlemcisi 80387 olurdu. Artık matematik işlemcisi normal işlemcinin içine gömülü olarak geliyor. Bu arada floating point ünitelerini kullanmak için yüksek seviyeli assembly kodları yazmak gerekir ve bu iş bizim için henüz çok erken. Fakat kalanı 10 ile çarpıp sonrada bölünen ile karşılaştırıp şayet bölünenden büyükse tekrar bölene bölme işlemine gidebilirsiniz, aynı kağıt üzerinde normal bölme işlemi yapar gibi, fakat floating point ünitelerini kullanmak inanın bu işten daha pratiktir ve daha kolay sonuç verir. Malesef Tüm Sonuçlar Binary Toplama, çıkartma, çarpma ve bölme komutlarını gördük, artık bu komutları kullanarak basit bir hesap makinesi programı yazmak isteyebilirsiniz, böyle bir programı yazmaya başladığınızda karşınıza sonuçları ekranda desimal formatta göstermek gibi bir problem çıkacaktır. Evet ekrana yazdırmak için daha önce programlar yazdık fakat bunu sadece stringler ile gerçekleştirdik. DB direktifi ile deklare edilen kelime katarları (stringler) hafızada byte-byte ve ardışık olarak saklanır aynı zamanda bu byte’lar elbette harflerin veya sayıların ASCII kod karşılıklarıdır. 8086 komut seti (bölüm 1) başlıklı makalemizde saat programı yapmıştık ve kaydedicilerde elde ettiğimiz sonuçları bir tablo vasıtasıyla ASCII karakterlerini ekranda göstermiştik. Bu yöntem sıkça kullanılmaz, hatta daha önce bu iş için bu yöntemi kullanan bir program görmedim diyebilirim. Representation yani gösterme veya sunum işlemi çok geniş bir yelpazede incelenebilir, bu tamamen kullanıcının hayal gücüne kalmış bir olaydır. Benim burada anlatmaya çalışacağım olay ise ekran text modunda iken hafızadaki binary ifadelerin ASCII karakter karşılıklarını ekranda göstermek olacaktır.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Diyelim ki bir işlem yaptınız ve sonucunu 20h (32) olarak AL kaydedicisinde saklamayı başardınız ve bunu ekranda göstereceksiniz, bunu direk olarak ekrana yazdırırsanız sadece imleci 1 kez ilerletmiş olursunuz çünkü 20h ascii kod tablosunda space yani boşluk karakterine karşılık gelir, klavyedeki en büyük tuş yani. Peki 32yi nasıl yazdıracağız? Unutmayın ki ascii kod tablosunda sadece rakamların, harflerin ve bir dizi kontrol karakterinin kod karşılıkları vardır sayıların kod karşılıkları yoktur. Bu bağlamda biz 32 yi değil 3 ve 2 yi yan yana ekranda göstermeyi düşünmeliyiz. Ama şu anda AL’de ne 3 ne 2 var sadece 20h var. Diğer bir gerçek 3’un ascii kod karşılığı 33h ve 2 nin ki ise 32h dır. 30h ile 39h arası ascii kod tablosunda rakamlar için ayrılmıştır. şimdi ekrana sırasıyla 33h’ı ve 32h’ı gönderirsek kullanıcı 32 yi görecektir. Problemi özetleyelim elimizde bir baytlık 20h var ve bizim bunu 2 byte lık 3332h dizisine dönüştürmemiz gerekiyor. Keşke bir komut bu işlemi bizim yerimize yapsa! Böyle bir komut varmı yokmu oraya geleceğiz ama 20h ile 3332h arasında güzel bir bağ var. 20h ı 0ah yani 10’a bölsek zaten hex’den decimal’e dönüşüm işlemi yapmış oluruz; mov mov div
ax, 20h bl, 0Ah bl ;AX=0203 yapar.
Keşke AX’teki 0203h değerinde 0’ların yerine 3 gelseydi; or
ax,3030h
;AX’teki 0203h artık 3233h oldu.
birde al ile ah’ı yer değiştirsek! Acaba buna gerçekten gerek var mı? Hatırlarsanız ekrana bir string yazdırmak için DOS kesmelerinden 09h nolu fonksiyonunu kullanmıştık ve bu fonksiyon hafızadaki stringleri yazdırıyordu, yani biz AX’teki bu 3233h değerini önce bir hafızaya atalım sonra 32h ile 33h’ın yerini değiştirmek gerekiyor mu düşünürüz; mov
sonuc, ax
; sonuc=3332h olur.
x86 tabanlı işlemciler hafızayı adreslerken little endian byte sıralamasını kullanırlar, bu yüzden al’deki düşük değerlikli byte hafızanın düşük numaralı adresine ax’teki yüksek değerlikli byte’ta hafızanın yüksek numaralı adresine yerleşmiş olur, böylece ax ile al’nin içeriklerini takas etmemize de gerek kalmaz. Artık sonuc değişkenini referans göstererek ekrana yazdırma kesmesini kullanabilirsiniz. lea mov int
dx,sonuc ah,9 21h
AAA, AAS Komutları Klavyeden giriş yapıldığında, basılan tuşa ait kod hafızada ascii formatta saklanır, mikroişlemci hesaplamaları binary yapar bu yüzden ascii’den binary’ye dönüşüm yapmak gerekir. Şayet ekrana bir karakter basılacaksa bu karakter ekrana gönderilmeden önce ascii forma dönüştürülmelidir.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Bizler günlük yaşamımızda desimal değerleri kullanırız, bu desimal değerleri hafızada binary rakamlar olan 1 ve 0’ları kullanarak gösterebiliriz, buna BCD (Binary Coded Decimal) kodlama diyoruz. Desimal numaralar hafızada BCD olarak gösterilmenin yanında ASCII olarakta gösterilebilir. Đşte AAA, DAA, AAS gibi komutları bunun için kullanıyoruz. Örneğin klavyeden girilen 1234 hafızada 31 32 33 34 olarak ASCII formda saklanır. BCD gösterimin ise iki farklı çeşidi vardır, bunlar packed BCD (paketlenmiş BCD) ve unpacked (paketlenmemiş) BCD’dir. 1234 hafızada 01 02 03 04 byte dizisi olarak saklanırsa bu paketlenmemiş BCD’dir, şayet aynı değer hafızada 12 34 byte dizisi olarak saklanırsa bu da paketlenmiş BCD’dir. AAA, AAS, AAD ve AAM komutlarının hepsi ascii değerlere dönüşüm için kullanılır. Bu komutların operandı yoktur AL kaydedicisindeki değerleri dönüştürürler. AAA (Ascii Adjust After Addition) komutu toplama komutundan sonra sonucu ascii ye ayarlar. Aşağıdaki örnekleri inceleyin; 34h = 0011 0100b 35h = 0011 0101b +_______________ 69h = 0110 1001b Sonuç 09 olması gerekir AAA komutu 6 değerini siler. Sonuç AL’de 09 olarak görülür. 36 = 0011 0100 35 = 0011 0101 +_______________ 6B = 0110 1011 Sonuç 11 olması gerekir AAA komutu B değerinin yerine 1 koyar ve toplama sonucu 9 değerini aştığından AH kaydedicisine de 1 koyar, yani AX = 0101 olur. Tüm bu işlemlerden sonra OR komutunu kullanarak sonucu ASCII gösterim için hazırlayabiliriz. SUB AH, AH MOV AL, 6 ADD AL, 8 AAA OR AL,30h
; AH temizleniyor ; ilk değer AL’de ; AL + 08h işlemi yapıldı sonuç 0Eh ; AX = 0104h ; AL = 34h
34h artık ascii olarak 4 demektir. Tabi burada AH kaydedicisini de kontrol edip şayet 01 ise bu değeri de 30h ile OR işlemine tabii tutmalıyız. Bu tür işlemler için genelde ekrana tek bir karakter basma fonksiyonu kullanılır yani AL’deki değer teker teker ekrana bastırılır. Henüz mantıksal komutları görmedik bu
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
yüzden aşağıdaki programı tam olarak anlamayabilirsiniz, bu yüzden açıklamalara dikkat edin.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Şekil - Makine dilinden insan diline dönüşüm
Pekte güzel bir ekran çıktısı olmasa da bu program dönüşümleri anlamak için iyi bir örnektir. Yukarıdaki şekilde program ilk çakıştırıldığında klavyeden sırayla 2 ve 3 girilmiş ve sonuç 5 olarak ekrana basılmış. Daha sonra 7 ve 8 değerleri girilmiş ve sonuç 15 olarak ekrana basılmış. Gerçektende sonuçlar doğru :) Unutulmaması gereken bir nokta AAA komutunun C bitini etkilediğidir, şayet AAA komutundan önceki bayrakların durumu sizin için önemliyse bayrakların durumunu saklamanız gerekir. AAS (Ascii Adjust after Subtract) komutuda AAA gibi çalışır, çıkartma komutundan sonra sonucu ASCII’ye ayarlamak için kullanılır. AAS komutu sekiz bitlik AL kaydedicisinin yüksek değerlikli 4 bitini kontrol eder, şayet AF bayrağı 1 ise (başka bir deyişle bu 4 bit Ah...Fh arasında ise) AL’den 6, AH’tan 1 çıkartılır. AH’tan 1 çıkartmak normalde 00 olan AH’ı FF yapmak yani rakamın negatifliğini ayarlamak demektir. mov al,35h sub al,31h aas ; or al,34h
; ascii 5 ;5-1=4 AF=0 olduğundan bir değişiklik olmaz sonuç hala 04h ; ascii 4
sonucun negatif çıktığı bir örnek; mov sub aas
al,34h al,38h
;ascii 4 ;ascii 8 -- sonuç FCh (negatif) ; AX = FF06 (yanlış sonuç)
yukarıdaki gibi bir durumda SUB komutu ile A ve C bayrakları set (1) olur. Bu durumda sonucu doğrudan 30h ile OR işlemine tabi tutmak hatalı olacaktır. FCh sonucu desimal -4 değerine eşittir bu yüzden burada programcı OR komutu ile doğru ascii değeri ekrana yazdırmadan önce NEG komutu ile tersini alabilir. Bu durumda sonuc 4 olacaktır. Tabiki bu 4 değeri ekrana yazdırılmadan önce önüne - işareti konulmalı. mov al,34h sub al,38h jnc devam neg al devam: aas or al,30h yukarıdaki kod parçası sonuç negatif olsa da pozitif olsa da, sonucun mutlak değerini doğru bir şekilde ascii değere dönüştürür. Unutmayalım ki klavye bize ekran text moundayken daima ascii değerler verecektir, bu çarpma ve bölme işlemlerinde de problem oluşturur. Klavyeden girilen değerlere sanki 30 eklenmiş gibi geleceğinden çarpma işleminin sonucu
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
girilen değerde bir modifikasyon yapmadığımız sürece yanlış hesaplarız. Bu tür modifikasyonları yapmak için başka mantıksal komutlara ihtiyacımız olacak, bu yüzden bu komutları anlatmadan DAA, DAS, AAD ve AAM gibi komutları açıklamak istemiyorum. 80x86 KOMUT SETĐ (Bölüm 4) Mantıksal (logical) komutlar AND, OR, XOR ve NOT adıyla bilinen ve matematiksel hesaplamalarda çok kullanılan komutlardır. AND komutu Yapı olarak AND (VE) mantığı 1 ve 0’lar ile ifade edilirse; 1 1 0 0
ve ve ve ve
1 0 1 0
= = = =
1 0 0 0
sonuçlarını üretir. Bu komutu assembly programcıları genelde maskeleme işlerinde kullanırlar. Örneğin 1 byte’lık değerin 7, 6, 5, ve 4. bitlerini göz ardı etmek için aşağıdaki gibi bir program parçası yazılabilir. mov al, A5h and al, 0Fh bu işlemlerden sonra AL’in yüksek değerli 4 biti (nibble) sıfırlanacaktır yani AL binary olarak ifade edersek 0000 0101 olacaktır. Buna düşük değerlikli 4 bite dokunmadan diğer bitleri sıfırlamakta denilebilir. Bu komutu elektronikçiler çok kullanırlar, mesela paralel porttan alınacak olan verinin sadece 5 bitini kontrol etmek için; mov dx,378h ; paralel port adresi in al, dx ; bu adresten bilgiyi al and al, 0010 0000b ; 5. biti kontrol et (maskele) jnz devam ; 5. bit 1 ise "devam" a dallan . . . . devam: . ;devam komutları . Diyelim ki AL’ye IN komutu ile alınan byte 1011 0001 olsun, bu durumda; 1011 0001 0010 0000 v __________ 0010 0000
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
yukarıdaki işlem yapılır ve AND komutu bayrak kaydedicisinin Z bitini 0 olarak kurar, çünkü sonuç 0’dan farklıdır. Đşte burada maske olarak sadece 5. biti 1 olan bir byte seçilmiştir. Not: Windows XP altında paralel port’a doğrudan erişim işletim sisteminin kerneli tarafından engellendiğinden bu programı windows XP öncesi işletim sistemlerinde çalıştırabilirsiniz. IN ve OUT komutlarının kullanımı sonraki makalelerin konusudur. AND komutunun kullanım formatları; and
hedef, kaynak
and and and and and and
;hedef := hedef & kaynak
reg, reg mem, reg reg, mem reg, imm mem, imm eax/ax/al, imm
OR komutu Mantıksal veya işlemini gerçekleştirir, 1 1 0 0
| | | |
1 0 1 0
= = = =
1 1 1 0
doğruluk tablosu yukarıdaki gibidir. Kullanım formatları AND komutundaki gibidir. XOR komutu XOR olmadan şifreleme işlemleri sanırım çok zor yapılırdı. Çok fazla kullanım alanı olmakla beraber veri paketleme ve şifreleme işlemleri için hayat kurtarıcı bir komuttur. eXclusiveOR (özel veya) kelimesinin kısaltmasıdır. Doğruluk tablosu; 1 1 0 0
^ ^ ^ ^
1 0 1 0
= = = =
0 1 1 0
Ben genelde öğrencilerime bu komutun yaptığı işi anlatırken "aynılarda 0 farklılarda 1" sonucunu veren mantıksal ifade derim. XOR komutu ile şifreleme ve paketleme işlemleri için örnekler şu anda belki de sizin için biraz ağır kaçabilir, bunun yerine swap (yer değiştirme) ve kaydedici sıfırlama örneklerini XOR komutunu ile neler yapılabildiğine örnek teşkil edeceğini düşünüyorum. xor
ax,ax
; ax=0 olur ve "mov ax,0" dan kat kat hızlı çalışır
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
;AX = 1234h ve BX=9876h olsun xor xor xor
ax, bx bx, ax ax, bx
; bu 3 komut sonrasında AX = 9876h ve BX = 1234h olur. XOR komutunun kullanım formatları AND ve OR komutlarınınkiyle aynıdır. Bayrakların Durumu Yukarıda anlattığım bu üç komut bayrak kaydedicisini aşağıdaki gibi etkiler; Carry bayrağını 0 yaparlar, Overflow bayrağını 0 yaparlar Zero bayrağını şayet sonuç 0 ise 1 yaparlar ve aksi durumlarda bu bayrağı 0 yaparlar En Yüksek değerlikli bit’i Sign bayrağına kopyalarlar Parity bayrağını (sonuçtaki 1’lerin sayısı çift ise) 1 yaparlar Auxiliary carry (Ara Elde) bayrağının durumunu değiştirirler. NOT komutu NOT komutu operandının mantıksal tersini alır. Kullanım formatları aşağıdaki gibidir; not reg not mem ; BL kaydedicisinin değeri 15h olsun; not bl ;bl = 0001 0101b iken bu komuttan sonra 1110 1010b olur. NOT komutu hiçbir bayrağı etkilemez. Şimdi Uygulama Zamanı Bu makalemde sizlere örnek teşkil etmesi için konsol ekranındaki görüntüyü (tüm yazıları) önce karma karışık bir hale getiren daha sonrada tekrar orijinal haline çeviren bir programı anlatmayı düşünüyorum. Öncelikle Windows’un cmd.exe yada komut istemi ile açılan pencerenin görüntüsünün aslında B800:0000 adresinin bir yansıması olduğunu hatırlatmak isterim. Debug ile bu alana göz atalım.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Şekil 1 - Aslında ekran yansıması da hafızadaki byte’lardan ibarettir.
Konsol penceresi açılınca şayet işletim sisteminiz Windows XP ise "Microsoft Windows XP....." ile başlayan bir yazı görülür. Debug’ı çalıştırıp b800:0000 adresini ekrana döktüğümüzde bu yazı hala aynı yerindedir. B800h segmenti de burada görünen yazının bulunduğu text video alanıdır. Şekilde B800:0000 adresindeki 4Dh değerinin aslında M harfi olduğunu ve C karakterininde yine aynı segmentte bir değer olduğunu görüyorsunuz. Yukarıdaki resimdeki hafıza dökümü, en üst satırın tamamını ve sonraki satırın yarısından biraz fazlasını gözler önüne seriyor. Hafıza dökümünde tek numaralı her offsette (0001h,0003h,00F3h gibi) 07h değerini görüyorsunuz. Bu text mode özelliği olup siyah zemin üzerine beyaz yazı anlamına gelir. Gerçektende ekrandaki karakterlerin hepsi beyaz ve zemin siyah değil mi? Konsol ekranının tamamı karakterlerle dolsa, ekranda kaç karakter görürüz? Tabi ki 80x25=2000 adet. Şimdi bu karakterlerin tek tek adreslerine erişip (özellik içeren 07h byte’larını atlayarak) bunları lojik bir işlemden geçirsem ekranın o anki görüntüsü değişir, hem de bizim boş olarak tabir ettiğimiz ama aslında 20h olan yerler bile. Böyle bir durumda ekranda karman çorman anlaşılmaz bir görüntünün belirmesi olası bir durumdur. Peki değiştirdiğim her byte’ı tekrar eski haline getirebilir miyim? Bütün bunların cevabı aşağıdaki programda saklı.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Şekil 2 - 80x25 text mode da görünen ekrana müdahele.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Şekil 3 - Program çalıştırılmadan önceki durum.
Şekil 4 - Ekrandaki yazılara ne oldu böyle?
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Şekil 5 - Klavyeden bir tuşa basıldığında görüntü tekrar normale döner.
Aslında video işlemleri için daha pratik interrupt’lar mevcuttur, burada anlatmaya çalıştığım, "şayet assembly dili ile hafızanın her yerine erişebilecek deneyime sahipseniz yapamayacağınız şey yoktur" gerçeğiydi. Bu programda özellikte, text video alanının segment adresini DS kaydedicisine yüklenişini ve bu alanın kaydedici dolaylı (register indirect) adresleme modu ile sanki kendi tanımladığımız bir data alanı gibi kullanılışını incelemenizi tavsiye ederim 80x86 String Komutları (Bölüm 4) Çeşitli bilgilerin birleşerek biraya gelmelerine string denir. “Merhaba” kelimesi bir string’dir. ASSEMBLY’de stringleri kontrol edebilen komutlar vardır ve bu komutlar hafıza blokları üzerinde çalışırlar. Bu makalemizde 80 X 86 ailesinde kullanılan string komutlarını (MOVS, CMPS, LODS, SCAS, STOS ) incelemeye çalışacağız, komutlarımız byte, word ve double word dizileri üzerinde işlemler gerçekleştirmek için kullanılmaktadır. Aşağıda ilgili komutlar ve kullanım şekillerini tablo halinde görebilirsiniz.
Komut Kullanım Şekli MOVS movsb,movsw,movsd CMPS cmpsb,cmpsw,cmpsd SCAS scasb,scasw,scasd STOS stosb,stosw,stosd LODS lodsb,kodsw,lodsd
String komutlarını ile işlem yaparken, Indis registerleri olarak adlandırılan SI ve DI registerlerinden, bellekteki bir bilgiyi bir adresten başka bir adrese taşırken kullanıyoruz. SI registerine taşınacak bilgi dizisinin başlangıç adresini, DI registerine de diziyi taşımak istediğimiz belleğin başlangıç adresini yerleştirerek
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
yapmak istediğimiz işlemi gerçekleştirebiliriz. Indis registerlerinin program içeirisinde kullanım şeklini gösteren örneğimizi inceleyebilirsiniz. Byte1 db ‘a’ Byte2 db ‘*’
;byte1 için ayrılan alana ’a’ değerini koy ;byte2 için ayrılan alana ’*’ değerini koy
Movs Byte2,Byte1 Lea di,Byte2 koy Lea si,Byte1 koy
;byte2 bilgi dizisini, byte1 bilgi dizisine kopyala ;taşımak istediğimiz belleğin başlangıç adresi di registerine ;taşınacak bilgi dizisinin başlangıç adresini si registerine
Movsb ;si registerinde bulunan byte dizisini di registeri ile gösterilen adrese byte olarak taşı
String işlemlerinde kullanılan komutları daha detaylı incelemeye başlayalım; MOVS Komutu: movs komutunun kullandığı veri tipine göre farklı kullanım şekilleri vardır, data segmentinde ve ofset adresi SI rejisterinde bulunan byte / word / double word dizilerini ES ve DI registerleri ile gösterilen adrese taşır. CX registerinde tutulan deger adedince taşıma işlemi tekrarlanır.movsb ile byte dizileri, movsw ile word dizileri ve movsd ile double word dizileri üzerinde işlemler yapılır. REP: kendisinden sonra gelen string komutunun cx registerinin değeri 0 olana dek tekrarlar.
Komut
movsb
movsw
movsd
Anlamı
Đşlem
byte dizisi taşı
si’de bulunan byte dizisini, cx registerinde tutulan değer adedince di registerine taşı
word dizisi taşı
si’de bulunan word dizisini, cx registerinde tutulan değer adedince di registerine taşı
double dizisi olarak taşı
si’de bulunan double word dizisini, cx registerinde tutulan değer adedince di
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
registerine taşı CMPS Komutu: REPE, REPZ, REPNE ve REPNZ komutlarının parametresi olarak bellekteki iki stringi birbirleriyle karşılaştırma işleminde kullanılır. Stringlerden biri Data Segmentte diğeri ise Extra Segmentte tutulur. Data Segmentindeki stringin ofset adresi SI registerine, Extra segmentteki stringin ofset adresi ise DI registerine yerleştirilir. Cx registerindeki değer adadince karşılaştırma işlemi tekrarlanır. Not:REPE: eşit oduğu zaman tekrarla REPZ: zero flag “0” oldugu zaman tekrarla REPNE: eşit olmadığı zaman tekrarla REPNZ: zero flag “0” olmadıgı zaman tekrarla
Komut
cmpsb
cmpsw
cmpsd
Anlamı
Đşlem
byte dizisi karşılaştır
data segmentte ve extra segmentte tutulan byte dizisini, cx registeri adedince karşılaştır
word dizisi karşılaştır
data segmentte ve extra segmentte tutulan word dizisini, cx registeri adedince karşılaştır
double word dizisi karşılaştır
data segmentte ve extra segmentte tutulan double word dizisini, cx registeri adedince karşılaştır
LODS Komutu: Lods komutu string komutları içinde tektir. Komut öncesinde herhangi bir komut kullanmaya gerek yoktur. Ofset adresi DS:SI rejisterinde bulunan byte / word / double word dizilerini al, ax, eax registerlerine, si registerinin değerini
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
arttırarak veya azaltarak kopyalar.
Komut
Anlamı
lodsb
lodsw
lodsd
Đşlem
byte dizisi kopyala
ofset adresi ds:si registerinde bulunan byte dizisini, si registerinin değerini arttırarak, al registerine taşı
word dizisi kopyala
ofset adresi ds:si registerinde bulunan word dizisini, si registerinin değerini arttırarak, ax registerine taşı
double word dizisi kopyala
ofset adresi ds:si registerinde bulunan double word dizisini, si registerinin değerini arttırarak, eax registerine taşı
SCAS Komutu: Ofset adresi DS:SI rejisterinde bulunan byte / word / double word dizileri içerisindeki değerle al, ax, eax registerlerindeki değerleri karşılaştırarak arama yapmak için kullanılır. Scas komutu AF, Cf, OF, PF, SF ve ZF bayraklarını ayarlarak arama işlemini gerçekleştirir.
Komut
scasb
Anlamı
Đşlem
ofset adresi ds:si registerinde bulunan byte dizisi byte dizisi içerisinde ara içerisindeki değerle, al registerindeki değeri karşılaştırarak arama yap
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
scasw
scasd
word dizisi içerisinde ara
ofset adresi ds:si registerinde bulunan word dizisi içerisindeki değerle, ax registerindeki değeri karşılaştırarak arama yap
double word dizisi içerisinde ara
ofset adresi ds:si registerinde bulunan double word dizisi içerisindeki değerle, eax registerindeki değeri karşılaştırarak arama yap
STOS Komutu: Stos komutu es:di ile gösterilen adreste bulunan akümülator içerisinde belirtilen değeri saklar. Bu işlemden sonra yön bayrağının durumuna göre işlemci di registerini arttırır veya azaltır. Stos komutu byte, word yada double word üzerinde işlemler yapmak için uygundur.
Komut
stosb
stosw
stosd
Anlamı
Đşlem
al içerisinde byte dizisi sakla
es:di ile gösterilen adresteki al registerinde, istenilen byte dizisi saklanır
ax içerisinde word dizisi sakla
es:di ile gösterilen adresteki ax registerinde, istenilen word dizisi saklanır
eax içerisinde double word dizisi sakla
es:di ile gösterilen adresteki eax registerinde,
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
istenilen double word dizisi saklanır
Şekil 1 - Programın Ekran Görüntüsü.
Örneğimizde, kelime içerisindeki küçük harfler büyük harflere dönüştürülecek. Dönüştürme işleminde kucuk harf olup olmadığını anlamak için “a = ASCII 61h” oldugu için bu değer ile karşılaştırma yapacağız. Eğer harf bu değerden buyuk ise küçük harf olduğunu anlayacağız ve harfi buyuğe çevirmek için 20h değerini çıkaracağız. Harf değerimizden küçükse büyük harf olduğuna karar vereceğiz ve çıkarma işlemi yapmayacağız. Dönüştürme işleminden sonra programı terk edeceğiz. Programın ekran çıktısından da gördüğünüz gibi klavyeden ilk harf grubunun tamamı küçük harf ikinci harf grubunun bir elemanı ise büyük harf olarak girilmiştir. Sonuçta enter tuşuyla harf girişi tamamlanıp sonuç değeri elde edilmiştir. Umarım string komutlarının kullanımı konusunda faydalı olur.
.model small .stack 64 .data snc db 10,13,’ ’,’$’,10,13
; sonuc için ayrılan alan
.code mov mov mov
ax,@data ds,ax es,ax
dizi_al: mov ah,01 int 21h cmp jz
al,13 yaz
;klavyeden karakter isteniyor ;girilen al’de ;al’deki değeri enter ile karşılaştır ;enter’e basılmışsa yaz’a atla
inc
si
;si ile kelime içinde bir sonraki karaktere ilerle
cmp jg jl
al,61h degis sbt
;al içindeki değerle a=61h karşılaştır ;girilen değerden buyukse degis’e atla ;girilen değerden kucukse sbt’e atla
degis: sub
al,20h
;al değerinden 20h çıkar
sbt:
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
sub
al,0h
mov yukle
[snc+si],al
;kelime içerisinde sıradaki harfe son al değerini
jmp
dizi_al
;dizi başlangıcına atla
yaz: mov lea int
ah,09h dx,snc 21h
dosa: mov ah,4ch int 21h end
;al değerinden 0h çıkar
;ekrana snc’de tutulan değer yazılıyor
;programı terket
Aslında string komutları ile yapılan işlemler bilgisayar kullanıcılarına o kadar yakın ki, mesela msword uygulamalarında kullanılan kopyala-yapıştır gibi. Yorumlarınızı bekliyorum. Bu makalede stack (yığın) hafıza bölgesini anlatmaya çalışacağım. Stack memory geçici değerlerin saklandığı bir hafıza alanıdır. Mikroişlemci SS:SP kaydedicileri ile bu hafıza alanına erişir. SS stack alanının segment adresini tutarken SP bu alandaki offsetleri işaret eder. Stack hafıza alanını kullanmama diye bir lüksümüz yok, bu makaleye kadar stack memory ile ilgili hiç bir komut yazmasakta, bu hafıza alanını yadığımız tüm programlar kullandı. Bununla beraber stack memory’yi kendi isteklerimiz doğrultusunda da kullanabiliriz. Bunun için bir çok komut mevcuttur. PUSH, POP, CALL, RET bunlardan bazıları ve bu makalede anlatılacaklar arasındadır. LIFO Mikroişlemci stack memory’yi diğer hafıza alanlarını adreslerken kullandığı yöntemden biraz daha farklı adresler. Öncelikle stack hafıza alanına yüklenen bilgiler LIFO (Last In First Out) yani ilk giren son çıkar yada ilk yüklenen bilgi en son alınır düzenine göre sıralanırlar.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Şekil 1 - Stack (Yığın) hafıza.
Yukarıdaki şekilde kırmızı bloğu stack memory olarak düşünebilirsiniz. BU alanın büyüklüğünü kod yazarken .stack talimatı ile belirliyoruz, .stack 32, .stack 100h gibi. Geçici verilerinizi saklamak için ne kadar alana ihtiyacınız varsa o kadar stack tahsis edebilirsiniz. Yine yukarıdaki şekilde orijinal SP yığın boşken SP kaydedicisinin aldığı değerdir. Mesela .stack 32 diye direktif verdiyseniz SP=0020h ı gösterir. Yığına veri itildikçe SP’nin değeri azalır, bu değerler yığından çekildikçe SP artar. x86 mimarisinde yığın hafızaya 16 bitlik çalışma modunda 16 bitlik değerler, 32 bitlik çalışma modunda ise 32 bitlik değerler saklayabilirsiniz. PUSH ve POP komutları Stack memory’de verilerinizi saklamak için kullanılan komutlardır. Stack’ta saklanan veriler veya adres bilgileri için genelde "stack’a itilen" kelimeleri kullanılır. Stack’tan geri alınan veriler içinse "stack’tan çekilen" kelimesi kullanılır. Özetle PUSH komutu stack’a bir veriyi itmek için, POP ise verileri çekmek için kullanılır. Yığın hafıza alanını etkileyen komutların genel kullanım formatları aşağıdaki gibidir; push pop push pop push pop push pop push pusha popa
reg16 reg16 reg32 reg32 segreg segreg mem mem imm
(**) (**) (CS hariç)
(*) (*) (*)
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
pushad popad pushf popf pushfd popfd
(**) (**)
(**) (**)
(*)- Sadece 80286 ve sonrası işlemcilerde. (**)- Sadece 80386 ve sonrası işlemcilerde. En çok kullanılan komutlar PUSH ve POP komutlarıdır. PUSHA ve PUSHAD tüm kaydedicileri yığında saklamak amacıyla kullanılır, POPA ve POPAD ise saklanan bu kaydedicileri geri almak için kullanılır. PUSHF, PUSHFD bayrak kaydedicisini saklamak POPF ve POPFD ise bunları geri almak için kullanılır. Peki neden kaydedicilerin içeriğini saklamak durumunda kalalım? Programcıların programlarını düz mantıkla yazmadığını biliyorsunuz, modüler programlama teknikleri kullanılmazsa 30-40 satırdan sonra kaynak kod anlaşılmaz hale gelir. Günümüzdeki programlar onbinlerce hatta milyonlarca komut satırından meydana geliyor. Bunun için programı parçalara ayırmak, modüller hazırlamak şart. Yüksek düzeyli programlama dillerinde kullanılan, sınıf, fonksiyon gibi kavramlar hep bu amaç için türetilmiştir. Assembly dilinde CALL ve RET komutlarıda bunu amaçlar. Hatta C dilinde ve birçok programlama dilinde fonksiyon çağrımlarının assembly dilindeki tam karşılığı CALL komutudur. Đster assembly dilinde call komutunu kullanın isterseniz C dilinde bir fonksiyonu çağırın, dallanma işlemi gerçekleşmeden önce geçici parametreler stack’ta saklanır. CALL ve RET Komutları Bu komutları anlatmadan önce assembly dilinde prosedür (procedure) mantığını kavramanız lazım. Bir örnekle prosedürleri anlayalım.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Şekil 2 - Prosedürler kullanılarak yapılmış bir kaynak kod..
Kaynak kodu buradan indirebilirsiniz. Bu program bir önceki makaledeki string komutlarına örnek olmakla beraber biz burada sadeve CALL ve RET komutlarını inceleyeceğiz. Program çalıştığında komut isteminden bir yazının girilmesini ister, şayet yazıyı oluşturan karakterler büyük harfli ise bunları küçük harfe dönüştürür. Programda üç tane prosedür bulunmakta ve ana prosedüre baktığınızda klasik kodlar olan segment ayarlama ve dosa dönüşün dışında sadece CALL komutlarını görüyorsunuz. Ana prosedürden 3 kere alt prosedürlere dallanma gerçekleşiyor. Her dallanma sırasında geri dönüş adresi yığında saklanır. Alt
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
prosedürlerden dönerkende ret komutu ile bu adres geri yüklenir. Şayet dallanma bir başka segmente tapılıyorsa CS ve IP, buradaki gibi aynı segment içindeyse sadece IP yani offset adresi yığında saklanır. Offset ve segment adreslerinin 2 byte olduğunu düşünecek olursanız bunun stackın yapısınada uygun olduğunu anlarsınız. Yığın hafıza yada stackın kullanılmadığı bir program yok gibidir. Tüm kesmeler (bunlarda bir çeşit alt prosedür gibi düşünüle bilir) çağrılmadan önce, o anki CS ve IP ayrıca tüm kaydediciler yığında saklanmak zorundadır. Aksi halde geri dönüş yapıldığında alt prosedürün değiştirdiği kaydediciler ile karşılaşılır, hatta bu bile mümkün olmaz çünkü mevcut CS ve IP saklanmadan geri dönüş söz konusu değildir. Satırlar yığın hafızayı anlatmakta biraz yetersiz kalıyor olabilir, bu konuyu anlamamış olabilirsiniz diye sizlere birde video hazırladım. Yukarıdaki programın örnek alındığı videoyu buradan download edip izlediğinizde sanırım problem çözülecek :) Şunları hatırlamakta fayda var; 1- Programlar genelde 3 adet hafıza alanından oluşur, bunlar code, data ve stack tır. 2- CPU bu alanlara adresleme yaparak erişir, 3- CPU CS ve IP kaydedicilerini kodlara erişip onları işlemek için, 4- DS, SI ve DI’yı değişkenlere datalara erişmek için, 5- SS, SP ve BP’yi yığın hafıza alanına erişmek ve biçimlendirmek için kullanılır. Örnek programımızdaki ana prosedür dışındada CALL komutunu kullanabilirsiniz, alt prosedürleri istediğiniz kadar çağırabilirsiniz. Fakat bir prosedür içinden kendini çağırırken dikkatli olmalısınız, yazacağınız kodlarla programın sonsuz döngüye girmesini engellemezseniz, "memory overflow" hatasından önce "stack overflow" hatasını alırsınız.
AAM ve AAD Komutu: AAM komutu, çarpma işlemi sonucunda, AX registerinde oluşan değeri ASCII formata dönüştürme amaçlı kullanılır. Daha önce anlatılan AAA ve AAS komutlarında olduğu gibi AAM ve AAD komutlarıda operandsız yazılırlar. AH ve AL registerlerini gizli operand olarak kulanmaktadırlar. AAM komutu, çarpma işlemi ile ax registerinde oluşan değerin ardından kullanılır. Al’nin değerini desimal 10 ile böler, elde edilen değeri ah’a aktarır. Bölümden kalan değeride tekrar al’ye aktarır.Yapılan işlemin sonucunda, elde edilen sayının sol dijiti ah’da, sağ dijiti ise al’de yer alır.AAM komutu, parity, sign ve zero flaglarını al’ye aktardığı değere göre uygun şekilde etkiler. Örneğimizde mul komutu, çarpım sonucu olan 00 51 değerini ax registerine aktarırken, aam komutuda bu değeri 00 81 olarak ASCII formatına dönüştürmüştür. mov al,09 mov bl,09 mul bl aam
;al’nin değeri 9 ;bl’nin değeri 9 ;ax = 00 51h ;ax = 08 01h
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
51h = 0101 I. adım 0001b al registerinin _________________ değerini = 08h 0Ah’a böl 0Ah = 0000 0Ah=d’10’ 1010b II.adım bölümün sonuç değerini ah’a , kalan’ı al’ye aktar III.adım Sonuç
ah = 08h al = 01h
ax = 0801h
AAD komutu ise bölme işleminde bölünenin sonucunda, AX registerinde oluşan değeri ASCII formata dönüştürme amaçlı kullanılır.AAD komutu, bölünen değerin yüksek seviyeli kısmını (ah registerinin içeriğini) desimal 10 ile çarpar ve al (düşük seviyeli kısmı) ile toplayarak al içerisine aktarır. Yapılan işlem sonucunda ah registeri sıfırlanır, al registerinde bulunan BCD sayısı, başka bir BCD sayısı ile bölünebilir duruma getirilmiş olur. AAD komutu, parity, sign ve zero flaglarını al’ye aktardığı değere göre uygun şekilde etkiler.
Örneğimizde desimal 35 sayısı, desimal 5’e bölünmekte ve sonuç yine desimal olarak elde edilmektedir. AAD komutunu kullanarak, ax üzerine paketlenmemiş olarak aktarılan sayıyı, bölme işlemi için uygun hale getiriyoruz. mov ax, 0305 ;ax = 03 05 aad ;ax = 00 23 mov bl,05 ; bl’ye al değerini koy div bl ;ax = 00 07 I. adım 03h = 0000 0011b ah 0Ah = 0000 1010b registerinin *_________________ içeriğini 1Eh = 0001 1110b 0Ah ile çarp 0Ah=d’10’ II.adım çarpım 1Eh = 0001 1110b sonucundaki 05h = 0000 0101b değere +_________________ al’nin 23h = 0010 0011b değerini ekle ve al’ye yaz III.adım Sonuç
23h = 0010 0011b _________________ =7 05h = 0000 0101b
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
DAA ve DAS komutu: DAA komutu ile yapılan toplama işlemi sonucunda her bir byte’da iki adet desimal dijit bulunur. ADD komutunun arkasından kullanılan DAA komutu, al’nin içeriği olan değere ve AF (auxilary flag) ile CF (carry flag)’nin durumuna bağlı olarak iki farklı şekilde işleme sokulur. I. Durum: al registerinin düşük seviyeli 4 biti 9’dan büyük veya AF set ise, al registeri 6 ile toplanarak, AF set edilir. II.Durum: al registerinin yüksek seviyeli 4 biti 9’dan büyük veya CF set ise, al registeri ile 60h toplanarak CF set edilir. Örneğimizde 9 ve 5 değerlerini toplayarak komutumuzla istenilen sonucu elde edeceğiz. mov al,09 ; al = 9 add al,05 ; al = 0E daa ; al =14
I. adım 09h = 0000 1001b al 05h = 0000 1001b registerlerini +__________________ topla 0Eh = 0000 1110b II.adım al registerinin düşük 0Eh = 0000 1110b seviyeli biti 06h = 0000 0110b 0’dan küçük olduğu için 6 +_________________ değerini 14h = 0001 0100b ekledik ve sonuç değere ulaştık
DAS komutu ile yapılan çıkartma işleminin al registeri üzerindeki sonucuna ve auxuiliary ile carry flaglarının durumuna bağlı olarak iki farklı şekilde kullanılır. I. Durum: al registerinin düşük seviyeli 4 biti 9’dan büyük veya AF set ise, al registerinden 6 çıkartılarak, AF set edilir. II.Durum: al registerinin yüksek seviyeli 4 biti 9’dan büyük veya CF set ise, al registerinden 60h çıkartılarak CF set edilir. DAS komutu, auxiliary, carry, parity, sign ve zero flaglarını etkiler. Örneğimizde, desimal 13 ’den 5 ’i çıkartma işlemini al registerinde yaptığımız için sonuç 0E oluyor. al registerindeki değerin desimal sayı olması için çıkartma komutunun ardından DAS düzenleyici komutu kullanılıyor. mov al,13 ;al=13h mov bl,05 ;bl=05h sub al,bl ;al = 0Eh das ;al = 08
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
I. adım 13h = 0001 0011b al 05h = 0000 1001b registerindeki değerden bl __________________ registerindeki 0Eh = 0000 1110b değeri çıkart II.adım al registerinin 0Eh = 0000 1110b düşük seviyeli biti 06h = 0000 0110b 0’dan küçük olduğu için 6 __________________ 08h = 0000 1000b değerini çıkardık ve sonuç değere ulaştık CBW ve CWD Komutu: CBW:Byte’ın worde çevrilmesi. CWD:Word’un double worde çevrilmesi Aritmetik işlemler sırasında operandlar farklı uzunluklara (byte,word veya double word) sahip olabilirler. Bu gibi durumlarda, işlem öncesinde operandların uzunluklarının düzenlenmesi gerekir.Küçük uzunluğa sahip operandın uzunluğunu, büyük uzunluğa sahip operandın uzunluğuna denkleştirilmektedir.Đşaretsiz sayılarla temsil edilen operandlar, rahat bir şekilde düzenlenir. Fakat işaretli sayılarla temsil edilen operandlarda değişim o kadar rahat olmamaktadır. Đşaretli sayıları içeren operandların uzunluklarının düzenlenmesi için CBW ve CWD komutları geliştirilmiştir. CBW komutu al ve ah, CWD komutu ise ax ve dx’i gizli operand olarak kullanırlar. CBW komutu, al içerisindeki işaretli sayının işaret bitini ah’ın tüm bitlerine, CWD komutu da ax içindeki sayının işaret bitinin değerini dx’in tüm bitlerine aktararak sonuca ulaşmamızı sağlar.
TEST Komutu: Test komutunun çalışma prensibi ve flagları etkileme biçimide dahil olmak üzere And ile aynıdır. Farklı olarak, komut ile birlikte kullanılan operandların değerlerini değiştirmez. Diğer lojik komutlarında olduğu gibi, carry, owerflow bayrakları test işleminin sonrasında reset edilirler, auxiliary dışındaki diğer bayraklarda işlem sonucuna uygun şekilde etkileneceklerdir. Farkı olmamasına rağmen, hangi durumda and, hangi durumda test komutunu kullanmalıyız sorusu aklımıza gelecektir. Örneğimizi inceleyelim;
I. Durum
and al,01
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
jz * ... ... test al,01 jz * ... ...
II.Durum
I. ve II. Durumda, al registerinin değeri desimal 1 ile karşılaştırılmaktadır; al registerindeki değer desimal 1’e eşit ise I.Durumda: al’nin değeri korunacak ve zero bayrağının reset durumuna gelmesine sebeb olacak, bu sayede jz’yi izleyen komut ile program devam etmiş olacaktır. Fakat değer 1’den farklı ise sonuç 0 olarak al’ye yerleştirilecek ve zero bayrağı set edilecektir, sonucunda ise * yerine kullanılan adrese dallanılacak ve program bu adreste bulunan komut ile sürecektir. Fakat al’nin değeri 1’e eşit değil ve başka işlemler için bize gerekli ise sorun çıkacaktır.Sonuçtan da anlaşıldığı gibi, al registerinin karşılaştırma öncesi değeri, karşılaştırma sonrası adımlarda da gerekli ise test komutunu tercih etmeliyiz. SHR ve SHL Komutu: SHR Komutu ile, soldaki operandın en düşük seviyeli biti carry bayrağına kopyalanır ve operandın tüm bitleri sağa doğru 1’er bit kayar. En soldaki bitin değeri 0 yapılır. Bu işlem SHR ile verilen ikinci operandın değeri kadar tekrarlanır. Örneğimizde, 30h değerini iki kere 1’er biti sağa kaydırarak sonucun nasıl değiştiğini inceleyeceğiz. mov al,30h shr al,01 shr al,01
;al=30h ;al=18h ;al=0Ch
Şekil 1 - Shr komutu ile bitlerin sağa kaydırılması
Adımlar
Hexadesimal Binary
Desimal CF
I. adım: al’ye 30h al=30h değerini koy
00110000 48
-
II.adım: değerleri 1 bit sağa al=18h kaydır, en düşük seviyeli biti
00011000 24
0
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
CF’na taşı III.adım: değerleri 1 bit sağa kaydır, al=0Ch en düşük seviyeli biti CF’na taşı
00001100 12
0
Programda da görüldüğü gibi her bir sağa kaydırma işlemi ile sayıyı ikiye bölmektedir.Her bir shr işlemi sonrasında carry bayrağı 0 ise çift sayı, 1 ise tek sayıdır. SHL Komutu ile, soldaki operandın en yüksek seviyeli biti carry bayrağına kopyalanır ve operandın tüm bitleri sola doğru 1’er bit kayar. En soldaki bitin değeri 0 yapılır. Bu işlem SHL ile verilen ikinci operandın değeri kadar tekrarlanır. Örneğimizde 0Ch değerini iki kere 1’er bit sola kaydırarak sonuç üzerindeki değişiklikleri inceleyeceğiz. mov al,0Ch shr al,01 shr al,01
;al=0Ch ;al=18h ;al=30h
Şekil 2 - Shl komutu ile bitlerin sola kaydırılması
Adımlar
Hexadesimal Binary
Desimal CF
I. adım: al’ye 0Ch al=0Ch değerini koy
00001100 12
-
II.adım: değerleri 1 bit sola kaydır, al=18h en yüksek seviyeli biti CF’na taşı
00011000 24
0
III.adım: değerleri al=0Ch 1 bit
00110000 48
0
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
sola kaydır, en yüksek seviyeli biti CF’na taşı Programda da görüldüğü gibi her bir sola kaydırma işlemi ile sayıyı iki ile çarpmış oluyoruz. RCR ve RCL Komutu: RCR Komutu, işleme tabi tutulan bitlerin herbirini bir pozisyon sağa kaydırır. Düşük seviyeli bit pozisyonundaki değer carry bayrağına, carry bayrağının değeride yüksek seviyeli bit pozisyonuna aktarılır. Đşlem ikinci operand değeri kadar tekrarlanır.
Şekil 3 - Rcr komutu ile bitlerin sağa kaydırılması
RCL Komutu, işleme tabi tutulan bitlerin herbirini bir pozisyon sola kaydırır. Carry flağının değeri düşük seviyeli bit pozisyonuna, yüksek seviyeli bit pozisyonundaki değer ise carry flağına aktarılır. Đşlem ikinci operand değeri kadar tekrarlanır.
Şekil 4 - Rcl komutu ile bitlerin sola kaydırılması
Şekil 5 - Programın ekran görüntüsü
Bu bölümde anlattığımız aam,das,shr ve rcl komutlarını kullanarak örnek bir program hazırladım. aam komutu kullanılan prosedürde shr komutunu işleme katmak istediğimiz al registerinin içeriğini bir bit sağa kaydırmak için, das ile ilgili prosedürde ise rcl komutuyla al registerinin içeriğini bir bit sola kaydırdım.
.model small .stack 64 .data aamsonuc db ’00$’,10,13 dassonuc db ’00$’
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
knm db 10,13,’$’ ;.................................................... .code mov ax,@data mov ds,ax mov es,ax call aamornek call dasornek ;.................................................... aamornek proc mov al,12h shr al,01 al=9h
;al registerinin içeriğini bir bit sağa kaydır,
mov bl,9h mul bl aam
;bl=9h ;ax=51h ;ax=81h ;al ve ah değerlerini ascii karakterlere çevir.
add ax,3030h mov byte ptr [aamsonuc],ah mov byte ptr [aamsonuc+1],al yerleştir.
;ax registerini sonuc dizisi içerisine
mov ah,09h lea dx,aamsonuc int 21h mov ah,09h lea dx,knm int 21h
;sonucu yaz
;alt satıra geç
aamornek endp ;.................................................... dasornek proc mov al,28h rcr al,01 al=14h mov bl,05h sub al,bl das
;al registerinin içeriğini bir bit sola kaydır,
;bl=5h ;al=Fh ;al=9h
add al,30h
;al değerini ascii karaktere çevir.
mov ah,02h mov dl,al int 21h
;sonucu yaz
dasornek endp ;....................................................
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
mov ah,4ch int 21h ;programdan çıkış end ;.................................................... Programımız, anlatılan komutların sağlaması olarak da değerlendirilebilir. Ben kodu çalıştırdım ve gördüm ki kullandığım komutlar işini başarıyla gerçekleştiriyor. Makalemizde aritmetik lojik komutların çalışma şekillerini, bayraklar üzerindeki etkilerini anlatmaya çalıştım. Benzer özelliklere sahip komutların kullanılması sizlerin tercihine kalmış, gerçekleştireceğiniz uygulamalar ve elde etmek istediğiniz performans doğrultusunda ihtiyacınıza en uygun olanı belirleyebilirsiniz.
Assembly kodlarımızı farklı programlama dilleri içerisinde de kullanabiliriz. Daha önce anlatılan makalelerde DOS’ta DEBUG ve TASM’ı kullandık. Bu bölümde ise, anlatacağımız kodları pascal ve c++ builder içerisinde kullanacağız. Sadece assembly komutlarını kullanarak programlar geliştirmek kodlar için kullanılan satır sayısı arttıktan sonra zor olmaya başladığından farklı programlama dilleri içerisinde assembly kodlarını alt programlar olarak yazmak daha avantajlı bir yol olur. Bu sayede temelden bize ait olan çalışma mantığını bildiğimiz istediğimiz özelliklere sahip fonksiyonlar geliştirme fırsatımız oluruz.
Şekil 1’de Turbo Pascal içerisindeki ASM....END; blokları arasında, Şekil 2’de C++ Builder 6.0 içerisindeki asm{...} blokları arasında tasm’da olduğu şekliyle kodlarımızı yerleştirebiliyoruz.
Turbo Pascal 7.0 içerisinde assembly kodlarının yerleştirildiği bloğun yapısı; _____________________________________________________________________________________ _______________________
şekil 1: Pascal içerisindeki assembly bölümü _____________________________________________________________________________________ _______________________
C++ Builder 6.0 içerisinde assembly kodlarının yerleştirildiği bloğun yapısı; _____________________________________________________________________________________ _______________________
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
şekil 2: C ++ Builder içersindeki assembly bölümü _____________________________________________________________________________________ _______________________
Şekil 1 ve 2’deki assembly blokları içerisinde, iki sayının toplanması işleminin nasıl gerçekleştiğine bakalım: Turbo Pascal 7.0’da ASM...END; ve C++ Builder 6.0’da ASM{...} blokları arasında, komut satırından girilen değerler MOV komutu ile AL registerine aktarıldıktan sonra ADD komutuyla toplama işlemini gerçekleştirip TOPLAM değişkenine AL registerinin son değeri olarak aktarıyoruz, assembly bloğu dışında programlama dilinin fonksiyonları ile sonucu görüntülüyoruz.
şekil 3: Turbo Pascal 7.0 ile assembly kod örneği
şekil 4: Turbo Pascal 7.0 Ekran Görüntüsü
C++ Builder içerisinde kullanacağımız assembly bloğu dışındaki component ve fonksiyonları inceleyelim. C ++ Builder Componentleri : Etiket (TLABEL): Form içerisinde bulunan Metin Kutusu’na ne tür değer girileceğini belirtmek için kullanılmaktadır. Metin Kutusu (TEdit): Kullanıcının bilgi girişi yapması ya da elde edilen sonucun görüntülenmesi için kullanılır.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Komut Düğmesi (TButton): Kullanıcın programda, gerçekleştireceği uygulamayı başlatan yada bitiren kontroldür. Assembly Bloğu dışında kullandığımız C ++ Builder Fonksiyonları: Fonksiyon Anlamı
Kullanım Şekli
StrToInt
Metin olarak girilen StrToInt(Edit1değeri >Text); sayıya dönüştürür.
IntToStr
Sayı olarak girilmiş IntToStr(Edit1değeri >Text); metine dönüştürür.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
şekil 5: C ++ Builder ile assembly kod örneği
şekil 6: C ++ Builder 6.0 Ekran Görüntüsü
C++ Builder 6.0 ile gerçekleştirdiğimiz örnek kodu buradan , Turbo Pascal 7.0 ile gerçekleştirdiğimiz örnek kodu buradan indirebilirsiniz. Makalemizde , assembly’nin diğer diller içerisinde nasıl kullanılacağını en basit haliyle anlatabilmek için kısa kodlar kullandım, daha gelişmiş kodları
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
gerçekleştirebilmeniz için sizlere başlangıç olması açısından önemli. Uzak mesafeler katedebilmek için küçük adımlar atmak gerekiyor. Adımlar küçük yol uzun, önemli olan hedefe ulaşmayı istemek.Herkese iyi çalışmalar... Makalemizde, 80X86 komut sistemine ait, CMPXCHG, CMPXCHG8B, SAL, SAR, SHLD, SHRD, ROL, ROR, BT, BTS, BTR, BTC, BSF, BSR aritmetik lojik komutlarını açıklamaya devam edeceğiz.
Mikroişlemci içinde yapılan işlemlerin durumlarını programcıya bildiren bir kaydedici mevcuttur. Tüm karar alma mekanizmaları bu kaydedicide gösterilen sonuçlar baz alınarak yapılır. ĐŞLEMCĐ DURUM KAYDEDĐCĐSĐ (PROCESSOR STATUS REGISTER) Diyelim ki klavyeden girilen iki adet değişken var bunlar eşit ise ekrana "eşit" eşit değilse "eşit değil" yazdırmak istiyoruz. Bunu üst seviye programlama dillerinde basit bir şekilde if-else mantığıyla yapabilirsiniz. Peki mikroişlemci bu değerlerin eşit olup olmadığını nasıl gösterecek bize. Cevabı basit bu değerlerin farkını alarak, sonuç sıfırsa girilen değerler eşittir aksi taktirde eşit değildir. Đyi ama assembly programcısı bu sonuç sıfır değerini nereden okuyacak? Bir işlemin sonucunun sıfır, negatif, pozitif veya elde ürettiği veya işlemin gerçekleştirilmesi için borç alındığı gibi durumları gösteren kaydediciye işlemci durum kaydedicisi diyoruz. Turbo debugger ile işlemci durum kaydedicisinin içeriğini gözlemleyebilirsiniz.
Şekil 1 - 8086’da işlemci durum kaydedicisi.
Şekilde kırmızı çerçeve içine alınmış kısım işlemci durum kaydedicisini bitsel olarak gösteriyor. Diğer kaydedicilerin aksine işlemci durum kaydedicisi programcı için bitsel olarak anlamlıdır. Ve bu bitlerin her birinin bir adı vardır. Örneğin C biti Carry yani taşıma biti veya Z biti Zero yani sıfır biti’dir. Terminolojide genellikle bu bitler "flag" yani bayrak olarak adlandırılır. Aslında
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
assembly programcıları işlemci durum kaydedicisi yerine bayrak kaydedicisi terimini daha çok kullanırlar. Örneğin Z için Zero Flag yani sıfır bayrağı denir. 8086, 80286, 80386, 80486 ve Pentium işlemcilerin bayrak kaydedicileri biraz farklıdır, daha doğrusu işlemciler geliştikçe ek bayraklar eklenmiştir, mesela 80286’dan itibaren işletim sistemlerinin yazımı için gerekli olan ayrıcalıklı mod bayrağı eklenmiştir. Yukarıdaki şekilde 8086’nın 8 bitlik bayrak kaydedicisini görmektesiniz, aşağıdaki şekilde ise 8086’dan 80486’ya kadar olan işlemcilerin sahip olduğu bayrakları görebilirsiniz.
Şekil 2 - x86 işlemci durum (bayrak) kaydedicisi.
Bayrak kaydedicisindeki her bir bitin 1 olma durumuna SET, 0 olma durumuna da RESET denir. Đşlemcinin ürettiği sonuçlar hakkında programcıyı uyaran bayrak kaydedicisindeki durumlar görecelidir. Örneğin carry (taşıma) bayrağı 1 olduğunda bazen işlemin elde ürettiğini bazen de borç alındığını ifade edebilir. Bazen de iki bayrağın durumu birlikte değerlendirilerek bazı sonuçlara varılır. Örneğin; MOV AX, 0FFFEh ; FFFE = 65534 işaretsiz sayıyı temsil ederse ADD AX, 3 ; C = 1, O = 0 olur ve bu bir boyut taşmasını gösterir. Bu örneği biraz daha açıklayacağım fakat daha önce matematik dersinde gördüğümüz bazı olayları hatırlatmak istiyorum. Hani bir fonksiyonun çözümü için sorularda "her x gerçel sayısı için" gibi ifadeler geçer ya, işte bu tür kabullenmeler assembly dilinde çok önemlidir. Çünkü 1 ve 0’ları ancak bu
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
şekilde yorumlayabilirsiniz, yani baştan evrensel kümenizi kafanızda tanımlamanız gerekir. Bu örnekte de FFFE=65534 değerini gösterirse diye başlıyoruz işe, nitekim FFFE işaretli sayı olarak kabul edilseydi -2 değerini gösterecekti. Ama burada işaretli değil sadece pozitif tamsayılarla işlem yapıldığı varsayılıyor. Bu işlemin sonucu AX kaydedicisinde 0001 olarak görülür. Đşlemci sayıların işaretli veya işaretsiz olduğuna bakmadan FFFE ye 3 ekler ve FFFE değeri önce FFFF sonra 0000 ve daha sonrada 0001 olur. Tabi ki 65534 + 3, işleminin sonucu 1’e eşit değildir. Đşlemin doğru sonucu olan 65537 değeri 2 byte’lık AX kaydedicisinin içine sığmadığından bu sonuç üretilmiştir. Bu toplama işlemi sürecinde sayı FFFF’den 0000’a dönerken C=1 olur, bunu elde olarak düşünebilirsiniz (tabi ki pozitif tamsayılar kümesi baz alınarak işlem yapılıyorsa) Aslında 0001 sonucunun başına bu elde getirildiğinde yani sonuç C biti ile birlikte okunursa 10001 olur ve bu gerçektende 65537 değerine eşittir. Fakat programcı doğal olarak AX kaydedicisini içindeki değeri yani 0001’i sonuç olarak alacaktır. Burada sonuç anlamlı aralık olan 0...65535 arasında olduğundan O=0 olmuştur. Özetle sonuç anlamlı aralıkta yanlış bir değerdir, bu durumu programcı iki bayrağa birden bakarak anlayabilir. Yukarıdaki gibi bir durumda programcı işlem yapıldıktan sonra bazı özel komutlarla C ve O (overflow-taşma) bitlerinide kontrol eder ve bu bitler sırasıyla 1 ve 0 ise bir boyut taşması olmuş demektir. Bu durumda programcı kullanıcı ekranına "sonuç çok büyük" gibi bir mesaj gönderebilir. Hesap makinelerinin belli bir değere kadar işlem yapabildiğini hatırlayın. Bu örnek için en başta FFFE işaretli bir saydır deseydik herhangi bir taşma söz konusu olmayacaktı. Đşlemci yine aynı sonucu üretecek fakat bu sefer programcı bunu -2+3 olarak değerlendireceğinden sonuç doğal olarak 1 ve AX’te 0001 değeri doğru sonuç olacaktı. Yine C=1 ve O=0 olsa da programcı için bu durum bir taşmayı göstermeyecektir. Đşte assembly programlama dili matematiğin bu basit gibi görünen temellerine bağlıdır. Hafızadaki 1 ve 0’ları nasıl yorumlarsanız öyle şekil alırlar. Bazen bu 1 ve 0’lar bir resim, bazen bir mp3 bazen de bir kelime oluveririler. Hani matrix filminde herkes kodlara bakıyor ama sadece Neo olayı idrak edebiliyordu ya, işte o misal :)
Şekil 3 - bilgisayarlar aslında 1’ler ve 0’lar dünyasıdır.
8086 için bayrak kaydedicisi 16 bitliktir ve bu 16 bit’te sadece 9 adet bayrak mevcuttur. 386 ve sonrası işlemcilerde ise bayrak kaydedicisi 32 bitliktir ve bu 32 bit’in her biri bir bayrağı temsil etmez, aralarda hiçbir şeyi ifade etmeyen boş bit’ler mevcuttur. Şu anda gerçek mod programları yani 8086 için programlar yazdığımızdan bu 9 adet bayrağın ne anlama geldiklerini incelememiz yeterlidir. Overflow Flag (OF) (Taşma Bayrağı): Đşaret taşması olduğunda olduğunda set (1) olur, mesela 1 byte’lık işaretli sayılarda 100+50=150’dir, fakat sonuç -
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
128...127 aralığında değildir. Direction Flag (DF) (Yön bayrağı): String işlemlerini yapan komutlar için kullanılır. 1 olduğunda index kaydedicisi otomatik olarak azalır. 0 ise index kaydedicisi otomatik olarak artar. (mesela bir string hafızanın bir bölgesinden başka bir bölgeye kopyalanırken işlemin stringin sonundanmı yoksa başındanmı yapılacağını D flag ile belirleyebilirsiniz) Interrupt-enable Flag (IF) (Kesme Yetki bayrağı): 1 olduğunda maskelenebilir kesmeler kullanılabilir. (kesmeler hakkında ayrıntı sonraki makalelerimizde!) Single-step Flag or Trace (TF) (Tek Adım veya Đzleme Bayrağı): Genelde debugger programları için kullanılır, 1 olduğunda bir anda sadece 1 komut işlenir. Sign Flag (SF) (Đşaret Bayrağı): MSB biti (en soldaki bit) 1 olduğunda bu bayrakta 1 olur. Zero Flag (ZF) (Sıfır Bayrağı): Sonuç sıfır olduğunda 1 olur. Auxiliary carry Flag (AF) (Ara Elde Bayrağı): AL kaydedicisinin 0-3 arasındaki bitlerinde yapılan işlemde elde veya borç üretilirse 1 olur. Parity Flag (PF) (Eşlik Bayrağı): 1 olduğunda sonucun düşük değerlikli baytındaki (mesela 127Fh gibi word boyutunda bir sonuç üretilmişse bunun 7F kısmı için) 1’lerin sayısının çift olduğunu gösterir. Carry Flag (CF) (Taşıma Bayrağı): 1 olduğunda elde üretildiğini veya borç alındığını belirtir. Bayrak Kaydedicisini özetleyecek olursak, 1- Đşlemcinin yaptığı işlemlerin özel durumlarını gösterirler 2- Bazı işlemler yapılmadan önce bazı bayrakların 1 yada 0 yapılması gerekebilir 3- Programcılar bazı işlemleri anlayabilmeleri için bazen birden fazla bayrağın birlikte ürettikleri durumları göz önüne almak zorundadırlar. Uzun bir süre bu bayraklardan OF, SF, ZF, CF olanları bizim için önemli olacak. Bayrakların geri kalan kısımları ve bayrakları etkilen komutları sonraki makalelerimizde yeri geldikçe açıklamaya çalışacağım. Đsterseniz şimdi bayrak kaydedicisi ve bayraklarla ilgili kafanızda daha belirgin bir şeklin oluşabilmesi için makalemizin başında bahsettiğimiz örneği hayata geçirelim. Şimdi yapacağımız programda kullanıcı klavyeden 2 adet değer girecek ve bu değerler eşitse ekrana "Girilen Degerler Esittir" yazacak değilse "Girilen Degerler Esit Degildir" yazacak. Đlk bakışta kullanıcı sayıları bilmiyor mu kardeşim ne gerek var böyle bir programa diyebilirsiniz :) Fakat değerlerin klavyeden değilde paralel porttan alındığını ve bu port’un bir klimaya bağlı olduğunu ve hava sıcaklığının 24C0 ye eşit olup olmadığını test eden ve eşitse klimayı durduran değilse çalıştıran bir programında bundan pek farklı olmayacağını tahmin edebilirsiniz. Bu örnekte henüz işlemediğimiz konulardan olan "ekran ve klavye işlemleri" ve
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
"şartlı dallanmalar" konularını kapsayan komutlar kullanacağız. Bunların ayrıntılarını daha sonra anlatacağımdan şimdilik fazla önemsemeyin. Bu komutlar aşağıdaki gibidir.
MOV AH,01 INT 21h
Bu komutlardan sonra program kullanıcı giriş yapana kadar durur ve kullanıcı klavyeden değer girdiğinde o değerin ASCII kod karşılığı AL kaydedicisine yerleştirilir.
JMP HedefAdres
Bu komut ile hedef adrese dallanma gerçekleştirilir.
JE HedefAdres
Bu komut ile hedef adrese Z bayrağı 1 ise dallanma gerçekleştirilir.
CMP BL, AL
Bu komut BL ile AL kaydedicisini karşılaştırır.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Şekil 4 - Klavyeden girilen değerleri test eden program.
Yukarıdaki programın kaynak kodlarını buradan download edin ve TASM ile
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
derleyin. Bu programı çalıştırdığınızda sizden iki adet değer girmenizi isteyecek, değerleri girerken enter tuşuna basmanıza gerek yok. Şayet girilen değerler eşitse ekranda "Girilen Degerler Esittir" değilse "Girilen Degerler Esit Degildir" mesajını göreceksiniz.
Şekil 5 - Programın ekran çıktısı.
Değerlerin eşit olup olmama durumunu belirleyen işlemci durum kaydedicisinin Z biti (Z bayrağı) dir. Bu bayrağın durumu karşılaştırma komutundan sonra girilen değerlere göre 1 yada 0 olur. Şayet girilen değerler eşit ise, eşit olan bu değerler karşılaştırılır ve sonuç sıfır elde edilir, bu durumda Z=1 olur. JE komutu ise ancak Z=1 olduğunda, operandında belirtilen adrese dallanma işlemini geçekleştirir. Aksi durumda işlemci klasik yolunu izler ve bir sonraki komutu işler.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Şekil 6 - Z=1 olduğunda 0024h offset adresine dallanılacak.
Klavyeden farklı değerler girildiğinde ise, karşılaştırma işleminin sonucunda z=0 olur. Bu durumda dallanma gerçekleşmez ve işlemci normal seyrini sürdürerek bir sonraki komutu işler.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Şekil 7 - Z=0 olduğunda dallanma gerçekleşmeyecek, program sonraki komutu takip edecektir.
Bayrakların durumları bayrakları etkileyen bir komut işlendiğinde değişir, her komut bayrakları etkilemez. Burada CMP komutu 6 adet bayrağı etkiliyor. Hangi komutun hangi bayrakları etkilediğine Intel’in komut setinden bakabilirsiniz. Komut setinden CMP komutunu bulursanız bu 6 bayrağın neler olduğunu görebilirsiniz.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
Şekil 8 - CMP ile AF, CF, OF, PF, SF, ZF bayrakları etkilenir.
Đsterseniz Şekil7 deki etkilenen tüm bayrakları ve neden dolayı 1 yada 0 olduklarını inceleyelim. Burada yapılan işlem "CMP BL, AL" dir ve BL=33, AL=34’dür. BL-AL yani 33h-34h işlemini yapan CMP komutu aşağıdaki nedenlerden dolayı bu bayrakları etkilemiştir. 1- 33-34 işlemi için borç gerektiğinden (Küçük sayı büyük sayıdan çıkartılıyor) CF=1, 2- Bu işlemin sonucu sıfır olmadığından ZF=0, 3- Đşlemin sonucu negatif olduğundan SF=1, 4- Đşlem sonucunda bir işaret taşması olmadığından OF=0, 5- Sonuçta elde edilen -1 yani FFh, başka bir deyişle 11111111b değerindeki 1’lerin sayısı sekiz adet yani çift olduğundan PF=1,
6- Binary olarak çıkartma işleminde daha 2. bitte 0’dan 1 çıkarılırken
borç alındığından AF=1 olmuştur.
Bizim programımızda sadece Z bayrağının durumu önemli olduğundan bu bayrağın durumuna göre programın akışını kontrol eden JE komutunu kullandık. Aslında burada JE komutu ile ilerleyen makalelerde ele alacağımız if-else mantığını oluşturduk. Gördüğünüz gibi program akış kontrolünde bayraklar hayati önem taşır. Đşlemci durum kaydedicisinde bulunan bu bayraklar şu anda aklınızın alamayacağı bir çok işlemde bizlere yardımcı olacak. Bu konuyu daha iyi anlamak için Turbo Debugger’da programın her komutunu adım-adım çalıştırın ve komut setinden faydalanarak, çalıştırdığınız komutun etkilediği bayrak varsa bunun nedenini araştırın.
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN
HAZIRLAYAN : 05BP2030 ETHEM DEMĐRKIRAN