Assembler' a Giriş - I
İlksöz
Burada herkezin anlayabileceği, uygulayabileceği, Türkçe, örnekli bir anlatım yapmaya çalıştım. Amacım Assembler dilini herkezin Türkçe bir kaynaktan ve yeterince tecrübesi olan bir ağızdan öğrenmesi. Maalesef ülkemizdeki bu konudaki kitapların çok azı hariç büyük çoğunluğu orjinalden çeviriye dayanıyor. Doğal olarak okuyucu uygulamada ve anlamada zorluk çekiyor. Ayrıca bazı terimleri Türkçe karşılıklarıda pek tutarlı olmuyor. Internette ise Ingilizce forumlar ve siteler bulunmasına rağmen teknik terimler ve Assembler dilinin diğer hazır dillere benzememesinden dolayı öğrenilme şansı azalıyor.
Assembler dili kesinlikle zor değildir. Bu dili öğrenemek için bol bol deneme yapmanız gerekiyor. Zamandan, sosyal yaşamdan biraz fedakarlık etmeniz gerekebilir.
Herkeze iyi çalışmalar ve başarılar dilerim. En iyiye ve en doğruya doğru hep beraber kürek çekelim...
CYDONIA
Giriş
Assembler merkezi işlemci olan bütün sistemlerde ve işletim sistemlerinde "kullanıcı - program - makina" iletişimi sağlayan ve sembolik komutlar sayesinde programlamayı sayılar düzeyinden sembollere çıkaran bir dildir. Normalde merkezi işlemci (CPU) komutları 1 ve 0 olarak anlar ve buna göre yapılması gereken işlemleri yerine getirir.
ASCII mantığı
Herhangi bir EXE dosyaya bir editor ile baktığımız zaman anlamsız ve karmaşık birçok semboller ve karakterler görürüz. Bunlar o an orada bulunan sayılara karşılık gelen ve ASCII olarak adlandırılan sembollerdir. Bu semboller hem sayıları hem büyük/küçük harfleri hemde birçok işareti içerir. ASCII tablosu 256 işaretten meydana gelir ve her sayı karşılığı bir semboldur. İlk 32 karakter kontrol karakterleri adı verilen ve görülmeyen karakterlerdir.Bunlar yazıcı, string gibi işlemlerde tek başlarına yada birden fazla görev alırlar. Örneğin 7 sayısı ASCII karakterlerinde 'BEEP' anlamına gelir ve programınızda eğer 'deneme yazısı',7,0 şeklinde kullanırsanız yazı sonrası bir 'beep' sesi duyarsınız. Yada 10 ve ardından 13 kullanırsanız yazınız bir satır aşağıya inip paragrafın en başına gelir. Örneğin: 'deneme yazısı',10,13,0
48 - 58 arası ise sayılardır. Buna göre:
48 = '0'
49 = '1'
50 = '2' ....
sayı şekillerine denk gelir ve bu standarttır.
Büyük harfler 65' den başlar 90' a kadar devam eder. Aynı şekilde küçük harflerde 97 ile 122 arasındadır. Bundan sonra 256 sayısına kadar karşılık gelen karakterler özel karakterlerdir. Bunlarla pek çok şekil oluşturulabilir.
Sayı sistemleri
Yukarıda verdiğim örnekleri inceleyenler ve herhangi bir hex editör ile dosyalara bakanlar bir gariplik olduğunun farkına varmışlardır. Hex editorde bütün sayılar iki basamaklı ve içinde a,b..f gibi karakterler içeriyor. Ayrıca 1 karakterine karşılık gelen sayı yukarıdaki gibi 48 değil 30 olduğunu görürler. İşte programlar daha derli toplu olduğu için, ASCII gösterimine daha yatkın olduğu için ve diğer sayı sistemlerine göre daha kullanışlı olduğu için "Onaltılık sistem" diye adlandırılan hex (hexdecimal) sistemi kullanırlar. Burada şunu açıklamakta yarar var, hex sistemi sadece kullanıcılar ve programcılar içindir. CPU sadece 1 ve 0 dan oluşan ikili sistemi (binary) anlar.
Şimdi konuyu toparlarsak üç değişik sayı sistemi olduğunu görürüz:
1 - Onluk sayma sayıları (dec) 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16... şeklinde devam eden bildiğimiz sayı sistemi
2 - Onaltılık sayılar (hex) 0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,10,11... şeklinde devam eden ve "a...f" arası ekstra olarak 6 sayı daha fazlalığı olan sayı sistemi.
3 - İkili sistem (binary) 0,1 Bu sistemde ya sıfır yada 1 olacaktır. 0 kapalı 1 ise açık mantığındadır. Bu sistem makina dilinin temelini oluşturur ve programlası ve yazması çok olduğu için assembler diline gerek duyulmuştur.
Normal sayılar hepmizin ilkokuldan beri öğrendiği sayı sistemidir. Sayı dizilimi 0 ile 9 arasıdır ve her 9 sonrası bir alt basamak bir yükselir.
Onaltılık sistemde toplam basamak yükseltme sayısı 16' dır. Biz normal sayma sisteminde 9 sayısından sonra 10 geldiğini biliyoruz. Onaltılık sistemde ise 9 sayısından sonra 0A,0B,0C,0D,0E,0F ve bundan sonra 10 gelir. Dolayısıyla 0A onaltılık sayısı normal sayı sisteminde 10 sayına eşit gelir. Aynı şekilde 10hex sayısı normal sayı sisteminde 16 sayısına eşit gelir. Kısaca onaltılık sayı sistemi 16' nın katları şeklinde büyür.
Assembler programlarında kullanılan sayılar şu şekillerde simgelenerek birbirinden ayrılırlar:
- 'd' harfi yada harfsiz bu bildiğimiz onluk sistemde demektir. Eğer bir kaynak kodunda sayının yanında (önceden bütün sayıların hex olacağını belirten .radix16 gibi bir komut kullanılmamışsa) hiçbir simge yoksa o sayı göründüğü değerinde bildiğimiz onluk tabanda bir sayıdır. Eğer onaltılık kurallarıyla yazılmış (a,b,c,d,e,f içerikli) ve yanında hiçbir işaret yoksa derleyici burada 'illegal number' şeklinde bir hata mesajı verir.
-'h' harfi ise her zaman onaltılık sistemde bir sayı olduğunu belirtir. Bu durumda normal onluk sistemde bir sayı yazıp yanına 'h' koyarsanız bu sayı onaltılık olarak kabul edilecek ve yazdığınız değer değilde onaltılık sistemin değerini alacaktır. Eğer onaltılık bir sayı ve yanına 'h' işaretini koyarsanız o sayı onaltılık olarak ve verdiğiniz değerde kabul edilecektir.
-'b' harfi ise ikilik sistemi belirtir ve adı üzerinde sadece 1 ve 0 kullanabilirsiniz. Aksi halde hata mesajı verir. Aşağıda hesaplamasını göreceksiniz ama önbilgi olarak vermek gerekirse yazmadığınız basamaklar daima 0 olarak kabul edilecektir.
Örnekler:
323 ----> onluk sayı sisteminde ve değeri 323
323d----> onluk sayı sisteminde ve değeri 323
323h----> onaltılık sayı sistemide ve değeri 803
02a-----> Hatalı sayı. İçinde onaltılık sayı sistemine ait 'a' kullanılmış ama sayının ne olduğu belirtilmemiş
02ah----> Onaltılık sayi onluk karşılık değeri 42
1110b--> İkilik sistemde bir sayı ve normalde 00001110b olarak kabul edilir ondalik 14, onaltılık 0e sayısına denk gelir
1310b--> Hatalı sayı yanında 'b' ile ikilik olduğu belirtimesine rağmen içinde 3 sayısı mevcut. 1 veya 0 olmak zorunda.
Çevrimler ve hesaplamalar:
Buraya kadar herkez bir fikir sahibi olmuştur. Burada da bunların nasıl ve neden böyle olduğunu göreceğiz. Öncelikle şunu belirtmem gerekiyor: onluk zaten biliyoruz ama onaltılık ve ikilik sistem mutlaka bilinmesi gerekiyor. Programlarınızda, denemelerinizde hep bu sistemler kullanılacak. Komutlar hep bu sistemlere göre çalışacak ve bazıları sadece bu sistemlerdeki değerlere göre hareket edecek. Bunun için bir assembler programcısının mutlaka bu sistemleri bilmesi ve kullanması gerekiyor.
Normal bildiğimiz sayma sayılarının 0 dan başlayıp 1,2,3,4,5,6,7,8,9,10,11... gibi gittiğini biliyoruz. Ona onluk sistem adını veren kural ise her 10 sayı sonrası bir basamak altını basamak atlatmasıdır. Onaltılık sistem ise yine 0 dan başlayıp 1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,10,11.. şeklinde devam eder. Burada görüldüğü gibi onaltılık sistem her 16 sayı sonrası basamak atlatıyor. Ayrıca a,b,c,d,e,f olmak üzere 6 tane daha fazladan sayıya sahip. O zaman onaltılık bir sistemde değer 10 ise onluk sistemede o sayı 16 demektir. Yani 16 nın katları olarak artmaktadır.
10h = 16
20h = 32
a4h = 164
gibi
Onaltılık bir sayıyı onluk sayıya nasıl çeviririz? -
Önce en kolay şekliyle başlayalım. Yukarıdaki örnek örnekte a4h onaltılık sayısının 164 olduğunu görüyoruz. a4h sayısına bakınca iki basamaktan olduğunu biliyoruz ve yine biliyoruz ki onaltılık sayı sistemi 16' nın katları ile büyür ve 'a' harfinin onluk sistemdeki değeri 10 , o zaman elimizde 0a +0 şeklinde bir sayı var:
0a* 16 + 4 olacak. 0a = 10 olduğuna göre: 10 *16+4 = 164
Birinci basamak her zaman kendi değerindedir. Sayı 0ah,0bh,0ch.. olsa bile onluk sisteme çevirilince birlik basamak olarak diğerlerine eklenir. Diğer basamalarda aynı bildiğimiz matematik kurallarında olduğu gibi basamak çarpanı esas alınarak çarpılır. Nasılki 164 sayısının 16*10+4 olduğunu biliyorsak onaltılık sistemde de 164 sayısının 0ah*10+4 olduğunu biliriz.
Daha büyük sayılarda onaltılık sistemden onluk sisteme çevrim söyle yapılır:
Elimizde 03f4ah gibi bir sayı olduğunu farzedelim. Bu sayı "3 - f - 4 - a" olarak 4 basamaklı ve onaltılık sistemde. Her zaman sondan başlıyoruz ve 2'1, 2'2, 2'3 gibi gidiyoruz. Burada çarpan sayıları daima sabittir.
Basamak Çarpan Üs 2'
--------- -------- -------
1 1 1 * 1
10 64 16 * 16
100 256 16 * 64
1000 4096 16 * 256
10000 65535 16 * 4096
100000 1048576 16 * 65535
1000000 16777216 16 * 1048576
10000000 268435456 16 * 16777216
gibi öyleyse bizim sayımızı bulmanın zamanı geldi
0ah * 1 = 10
04h * 16 = 16 * 4 = 64
0fh * 256 = 15 * 256 = 3840
03h * 4096 = 03 * 4096 = 12288
Toplam : 10+64+3840+12288 = 16202
Biraz zor gibi gözüksede alıştırma yaptıkça daha çok anlaşılır. Zaten çoğu kez bende büyük sayıların çevriminde windows' un hesap makinasını kullanıyorum. Bunun assembler rutin yazıp çevirmek yada _wsprintf gibi bir fonksiyonunu kullanmak aslında daha kolay. Ama mantığının bilinmesi gerekir.
Ondalık sayıların ikili sisteme çevrilişi
Ondalık bir sayıyı ikilik bir sayı sistemine çevirmek çok kolaydır. Öncelikle ikilik bir sayının 1 ve 0 sayılarından oluştuğunu hatırlayalım. Bu düzen nasıl oluşur?. Bir sayı ikiye ya tam bölünür yada kalanlı bölünür. Eğer tam bölünüyorsa 0, kalanlı bölünüyorsa 1 olur. Kalanlı bölünme drurumunda 1 olur ve sayıdan bir eksiltilerek işleme devam edilir. Örneğin:
22 /2 1
11/2 0
5/2 0
2/2 1
1/1 1
olur ve daima tersden yazılarak ikili sisteme çevrilmiş olur. O zaman 22 ondalık sayısı 11001 ikili sistemine eşit olur. Bu işlem esasen bundan sonraki kıonuda göreceğimiz bitlere ayırma işlemidir.
Bit , Byte, Kilobyte, Megabyte, Gigabyte
Her zaman duyarız şu program diskte şu kadar kilobyte yer kaplıyor diye. Yada yeni bir disk aldım 20gb gibi. Bunlar aslında datanın uzunluğudur. Yani verinin kapladığı alandır. Bu büyüklükler temeldir ve disk, hafıza, program içinde aynıdır. Sadece bit büyüklük olarak farklılık gösterir ancak diğerleri hep 1024' ün katları olarak artar ve birimlenir.
1 bit en fazla 1 birim olabilir,
1 byte 0 ile 256 arasında değer alır.
1 kilobyte 0 ile 65535 arasında değer alır.
1 megabyte 0 ile 4294967295 arasında değer alır.
Birimler sırasıyla şu şekilde küçükten büyüğe doğru sıralanır: bit, byte, kilobyte,megabyte,gigabyte,terabyte... Burada en küçük birim olan 'bit' tek başına kendiden sonraki birimin 8/1 ni oluşuturur. Kısaca açarsak 1 byte = 8 bittir. Aynı şekilde 2 byte 16 bittir. Bunu şöyle göstererek daha iyi anlaşılır hale getirebiliriz:
Elimizdeki sayı: 256 ve bu sayının onaltılık şekli 0FFh, bunu ikili sisteme çevirirsek :
256/2 1
128/2 1
64/2 1
32/2 1
16/2 1
8/2 1
4/2 1
2/2 1
1/1 1
sonucunu elde ederiz. bu durumda 256 sayısı 1 byte ve 8 bitten oluşur. Bu 8 tane bitin illaki 1 ve 0 olması gerekmez. Sonuçta 8 tane 1 vya 0 1 byte'ı oluşturur. Byte ile beraber bütün üst birimler 1024 ' ün katları olarak artar.
1024 byte = 1 kb (kilobyte)
1024 kb = 1 mb (megabyte)
1024 mb = 1 gb (gigabyte)
1024 gb = 1 tb (terabyte)
Şeklinde açıklanabilir. Makina dili işlemleri CPU' a ikili sistemde gönderildiğini söylemiştik. Yukarıdaki birimlere bakınca sadece ikili sistemde programlamadının ne kadar zor oluduğu görülmektedir. Assembler dili bunu için geliştirilmiştir. Kullandığımız programlar dili veya arabirim ne olursa olsun sonuçta assembler kodlarına döner. Kullandığımız *.exe *.com gibi dosyalar aslında assembler dilinden oluşan makina dilini içerir ve program hafızada çalışmaya başlayınca CPU' a (merkez işlem birimi) 1 ve 0 olarak çevrilerek gönderilir. Delphi, C++, VB gibi dillerde kullanmdığımız hemen hemen bütün komutlar semboliktir. Bu komutlara karşılık gelen makina komutları setleri vardırki bunlar sayesinde tekrar tekrar aynı kodları yazmak yerine daha düzenli ve daha optimize bir şekilde makina dili programları oluşturulabilir. Makina dili assemblerdır. Tüm assembler komutlarına birden "opkod" olarak isim verilir.
Registerler
Registeler en basit şekilde şöyle anlatılabilir: a=1 Burada "a" isimli bir değişkenimiz var ve ona bir değerini atıyoruz. Bundan sonra yeni bir değer atamadığımız sürece a her zaman 1 olacaktır. Aynen bunun gibi CPU' nun her türlü işlemlerini yerine getirmesi için isimleri sabit registerleri vardır. Bunları yukarıdaki gibi değişkenlere benzetebilirsiniz. Bunlar
AX ,BX , CX, DX = Bunlar matematiksel ve mantıksal işlemlerde kullanılır. Bu registerler çalışan özel opkodlar vardır.
SI, DI = Yazı, veri arama veya aktarımı gibi işlemler
BP, SP = Yığın işleleri ile ilgili
IP = Komut noktası o andaki nokta
ES, DS, CS, SS = Segment işlemleri ile ilgili
F = Bayrak registeri, Bu registerin bitlerine göre işlemler yapılır yada yapılmaz
Bunlardan başka korumalı modu sistem ile ilgili DR0-DR7 ve matematik işlemcini St(0) - St(7) registerleri bulunmaktadır. Ayrıca MMX ile birlikte yeni registerler gelmiştir.
Registerler kullanım şekline göre yada yapılarına göre değer alabilirler. Örneğin AX register diğer BX,CX,DX registerler gibi 8 bit 8 bit şeklinde değer alabilir yada tamamen 16 bit şeklinde değer alabilir. Açılımı söyledir:
AX = AL ve AH
BX = BL ve BH
CX = CL ve CH
DX = DL ve DH
Burada 'L' low demektir ve ilk 8 biti (1byte) temsil eder. 'H' ise high demektir ve son 8 biti (1 byte) temsil eder.
SI, DI registeler genel amaçlı kullanıldıkları gibi işlemcinin tahsis ettiği bazı özel komutlarla beraber veri aktarma, doldurma, silme yada karşılaştırma işlemlerini yerine getirir.
SI, DI, BP, SP, IP,ES,CS,DS,SS, FS, GS,F registerler daima 16 bittir (2byte) ve yukarıdaki registerler gibi düşük-alçak bit şeklinde ayrılmazlar. Ancak komutlarla bu bitlere müdahile ve değiştirmek mümkündür.
AX,BX,CX,DX registerler matematiksel işlemler, lojik işlemler, sayma işlemleri, ve bazı komutlarla string işlemlerinde kullanılır.
AX register "akumulatör" olarak adlandırılır. Bu register ile çalışan ve sonuçları bu registere aktaran pek çok opkod vardır.
BX register "base = taban" register olarak adlandırılır. Örneğin XLAT gibi komutlar bu register ile çalışır ve genelde AX registere yakın ve yardımcı olarak kullanılan bir registerdir.
CX register "Counter = sayaç" olarak nitelendirilir. Bu register sayaç ve dongü konularına tahsis edilmiştir. Yine pek çok opkod bu registere göre sayma ve sonuçlandırma işlenmleri yapar.
DX register yine diğer registerler gibi birçok işlemde kullanıldığı gibi bölüm sonu kalan sayısı için AX registerle beraber kullanılır.
Bu registerler DOS ortamında 16 bit yada 8 bit olarak çeşitli interrupt (kesme) fonksiyonlarınıda oluştururlar.
CS,DS,ES,GS,FS registerler programın bulunduğu hafıza bölgesi ile ilgili tanımlamarı yaparlar. Örneğin bir yere veri aktarılacaksa ve bu bölge programın bulunduğu bölgenin dışında ise bu registerler ile exstra olarak gösterilerek aktarma yapılabilir.
Bütün registerler değer alırken başına 'MOV' komunu alırlar. Bu komut yükle anlamındadır ve "mov ax,1020" dersek bunun anlamı "ax registere 1020 yükle" olur. Buradan sonra ax register değiştirilmediği sürece 1020 değerini almış olur. IP ve F register hariç bütün registeler MOV komutu işle değer alır, veya değer yazar.
SI, DI registerler CPU' nun onlara tahsis ettiği komutlar ile hızlı ve en yüksek seviyede data aktarma, arama, işleme yeteneği kazanır. Aynı zamanda diğer registerler ile matematiksel ve lojik işlemlerde de kullanalabilirler.
BP, SP, SS registerler yığın için kullanılır. Yığın kelimesi açıklamak gerekirse programın çalışması için saklanan değerlerdir. Yani programda aynı registeri kullanarak farklı işlemler yapmamız gerekiyorsa önceki register değerini saklamamız gerekiyor. Aksi halde bu değeri kaybederiz ve yeniden kazanmak mümün olmayabilir. Bunu iki yolla yaparız. Ya bir yere bu değeri yazarız yada sistemin bize verdiği saklama komutları ile hafızada saklarız. BP ve SP registerler işte bu hafızada yığılan saklana değerlerin yeni gösterir
IP register özel bir registerdir ve direk olarak kullanılamaz. Programın o andaki çalışma adresini belirtir ve değiştirmek için CPU' nun verdiği özel debug registerler kullanılır.
ES, DS, CS, FS, GS, SS registerler hafızayı daha iyi ve güçlü kullanmayı sağlamak, doğru veri kontrolu ve aktarımı sağlamak ve programın çalıştığı yeri saptamak için kullanılır. Bazılarının bilinçsizce değiştirilmesi programda büyük sorunlara yol açar ancak hiç bir deneme yada sorun sisteme kalıcı bir zarar vermez, reset işlemi ile herşey eskiye döner.
F register Türkçe adıyla bayrak register sistemin karşılaştırmalar, olay - sonuç - durum gibi hayati hallerine yön verme gibi etkilere sahiptir. Bütün atlamalar bu registerin bitlerine bağlı olarak oluşur. Bir karşılaştırmanın sonucunda bu registerin sabit ve belli bitleri set (1) veya reset (0) olarak durum bildirimi yaparlar. Şartsız atlama komutu hariç bütün atlama komutları bu registerin bitlerine bakarak atlama yaparlar.
Modlar
8088 işlemciler hem 16 bit bir CPU idi hem şu anda kullandığımız işlemcilere göre daha yavaş ve daha az özellikliydi. Sanal86 modu yoktu. Zamanına göre yeterli görülsede sonradan bunun asla yetemeyeceğini anlaşıldı ve 80286 ile beraber yeni bir16 bitlik işlemci ve mod stratejisi geliştirildi. Buna göre hem eskiye uyumlu olarak 8088 gibi çalışacak hemde yeni nesil programları destekleyecekti.. Ancak asıl düzenlemenin 80386 sonrası işlemcilerde yapıldığı kabul edilir. Uzun süreli DOS işletim sisteminin hakimiyeti ve korumalı modun DOS altında zahmetli programlanması bizim bu yeniliklerden ve birçok avantajdan istemeyerekte olsa mahrum kalmamıza neden olduı. Windows ile beraber 80386' dan beri gelen değişim ve avantajlar kullanıma geçti. En başta birden çok program çalıştırma, büyük adresleme yapabilme ve zaten var olan 32 bitlik komutları tam anlamıyla kullanma bunlardan sadece bazıları.
Şu an kullandığımız 386+ işlemcili sistemlerde üç mod var. Bunlar: Gerçek mod, Korumalı mod ve Sanal mod. Bunların arasında çalışma, adresleme, hafıza kullanılımı gibi pek çok farklılıklar var. Bilgisayar ilk çalıştırıldığında gerçek modda çalışır. Daha sonra işletim sistemi onu istediği moda geçirir.
DOS işletim sistemi gerçek modda çalışır. Bu mod tek (single) moddur ve yardımcı emulasyonlar olmadığı sürece aynı anda tek program çalıştırılır. Ayrıca adresleme şekli segment ve offset düzenine bağlı. Bir programında bir yerin adresi Seg:Off olarak gösterilir ve segmentler 16' nın katları şeklinde artar. Bir verinin yada programı fiziksel adresini segment 16 ile çarpılır. Bu onaltılık sistemde 10h ile çarpmaktır ve kısaca yanına bir 0 eklemek yeterli. Bundan sonrada offset çıkan sayı ile toplanır ve bize fiziksel hafızadaki adresi verir.
Korumalı mod çoklu işlemi (multi) destekleyen sağlam, ve güçlü bir hafıza stratejisi olan bir moddur. Gerçek mod ile korumalı mod arasındaki en önemli farklardan birisi hassasiyet derecelendirmesidir. Buna "ring" de denir ve korumalı modda toplam 4 hassasiyet seviyesi vardır. En düşük seviye 0 aslında en büyük, gerçek zamanlı seviyedir. Gerçek modda bir program hafızada başka bir program rahatça ulaşabilir ve değiştirebilir. Bunun pek sakıncası ve bilgi kaybı açısından riski vardır. Korumalı modda ise bu ancak iki programda 0 (ring 0) olması durumuda mümkündür.
Sanal mod gerçek mod ile korumalı mod arasındaki emulasyonu sağlar. Böylece korumalı modda gerçek mod programlarınıda kullanabilmek mümkün olur.
CS, DS, ES registerler bir bakıma gerçek moda ait segment registerleridir. Çünkü korumalı modda segment kavramı yoktur. Ayrıca korumalı mod bazı gerçek mod komutlarının direkt olarak çalışmasına izin vermez. Örneğin dos işletim sisteminin vazgeçilmez komutlarından int nn ( interrupt) komutu ancak bir VXD içerinde çalışır. VXD dosyaları daima en yüksek modda çalışan programlardır.
32 bit registerler
Önceki konularda 16 bir registerleri anlatmıştım. Buna göre 16 bit registerler 8 olarak ikiye ayrılabilirler. 32 bit registerlerde registerin 16 bitlik kısmı aynı şekilde kullanılabilir ancak eklenen 16 bitlik kısım böyle 8' er bit yada tek başına 16 bit halinde kullanılamazlar. Ancak lojik komutlar ile bit kaydırması yaparak erişilir. 16 bitlik bir register en fazla 65535 değerini alırken 32 bitlik bir register 4294967295 değerine kadar alır.
Yukarıda anlattığım bütün registerler başlarına bir 'E' (extended) harfi alarak 32 bit olurlar.
AX --> EAX
BX--> EBX
F ---> EF
SP --> ESP .... gibi
Registelerin normal olarak 'mov' komutu ile adresleme ve yükleme yaptığını daha önce belirmiştir. Buna göre AX registere örneğin 1234h sayını yüklemek istediğimizde :
mov ax,1234h
yazmamız yeterli Ayrıca 32 bit bir register olarak kullanmak istediğimizde :
mov eax,12345678
şeklinde kullanırız. Bu örnekte görüldüğü gibi bütün registerler MOV regname, değer şeklinde yüklenirler.
Adresleme ise bir yerden değer yükleme yada bir yere değer aktarma demektir. Bir adresten değer alıp registerleri yüklemek için [] köşel parantezi kullanırız. Ayrıca editör ile bir asm dosyası oluşturduksa ve sembolik olarak bir yeri veri adreslemesi için kullanıyorsak LEA komutunu yada adresin yanına offset komutu kullanabiliriz.
mov ax word ptr,[1234]
mov cx,word ptr,[sayac1]
mov dl, byte ptr [sayi]
mov ax,[bx]
mov si,[ax+bx]
şeklinde kullanılır. Ayrıca sembolik adreslemeler için:
lea ax, string1
lea si,aktarma_start
mov si, offset aktarma_start
olarak kullanılabilir.
Şimdi register değerlerinin değiştirilmesi ile ilgili örnekleri inceleyelim:
Örnek1: Burada AX registerin düşük 8 biti değiştiriliyor
mov ax, 1234h -------> ax=1234
mov al,0aah -------> ax = 12aah
Örnek2:
mov ax,4423 ----> ax=4423
mov bx,1167 ----> bx = 1123
mov al,bl -----> al= bl yani al= 23 iken 67 oluyor
mov word ptr [sonuc],ax -----> sonuc adresine ax register yazılıyor. AX = 4467
Adreslemeler
Adresleme konusu assembler dilinde önemli bir konudur. Adresleme hafızanın bir bölgesini kullanma yada bir bölgesini gösterme olarak tanımlanalabilir. Herhangi bir değeri saklama, bir hafıza bölgesi üzerinde çalışma yada kısaca BUFFER olarak kullanma olanağı sağlar. Örneğin klavyeden girilen bir yazıyı inceleyebilmek ve üzerinde araştırma yapabilmek için bir bölgeye yazılması gereklidir. Bunun için kullanılan fonksiyona göre registerlerle önceden ayrılmış bir adrese aktarma yapılır. Daha sona burada kullanıcının istediği şekilde bu yazı üzerinde değişiklik, hesaplama veya mantıksal işlemler yapabilir.
Başlıca adreslemeler şunlardır:
- Register adreslemesi : Registere bir değeri yüklemek için registerin biti kadar bir adresten yükleme yapılması.
Örneğin: mov eax, [sayi1] veya mov eax,sayi1
Burada eax register sayi1 ile belirtilen hazıfa adresinden o andaki değeri alıp eax registere yüklüyor.
- Değer saklamak için adresleme (Dolaylı adresleme) : Burada ileride göreceğimiz PUSH ve POP komutuna benzer olarak istenilen bir hafıza adresine saklama işlemi yapılır. Bu bir register olabileceği gibi bağımsız bir değerde olabilir.
Örneğin : mov dword ptr [deneme],ebx yada mov word ptr [04010000],1234h gibi
- Hafıza bölgesi adreslemesi : Bu adresleme ile register hafızanın herhengi bir bölgesi işaretlenir. Bunun için MOV komutu yerine LEA komutunu kullanabiliriz. MOV komutu registere verilen değeri yükler yada adresler. LEA komutuda aynı işi yapar. Ancak MOV komutundan önemli farkı vardır. Verdiğiniz yerdeki değeri değil adresi yükleme yapar. Örneğin:
debug 'Yazi1',0
lea eax, debug şeklinde verilince eax registere 'Yazi' değeri değil bulunduğu yerin adresi yüklenir. Böylece biz bu vernini başından itibaren istediğimiz gibi çalışma yapabiliriz.
- Dolaysız adresleme : Bu adresleme registerler arasındaki değerlerin hafıza adresi olarak kullanılması ile oluşan adreslemedir. Buna göre elimizde hiçbir değer yok sadece registeler vardır. Yaygın bir kullanıma sahiptir. Örneğin:
mov dword ptr [esi] ,eax
mov byte ptr [eax],bl
mov dword ptr [esi],ebx
mov word ptr [edi+esi],ax
gibi sadece registeler arası adreslemedir.
Yığın (Stack) konusu
Komutunu vermeden önce yığın konusunun ne olduğundan bahsetmekte yarar var. Yığın kısaca saklamak istediğimiz değerleri hafızanın ESP (veya 16 bit olarak SP) registerler gösterilen bölgeye yığılması ve dolayısıyla saklanmasıdır. Kullanıdığımız değerleri burada örnek olarak 32 bitlik değerler olarak kabul edersek daha anlaşılır bir şeklide 4 * 8 bit anlamına gelirki bunu 4 tane sayı veya simge olarak düşünebiliriz. Örneğin programımızda eax registerde 12345678 değerinin olduğunu düşünelim. Ancak yine eax register ile bir işlem yapacağız ve bu değer değişecek. Bunu saklamamız gerekiyor. Yukarıda örnek verdiğim şekilde adresleme yaparak hafızanın bir yerine yazar ve saklarız ama ya bu register bir sayaç yada çok sayıda değişme gösteriyorsa. İşte bunun için CPU iki tane register ile bu sorunu en çözümlü ve basit olacak şekile indirmiştir. Normalde gerçek mod segment yapısına sahip olduğu için yığın işleminde sadece hafıza adresini göstermesi yetmez. Bunun için yığın bölgesinin tam adresinin bulunması amacıyla SS (Stack Segment) adıyla bir yardımcı register daha vardır. Gerçek modda yani DOS' da yığın bölgesini SS:SP şeklinde algılarız ve işlemler buna göre yapılır. Korumalı modda böyle bir segmentleme ihitiyacı yoktur. Dosya hafızaya yüklendiği anda Windows otomatik olarak o program için bir bir yığın noktası belirler ve ESP registere bu noktayı yükler. Hiçbir CPU 8 bitlik bir değeri saklamaz. En düşük register veya değer saklaması 16 bittir. Program içinde ve her modda değerleri bu yığın bölgesine atmak ve almak için PUSH ve POP komutları vardır. Bu komutlardan PUSH komutu saklanılan registeri yada değeri değiştirnmez, aynbı değerin kopyasını alır. POP komutu ise verilen registerin değerini siler ve yüklediği değeri uygular. Burada 32 bit registerler kullandığımızı varsayabilir. PUSH komutu değeri yığın bölgesine yazdıktan sonra ESP registerin değerini 32 bit (4byte) eksiltir. Eğer kullandığımız register 16 bit ise 16 bit (2byte) eksilme yapar. Burada önemli konu ise CPU' nun değer olarak çalışılan moduda dikkate almasıdır. Örneğin 32bit korumalı modda 16bitlik bir değeri yığına atmak istersek bunu daima 32 bir olarak kabul edecek, değer vermediğimiz kısmı ise 0 olarak kabul edecektir. Bu yeni değerleri saklamak için uygun adreslemeyi yapmasıdır. Aynı şekilde POP komutu değeri yığın bölgesinden alır ve ESP register +4 artar. Bu konu biraz karışık gibi görünebilir. Ancak kullandıkça öyle olmadığını göreceksiniz. Önemli bir hususda girilen değerlerin çıkış şeklidir. Örneğin sırasıya 10 20 30 değerleri push komutu ile yığında saklanmışsa geri dönüş daima 30 20 10 şeklinde olacaktır. Yani son giren ilk çıkar kuralı geçerlidir. Eğer bu sıralama bozulursa yığına atılan değerler hep yanlış çıkacaktır. Normalde API çağrıları hariç kullandığınız her PUSH komutu için POP komutuda kullanarak kullandığınız her değeri yığından almanız gerekir. Yine önceden girilmiş bir değer ESP register vasıtasıyla eksiltmeden yada POP yapmadan alınabilir. Bunun için örnmeğin mov eax, [esp-8] gibi bir komut yeterli.
Şimdi daha iyi anlaşılabilmesi için
ESP = 643038 olduğu farzedelim
mov eax,12345678h
push eax ; -----> eax registerin 12345678 değeri yığına atılıyor. ESP = 643038 - 4 = 643034 oldu
mov ax,0aa5599aah
push eax ; -----> eax registerin yeni değeri olan 0aa5599aah değeri yığına atıldı. ESP=643034-4= 643030 oldu.
pop eax ; -----> son giren ilk çıkar. eax = 0aa5599aah oldu ve yığın 643030+4 = 643034 oldu
pop eax ; ----> önceki değer çıktı. eax = 12345678h ve ESP = 643034+4 = 643038 oldu.
Burada görüldüğü gibi eax registerin ilk değeri 12345678h saklandı daha sonra 0aa5599aah ile işlem yapıldı ve tekrar yığından alınarak baştaki değerine dönüldü. Burada bir tiyo vermek gerekirse illaki girilen registerler çıkılacak diye bir kural yok. Yani iz yukarıda eax register ile yığına attık. Ancak dönüşte ebx, ecx,edx,... gibi bütün registeleri kullanabiliriz. Örneğin:
mov ax,12345678h ; eax = 12345678h
push eax ; eax yığına atılarak saklanıyor
mov eax,0 ; eax registere 0 değerini verdik
pop ebx ; yığındaki son değer alınıyor ilk başta girdiğimiz 12345678 değeri artık ebx registerde yüklü.
Yığın konusu döngüler içinde döngü sayısını tutmak içinde kullanılır. Bu anlamda yığın çok önemlidir. Ayrıca Bayrak registerde (F - EF) yığın içinde saklanılabilir. 386+ sonrası komutlar arasında tek komutla bütün registerleri yığına atan ve yine tek komutla hepsini yığından atan PUSHA ve POPA komutu eklenmiştir.
***********************************************************************************************
İlksöz
Burada herkezin anlayabileceği, uygulayabileceği, Türkçe, örnekli bir anlatım yapmaya çalıştım. Amacım Assembler dilini herkezin Türkçe bir kaynaktan ve yeterince tecrübesi olan bir ağızdan öğrenmesi. Maalesef ülkemizdeki bu konudaki kitapların çok azı hariç büyük çoğunluğu orjinalden çeviriye dayanıyor. Doğal olarak okuyucu uygulamada ve anlamada zorluk çekiyor. Ayrıca bazı terimleri Türkçe karşılıklarıda pek tutarlı olmuyor. Internette ise Ingilizce forumlar ve siteler bulunmasına rağmen teknik terimler ve Assembler dilinin diğer hazır dillere benzememesinden dolayı öğrenilme şansı azalıyor.
Assembler dili kesinlikle zor değildir. Bu dili öğrenemek için bol bol deneme yapmanız gerekiyor. Zamandan, sosyal yaşamdan biraz fedakarlık etmeniz gerekebilir.
Herkeze iyi çalışmalar ve başarılar dilerim. En iyiye ve en doğruya doğru hep beraber kürek çekelim...
CYDONIA
Giriş
Assembler merkezi işlemci olan bütün sistemlerde ve işletim sistemlerinde "kullanıcı - program - makina" iletişimi sağlayan ve sembolik komutlar sayesinde programlamayı sayılar düzeyinden sembollere çıkaran bir dildir. Normalde merkezi işlemci (CPU) komutları 1 ve 0 olarak anlar ve buna göre yapılması gereken işlemleri yerine getirir.
ASCII mantığı
Herhangi bir EXE dosyaya bir editor ile baktığımız zaman anlamsız ve karmaşık birçok semboller ve karakterler görürüz. Bunlar o an orada bulunan sayılara karşılık gelen ve ASCII olarak adlandırılan sembollerdir. Bu semboller hem sayıları hem büyük/küçük harfleri hemde birçok işareti içerir. ASCII tablosu 256 işaretten meydana gelir ve her sayı karşılığı bir semboldur. İlk 32 karakter kontrol karakterleri adı verilen ve görülmeyen karakterlerdir.Bunlar yazıcı, string gibi işlemlerde tek başlarına yada birden fazla görev alırlar. Örneğin 7 sayısı ASCII karakterlerinde 'BEEP' anlamına gelir ve programınızda eğer 'deneme yazısı',7,0 şeklinde kullanırsanız yazı sonrası bir 'beep' sesi duyarsınız. Yada 10 ve ardından 13 kullanırsanız yazınız bir satır aşağıya inip paragrafın en başına gelir. Örneğin: 'deneme yazısı',10,13,0
48 - 58 arası ise sayılardır. Buna göre:
48 = '0'
49 = '1'
50 = '2' ....
sayı şekillerine denk gelir ve bu standarttır.
Büyük harfler 65' den başlar 90' a kadar devam eder. Aynı şekilde küçük harflerde 97 ile 122 arasındadır. Bundan sonra 256 sayısına kadar karşılık gelen karakterler özel karakterlerdir. Bunlarla pek çok şekil oluşturulabilir.
Sayı sistemleri
Yukarıda verdiğim örnekleri inceleyenler ve herhangi bir hex editör ile dosyalara bakanlar bir gariplik olduğunun farkına varmışlardır. Hex editorde bütün sayılar iki basamaklı ve içinde a,b..f gibi karakterler içeriyor. Ayrıca 1 karakterine karşılık gelen sayı yukarıdaki gibi 48 değil 30 olduğunu görürler. İşte programlar daha derli toplu olduğu için, ASCII gösterimine daha yatkın olduğu için ve diğer sayı sistemlerine göre daha kullanışlı olduğu için "Onaltılık sistem" diye adlandırılan hex (hexdecimal) sistemi kullanırlar. Burada şunu açıklamakta yarar var, hex sistemi sadece kullanıcılar ve programcılar içindir. CPU sadece 1 ve 0 dan oluşan ikili sistemi (binary) anlar.
Şimdi konuyu toparlarsak üç değişik sayı sistemi olduğunu görürüz:
1 - Onluk sayma sayıları (dec) 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16... şeklinde devam eden bildiğimiz sayı sistemi
2 - Onaltılık sayılar (hex) 0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,10,11... şeklinde devam eden ve "a...f" arası ekstra olarak 6 sayı daha fazlalığı olan sayı sistemi.
3 - İkili sistem (binary) 0,1 Bu sistemde ya sıfır yada 1 olacaktır. 0 kapalı 1 ise açık mantığındadır. Bu sistem makina dilinin temelini oluşturur ve programlası ve yazması çok olduğu için assembler diline gerek duyulmuştur.
Normal sayılar hepmizin ilkokuldan beri öğrendiği sayı sistemidir. Sayı dizilimi 0 ile 9 arasıdır ve her 9 sonrası bir alt basamak bir yükselir.
Onaltılık sistemde toplam basamak yükseltme sayısı 16' dır. Biz normal sayma sisteminde 9 sayısından sonra 10 geldiğini biliyoruz. Onaltılık sistemde ise 9 sayısından sonra 0A,0B,0C,0D,0E,0F ve bundan sonra 10 gelir. Dolayısıyla 0A onaltılık sayısı normal sayı sisteminde 10 sayına eşit gelir. Aynı şekilde 10hex sayısı normal sayı sisteminde 16 sayısına eşit gelir. Kısaca onaltılık sayı sistemi 16' nın katları şeklinde büyür.
Assembler programlarında kullanılan sayılar şu şekillerde simgelenerek birbirinden ayrılırlar:
- 'd' harfi yada harfsiz bu bildiğimiz onluk sistemde demektir. Eğer bir kaynak kodunda sayının yanında (önceden bütün sayıların hex olacağını belirten .radix16 gibi bir komut kullanılmamışsa) hiçbir simge yoksa o sayı göründüğü değerinde bildiğimiz onluk tabanda bir sayıdır. Eğer onaltılık kurallarıyla yazılmış (a,b,c,d,e,f içerikli) ve yanında hiçbir işaret yoksa derleyici burada 'illegal number' şeklinde bir hata mesajı verir.
-'h' harfi ise her zaman onaltılık sistemde bir sayı olduğunu belirtir. Bu durumda normal onluk sistemde bir sayı yazıp yanına 'h' koyarsanız bu sayı onaltılık olarak kabul edilecek ve yazdığınız değer değilde onaltılık sistemin değerini alacaktır. Eğer onaltılık bir sayı ve yanına 'h' işaretini koyarsanız o sayı onaltılık olarak ve verdiğiniz değerde kabul edilecektir.
-'b' harfi ise ikilik sistemi belirtir ve adı üzerinde sadece 1 ve 0 kullanabilirsiniz. Aksi halde hata mesajı verir. Aşağıda hesaplamasını göreceksiniz ama önbilgi olarak vermek gerekirse yazmadığınız basamaklar daima 0 olarak kabul edilecektir.
Örnekler:
323 ----> onluk sayı sisteminde ve değeri 323
323d----> onluk sayı sisteminde ve değeri 323
323h----> onaltılık sayı sistemide ve değeri 803
02a-----> Hatalı sayı. İçinde onaltılık sayı sistemine ait 'a' kullanılmış ama sayının ne olduğu belirtilmemiş
02ah----> Onaltılık sayi onluk karşılık değeri 42
1110b--> İkilik sistemde bir sayı ve normalde 00001110b olarak kabul edilir ondalik 14, onaltılık 0e sayısına denk gelir
1310b--> Hatalı sayı yanında 'b' ile ikilik olduğu belirtimesine rağmen içinde 3 sayısı mevcut. 1 veya 0 olmak zorunda.
Çevrimler ve hesaplamalar:
Buraya kadar herkez bir fikir sahibi olmuştur. Burada da bunların nasıl ve neden böyle olduğunu göreceğiz. Öncelikle şunu belirtmem gerekiyor: onluk zaten biliyoruz ama onaltılık ve ikilik sistem mutlaka bilinmesi gerekiyor. Programlarınızda, denemelerinizde hep bu sistemler kullanılacak. Komutlar hep bu sistemlere göre çalışacak ve bazıları sadece bu sistemlerdeki değerlere göre hareket edecek. Bunun için bir assembler programcısının mutlaka bu sistemleri bilmesi ve kullanması gerekiyor.
Normal bildiğimiz sayma sayılarının 0 dan başlayıp 1,2,3,4,5,6,7,8,9,10,11... gibi gittiğini biliyoruz. Ona onluk sistem adını veren kural ise her 10 sayı sonrası bir basamak altını basamak atlatmasıdır. Onaltılık sistem ise yine 0 dan başlayıp 1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,10,11.. şeklinde devam eder. Burada görüldüğü gibi onaltılık sistem her 16 sayı sonrası basamak atlatıyor. Ayrıca a,b,c,d,e,f olmak üzere 6 tane daha fazladan sayıya sahip. O zaman onaltılık bir sistemde değer 10 ise onluk sistemede o sayı 16 demektir. Yani 16 nın katları olarak artmaktadır.
10h = 16
20h = 32
a4h = 164
gibi
Onaltılık bir sayıyı onluk sayıya nasıl çeviririz? -
Önce en kolay şekliyle başlayalım. Yukarıdaki örnek örnekte a4h onaltılık sayısının 164 olduğunu görüyoruz. a4h sayısına bakınca iki basamaktan olduğunu biliyoruz ve yine biliyoruz ki onaltılık sayı sistemi 16' nın katları ile büyür ve 'a' harfinin onluk sistemdeki değeri 10 , o zaman elimizde 0a +0 şeklinde bir sayı var:
0a* 16 + 4 olacak. 0a = 10 olduğuna göre: 10 *16+4 = 164
Birinci basamak her zaman kendi değerindedir. Sayı 0ah,0bh,0ch.. olsa bile onluk sisteme çevirilince birlik basamak olarak diğerlerine eklenir. Diğer basamalarda aynı bildiğimiz matematik kurallarında olduğu gibi basamak çarpanı esas alınarak çarpılır. Nasılki 164 sayısının 16*10+4 olduğunu biliyorsak onaltılık sistemde de 164 sayısının 0ah*10+4 olduğunu biliriz.
Daha büyük sayılarda onaltılık sistemden onluk sisteme çevrim söyle yapılır:
Elimizde 03f4ah gibi bir sayı olduğunu farzedelim. Bu sayı "3 - f - 4 - a" olarak 4 basamaklı ve onaltılık sistemde. Her zaman sondan başlıyoruz ve 2'1, 2'2, 2'3 gibi gidiyoruz. Burada çarpan sayıları daima sabittir.
Basamak Çarpan Üs 2'
--------- -------- -------
1 1 1 * 1
10 64 16 * 16
100 256 16 * 64
1000 4096 16 * 256
10000 65535 16 * 4096
100000 1048576 16 * 65535
1000000 16777216 16 * 1048576
10000000 268435456 16 * 16777216
gibi öyleyse bizim sayımızı bulmanın zamanı geldi
0ah * 1 = 10
04h * 16 = 16 * 4 = 64
0fh * 256 = 15 * 256 = 3840
03h * 4096 = 03 * 4096 = 12288
Toplam : 10+64+3840+12288 = 16202
Biraz zor gibi gözüksede alıştırma yaptıkça daha çok anlaşılır. Zaten çoğu kez bende büyük sayıların çevriminde windows' un hesap makinasını kullanıyorum. Bunun assembler rutin yazıp çevirmek yada _wsprintf gibi bir fonksiyonunu kullanmak aslında daha kolay. Ama mantığının bilinmesi gerekir.
Ondalık sayıların ikili sisteme çevrilişi
Ondalık bir sayıyı ikilik bir sayı sistemine çevirmek çok kolaydır. Öncelikle ikilik bir sayının 1 ve 0 sayılarından oluştuğunu hatırlayalım. Bu düzen nasıl oluşur?. Bir sayı ikiye ya tam bölünür yada kalanlı bölünür. Eğer tam bölünüyorsa 0, kalanlı bölünüyorsa 1 olur. Kalanlı bölünme drurumunda 1 olur ve sayıdan bir eksiltilerek işleme devam edilir. Örneğin:
22 /2 1
11/2 0
5/2 0
2/2 1
1/1 1
olur ve daima tersden yazılarak ikili sisteme çevrilmiş olur. O zaman 22 ondalık sayısı 11001 ikili sistemine eşit olur. Bu işlem esasen bundan sonraki kıonuda göreceğimiz bitlere ayırma işlemidir.
Bit , Byte, Kilobyte, Megabyte, Gigabyte
Her zaman duyarız şu program diskte şu kadar kilobyte yer kaplıyor diye. Yada yeni bir disk aldım 20gb gibi. Bunlar aslında datanın uzunluğudur. Yani verinin kapladığı alandır. Bu büyüklükler temeldir ve disk, hafıza, program içinde aynıdır. Sadece bit büyüklük olarak farklılık gösterir ancak diğerleri hep 1024' ün katları olarak artar ve birimlenir.
1 bit en fazla 1 birim olabilir,
1 byte 0 ile 256 arasında değer alır.
1 kilobyte 0 ile 65535 arasında değer alır.
1 megabyte 0 ile 4294967295 arasında değer alır.
Birimler sırasıyla şu şekilde küçükten büyüğe doğru sıralanır: bit, byte, kilobyte,megabyte,gigabyte,terabyte... Burada en küçük birim olan 'bit' tek başına kendiden sonraki birimin 8/1 ni oluşuturur. Kısaca açarsak 1 byte = 8 bittir. Aynı şekilde 2 byte 16 bittir. Bunu şöyle göstererek daha iyi anlaşılır hale getirebiliriz:
Elimizdeki sayı: 256 ve bu sayının onaltılık şekli 0FFh, bunu ikili sisteme çevirirsek :
256/2 1
128/2 1
64/2 1
32/2 1
16/2 1
8/2 1
4/2 1
2/2 1
1/1 1
sonucunu elde ederiz. bu durumda 256 sayısı 1 byte ve 8 bitten oluşur. Bu 8 tane bitin illaki 1 ve 0 olması gerekmez. Sonuçta 8 tane 1 vya 0 1 byte'ı oluşturur. Byte ile beraber bütün üst birimler 1024 ' ün katları olarak artar.
1024 byte = 1 kb (kilobyte)
1024 kb = 1 mb (megabyte)
1024 mb = 1 gb (gigabyte)
1024 gb = 1 tb (terabyte)
Şeklinde açıklanabilir. Makina dili işlemleri CPU' a ikili sistemde gönderildiğini söylemiştik. Yukarıdaki birimlere bakınca sadece ikili sistemde programlamadının ne kadar zor oluduğu görülmektedir. Assembler dili bunu için geliştirilmiştir. Kullandığımız programlar dili veya arabirim ne olursa olsun sonuçta assembler kodlarına döner. Kullandığımız *.exe *.com gibi dosyalar aslında assembler dilinden oluşan makina dilini içerir ve program hafızada çalışmaya başlayınca CPU' a (merkez işlem birimi) 1 ve 0 olarak çevrilerek gönderilir. Delphi, C++, VB gibi dillerde kullanmdığımız hemen hemen bütün komutlar semboliktir. Bu komutlara karşılık gelen makina komutları setleri vardırki bunlar sayesinde tekrar tekrar aynı kodları yazmak yerine daha düzenli ve daha optimize bir şekilde makina dili programları oluşturulabilir. Makina dili assemblerdır. Tüm assembler komutlarına birden "opkod" olarak isim verilir.
Registerler
Registeler en basit şekilde şöyle anlatılabilir: a=1 Burada "a" isimli bir değişkenimiz var ve ona bir değerini atıyoruz. Bundan sonra yeni bir değer atamadığımız sürece a her zaman 1 olacaktır. Aynen bunun gibi CPU' nun her türlü işlemlerini yerine getirmesi için isimleri sabit registerleri vardır. Bunları yukarıdaki gibi değişkenlere benzetebilirsiniz. Bunlar
AX ,BX , CX, DX = Bunlar matematiksel ve mantıksal işlemlerde kullanılır. Bu registerler çalışan özel opkodlar vardır.
SI, DI = Yazı, veri arama veya aktarımı gibi işlemler
BP, SP = Yığın işleleri ile ilgili
IP = Komut noktası o andaki nokta
ES, DS, CS, SS = Segment işlemleri ile ilgili
F = Bayrak registeri, Bu registerin bitlerine göre işlemler yapılır yada yapılmaz
Bunlardan başka korumalı modu sistem ile ilgili DR0-DR7 ve matematik işlemcini St(0) - St(7) registerleri bulunmaktadır. Ayrıca MMX ile birlikte yeni registerler gelmiştir.
Registerler kullanım şekline göre yada yapılarına göre değer alabilirler. Örneğin AX register diğer BX,CX,DX registerler gibi 8 bit 8 bit şeklinde değer alabilir yada tamamen 16 bit şeklinde değer alabilir. Açılımı söyledir:
AX = AL ve AH
BX = BL ve BH
CX = CL ve CH
DX = DL ve DH
Burada 'L' low demektir ve ilk 8 biti (1byte) temsil eder. 'H' ise high demektir ve son 8 biti (1 byte) temsil eder.
SI, DI registeler genel amaçlı kullanıldıkları gibi işlemcinin tahsis ettiği bazı özel komutlarla beraber veri aktarma, doldurma, silme yada karşılaştırma işlemlerini yerine getirir.
SI, DI, BP, SP, IP,ES,CS,DS,SS, FS, GS,F registerler daima 16 bittir (2byte) ve yukarıdaki registerler gibi düşük-alçak bit şeklinde ayrılmazlar. Ancak komutlarla bu bitlere müdahile ve değiştirmek mümkündür.
AX,BX,CX,DX registerler matematiksel işlemler, lojik işlemler, sayma işlemleri, ve bazı komutlarla string işlemlerinde kullanılır.
AX register "akumulatör" olarak adlandırılır. Bu register ile çalışan ve sonuçları bu registere aktaran pek çok opkod vardır.
BX register "base = taban" register olarak adlandırılır. Örneğin XLAT gibi komutlar bu register ile çalışır ve genelde AX registere yakın ve yardımcı olarak kullanılan bir registerdir.
CX register "Counter = sayaç" olarak nitelendirilir. Bu register sayaç ve dongü konularına tahsis edilmiştir. Yine pek çok opkod bu registere göre sayma ve sonuçlandırma işlenmleri yapar.
DX register yine diğer registerler gibi birçok işlemde kullanıldığı gibi bölüm sonu kalan sayısı için AX registerle beraber kullanılır.
Bu registerler DOS ortamında 16 bit yada 8 bit olarak çeşitli interrupt (kesme) fonksiyonlarınıda oluştururlar.
CS,DS,ES,GS,FS registerler programın bulunduğu hafıza bölgesi ile ilgili tanımlamarı yaparlar. Örneğin bir yere veri aktarılacaksa ve bu bölge programın bulunduğu bölgenin dışında ise bu registerler ile exstra olarak gösterilerek aktarma yapılabilir.
Bütün registerler değer alırken başına 'MOV' komunu alırlar. Bu komut yükle anlamındadır ve "mov ax,1020" dersek bunun anlamı "ax registere 1020 yükle" olur. Buradan sonra ax register değiştirilmediği sürece 1020 değerini almış olur. IP ve F register hariç bütün registeler MOV komutu işle değer alır, veya değer yazar.
SI, DI registerler CPU' nun onlara tahsis ettiği komutlar ile hızlı ve en yüksek seviyede data aktarma, arama, işleme yeteneği kazanır. Aynı zamanda diğer registerler ile matematiksel ve lojik işlemlerde de kullanalabilirler.
BP, SP, SS registerler yığın için kullanılır. Yığın kelimesi açıklamak gerekirse programın çalışması için saklanan değerlerdir. Yani programda aynı registeri kullanarak farklı işlemler yapmamız gerekiyorsa önceki register değerini saklamamız gerekiyor. Aksi halde bu değeri kaybederiz ve yeniden kazanmak mümün olmayabilir. Bunu iki yolla yaparız. Ya bir yere bu değeri yazarız yada sistemin bize verdiği saklama komutları ile hafızada saklarız. BP ve SP registerler işte bu hafızada yığılan saklana değerlerin yeni gösterir
IP register özel bir registerdir ve direk olarak kullanılamaz. Programın o andaki çalışma adresini belirtir ve değiştirmek için CPU' nun verdiği özel debug registerler kullanılır.
ES, DS, CS, FS, GS, SS registerler hafızayı daha iyi ve güçlü kullanmayı sağlamak, doğru veri kontrolu ve aktarımı sağlamak ve programın çalıştığı yeri saptamak için kullanılır. Bazılarının bilinçsizce değiştirilmesi programda büyük sorunlara yol açar ancak hiç bir deneme yada sorun sisteme kalıcı bir zarar vermez, reset işlemi ile herşey eskiye döner.
F register Türkçe adıyla bayrak register sistemin karşılaştırmalar, olay - sonuç - durum gibi hayati hallerine yön verme gibi etkilere sahiptir. Bütün atlamalar bu registerin bitlerine bağlı olarak oluşur. Bir karşılaştırmanın sonucunda bu registerin sabit ve belli bitleri set (1) veya reset (0) olarak durum bildirimi yaparlar. Şartsız atlama komutu hariç bütün atlama komutları bu registerin bitlerine bakarak atlama yaparlar.
Modlar
8088 işlemciler hem 16 bit bir CPU idi hem şu anda kullandığımız işlemcilere göre daha yavaş ve daha az özellikliydi. Sanal86 modu yoktu. Zamanına göre yeterli görülsede sonradan bunun asla yetemeyeceğini anlaşıldı ve 80286 ile beraber yeni bir16 bitlik işlemci ve mod stratejisi geliştirildi. Buna göre hem eskiye uyumlu olarak 8088 gibi çalışacak hemde yeni nesil programları destekleyecekti.. Ancak asıl düzenlemenin 80386 sonrası işlemcilerde yapıldığı kabul edilir. Uzun süreli DOS işletim sisteminin hakimiyeti ve korumalı modun DOS altında zahmetli programlanması bizim bu yeniliklerden ve birçok avantajdan istemeyerekte olsa mahrum kalmamıza neden olduı. Windows ile beraber 80386' dan beri gelen değişim ve avantajlar kullanıma geçti. En başta birden çok program çalıştırma, büyük adresleme yapabilme ve zaten var olan 32 bitlik komutları tam anlamıyla kullanma bunlardan sadece bazıları.
Şu an kullandığımız 386+ işlemcili sistemlerde üç mod var. Bunlar: Gerçek mod, Korumalı mod ve Sanal mod. Bunların arasında çalışma, adresleme, hafıza kullanılımı gibi pek çok farklılıklar var. Bilgisayar ilk çalıştırıldığında gerçek modda çalışır. Daha sonra işletim sistemi onu istediği moda geçirir.
DOS işletim sistemi gerçek modda çalışır. Bu mod tek (single) moddur ve yardımcı emulasyonlar olmadığı sürece aynı anda tek program çalıştırılır. Ayrıca adresleme şekli segment ve offset düzenine bağlı. Bir programında bir yerin adresi Seg:Off olarak gösterilir ve segmentler 16' nın katları şeklinde artar. Bir verinin yada programı fiziksel adresini segment 16 ile çarpılır. Bu onaltılık sistemde 10h ile çarpmaktır ve kısaca yanına bir 0 eklemek yeterli. Bundan sonrada offset çıkan sayı ile toplanır ve bize fiziksel hafızadaki adresi verir.
Korumalı mod çoklu işlemi (multi) destekleyen sağlam, ve güçlü bir hafıza stratejisi olan bir moddur. Gerçek mod ile korumalı mod arasındaki en önemli farklardan birisi hassasiyet derecelendirmesidir. Buna "ring" de denir ve korumalı modda toplam 4 hassasiyet seviyesi vardır. En düşük seviye 0 aslında en büyük, gerçek zamanlı seviyedir. Gerçek modda bir program hafızada başka bir program rahatça ulaşabilir ve değiştirebilir. Bunun pek sakıncası ve bilgi kaybı açısından riski vardır. Korumalı modda ise bu ancak iki programda 0 (ring 0) olması durumuda mümkündür.
Sanal mod gerçek mod ile korumalı mod arasındaki emulasyonu sağlar. Böylece korumalı modda gerçek mod programlarınıda kullanabilmek mümkün olur.
CS, DS, ES registerler bir bakıma gerçek moda ait segment registerleridir. Çünkü korumalı modda segment kavramı yoktur. Ayrıca korumalı mod bazı gerçek mod komutlarının direkt olarak çalışmasına izin vermez. Örneğin dos işletim sisteminin vazgeçilmez komutlarından int nn ( interrupt) komutu ancak bir VXD içerinde çalışır. VXD dosyaları daima en yüksek modda çalışan programlardır.
32 bit registerler
Önceki konularda 16 bir registerleri anlatmıştım. Buna göre 16 bit registerler 8 olarak ikiye ayrılabilirler. 32 bit registerlerde registerin 16 bitlik kısmı aynı şekilde kullanılabilir ancak eklenen 16 bitlik kısım böyle 8' er bit yada tek başına 16 bit halinde kullanılamazlar. Ancak lojik komutlar ile bit kaydırması yaparak erişilir. 16 bitlik bir register en fazla 65535 değerini alırken 32 bitlik bir register 4294967295 değerine kadar alır.
Yukarıda anlattığım bütün registerler başlarına bir 'E' (extended) harfi alarak 32 bit olurlar.
AX --> EAX
BX--> EBX
F ---> EF
SP --> ESP .... gibi
Registelerin normal olarak 'mov' komutu ile adresleme ve yükleme yaptığını daha önce belirmiştir. Buna göre AX registere örneğin 1234h sayını yüklemek istediğimizde :
mov ax,1234h
yazmamız yeterli Ayrıca 32 bit bir register olarak kullanmak istediğimizde :
mov eax,12345678
şeklinde kullanırız. Bu örnekte görüldüğü gibi bütün registerler MOV regname, değer şeklinde yüklenirler.
Adresleme ise bir yerden değer yükleme yada bir yere değer aktarma demektir. Bir adresten değer alıp registerleri yüklemek için [] köşel parantezi kullanırız. Ayrıca editör ile bir asm dosyası oluşturduksa ve sembolik olarak bir yeri veri adreslemesi için kullanıyorsak LEA komutunu yada adresin yanına offset komutu kullanabiliriz.
mov ax word ptr,[1234]
mov cx,word ptr,[sayac1]
mov dl, byte ptr [sayi]
mov ax,[bx]
mov si,[ax+bx]
şeklinde kullanılır. Ayrıca sembolik adreslemeler için:
lea ax, string1
lea si,aktarma_start
mov si, offset aktarma_start
olarak kullanılabilir.
Şimdi register değerlerinin değiştirilmesi ile ilgili örnekleri inceleyelim:
Örnek1: Burada AX registerin düşük 8 biti değiştiriliyor
mov ax, 1234h -------> ax=1234
mov al,0aah -------> ax = 12aah
Örnek2:
mov ax,4423 ----> ax=4423
mov bx,1167 ----> bx = 1123
mov al,bl -----> al= bl yani al= 23 iken 67 oluyor
mov word ptr [sonuc],ax -----> sonuc adresine ax register yazılıyor. AX = 4467
Adreslemeler
Adresleme konusu assembler dilinde önemli bir konudur. Adresleme hafızanın bir bölgesini kullanma yada bir bölgesini gösterme olarak tanımlanalabilir. Herhangi bir değeri saklama, bir hafıza bölgesi üzerinde çalışma yada kısaca BUFFER olarak kullanma olanağı sağlar. Örneğin klavyeden girilen bir yazıyı inceleyebilmek ve üzerinde araştırma yapabilmek için bir bölgeye yazılması gereklidir. Bunun için kullanılan fonksiyona göre registerlerle önceden ayrılmış bir adrese aktarma yapılır. Daha sona burada kullanıcının istediği şekilde bu yazı üzerinde değişiklik, hesaplama veya mantıksal işlemler yapabilir.
Başlıca adreslemeler şunlardır:
- Register adreslemesi : Registere bir değeri yüklemek için registerin biti kadar bir adresten yükleme yapılması.
Örneğin: mov eax, [sayi1] veya mov eax,sayi1
Burada eax register sayi1 ile belirtilen hazıfa adresinden o andaki değeri alıp eax registere yüklüyor.
- Değer saklamak için adresleme (Dolaylı adresleme) : Burada ileride göreceğimiz PUSH ve POP komutuna benzer olarak istenilen bir hafıza adresine saklama işlemi yapılır. Bu bir register olabileceği gibi bağımsız bir değerde olabilir.
Örneğin : mov dword ptr [deneme],ebx yada mov word ptr [04010000],1234h gibi
- Hafıza bölgesi adreslemesi : Bu adresleme ile register hafızanın herhengi bir bölgesi işaretlenir. Bunun için MOV komutu yerine LEA komutunu kullanabiliriz. MOV komutu registere verilen değeri yükler yada adresler. LEA komutuda aynı işi yapar. Ancak MOV komutundan önemli farkı vardır. Verdiğiniz yerdeki değeri değil adresi yükleme yapar. Örneğin:
debug 'Yazi1',0
lea eax, debug şeklinde verilince eax registere 'Yazi' değeri değil bulunduğu yerin adresi yüklenir. Böylece biz bu vernini başından itibaren istediğimiz gibi çalışma yapabiliriz.
- Dolaysız adresleme : Bu adresleme registerler arasındaki değerlerin hafıza adresi olarak kullanılması ile oluşan adreslemedir. Buna göre elimizde hiçbir değer yok sadece registeler vardır. Yaygın bir kullanıma sahiptir. Örneğin:
mov dword ptr [esi] ,eax
mov byte ptr [eax],bl
mov dword ptr [esi],ebx
mov word ptr [edi+esi],ax
gibi sadece registeler arası adreslemedir.
Yığın (Stack) konusu
Komutunu vermeden önce yığın konusunun ne olduğundan bahsetmekte yarar var. Yığın kısaca saklamak istediğimiz değerleri hafızanın ESP (veya 16 bit olarak SP) registerler gösterilen bölgeye yığılması ve dolayısıyla saklanmasıdır. Kullanıdığımız değerleri burada örnek olarak 32 bitlik değerler olarak kabul edersek daha anlaşılır bir şeklide 4 * 8 bit anlamına gelirki bunu 4 tane sayı veya simge olarak düşünebiliriz. Örneğin programımızda eax registerde 12345678 değerinin olduğunu düşünelim. Ancak yine eax register ile bir işlem yapacağız ve bu değer değişecek. Bunu saklamamız gerekiyor. Yukarıda örnek verdiğim şekilde adresleme yaparak hafızanın bir yerine yazar ve saklarız ama ya bu register bir sayaç yada çok sayıda değişme gösteriyorsa. İşte bunun için CPU iki tane register ile bu sorunu en çözümlü ve basit olacak şekile indirmiştir. Normalde gerçek mod segment yapısına sahip olduğu için yığın işleminde sadece hafıza adresini göstermesi yetmez. Bunun için yığın bölgesinin tam adresinin bulunması amacıyla SS (Stack Segment) adıyla bir yardımcı register daha vardır. Gerçek modda yani DOS' da yığın bölgesini SS:SP şeklinde algılarız ve işlemler buna göre yapılır. Korumalı modda böyle bir segmentleme ihitiyacı yoktur. Dosya hafızaya yüklendiği anda Windows otomatik olarak o program için bir bir yığın noktası belirler ve ESP registere bu noktayı yükler. Hiçbir CPU 8 bitlik bir değeri saklamaz. En düşük register veya değer saklaması 16 bittir. Program içinde ve her modda değerleri bu yığın bölgesine atmak ve almak için PUSH ve POP komutları vardır. Bu komutlardan PUSH komutu saklanılan registeri yada değeri değiştirnmez, aynbı değerin kopyasını alır. POP komutu ise verilen registerin değerini siler ve yüklediği değeri uygular. Burada 32 bit registerler kullandığımızı varsayabilir. PUSH komutu değeri yığın bölgesine yazdıktan sonra ESP registerin değerini 32 bit (4byte) eksiltir. Eğer kullandığımız register 16 bit ise 16 bit (2byte) eksilme yapar. Burada önemli konu ise CPU' nun değer olarak çalışılan moduda dikkate almasıdır. Örneğin 32bit korumalı modda 16bitlik bir değeri yığına atmak istersek bunu daima 32 bir olarak kabul edecek, değer vermediğimiz kısmı ise 0 olarak kabul edecektir. Bu yeni değerleri saklamak için uygun adreslemeyi yapmasıdır. Aynı şekilde POP komutu değeri yığın bölgesinden alır ve ESP register +4 artar. Bu konu biraz karışık gibi görünebilir. Ancak kullandıkça öyle olmadığını göreceksiniz. Önemli bir hususda girilen değerlerin çıkış şeklidir. Örneğin sırasıya 10 20 30 değerleri push komutu ile yığında saklanmışsa geri dönüş daima 30 20 10 şeklinde olacaktır. Yani son giren ilk çıkar kuralı geçerlidir. Eğer bu sıralama bozulursa yığına atılan değerler hep yanlış çıkacaktır. Normalde API çağrıları hariç kullandığınız her PUSH komutu için POP komutuda kullanarak kullandığınız her değeri yığından almanız gerekir. Yine önceden girilmiş bir değer ESP register vasıtasıyla eksiltmeden yada POP yapmadan alınabilir. Bunun için örnmeğin mov eax, [esp-8] gibi bir komut yeterli.
Şimdi daha iyi anlaşılabilmesi için
ESP = 643038 olduğu farzedelim
mov eax,12345678h
push eax ; -----> eax registerin 12345678 değeri yığına atılıyor. ESP = 643038 - 4 = 643034 oldu
mov ax,0aa5599aah
push eax ; -----> eax registerin yeni değeri olan 0aa5599aah değeri yığına atıldı. ESP=643034-4= 643030 oldu.
pop eax ; -----> son giren ilk çıkar. eax = 0aa5599aah oldu ve yığın 643030+4 = 643034 oldu
pop eax ; ----> önceki değer çıktı. eax = 12345678h ve ESP = 643034+4 = 643038 oldu.
Burada görüldüğü gibi eax registerin ilk değeri 12345678h saklandı daha sonra 0aa5599aah ile işlem yapıldı ve tekrar yığından alınarak baştaki değerine dönüldü. Burada bir tiyo vermek gerekirse illaki girilen registerler çıkılacak diye bir kural yok. Yani iz yukarıda eax register ile yığına attık. Ancak dönüşte ebx, ecx,edx,... gibi bütün registeleri kullanabiliriz. Örneğin:
mov ax,12345678h ; eax = 12345678h
push eax ; eax yığına atılarak saklanıyor
mov eax,0 ; eax registere 0 değerini verdik
pop ebx ; yığındaki son değer alınıyor ilk başta girdiğimiz 12345678 değeri artık ebx registerde yüklü.
Yığın konusu döngüler içinde döngü sayısını tutmak içinde kullanılır. Bu anlamda yığın çok önemlidir. Ayrıca Bayrak registerde (F - EF) yığın içinde saklanılabilir. 386+ sonrası komutlar arasında tek komutla bütün registerleri yığına atan ve yine tek komutla hepsini yığından atan PUSHA ve POPA komutu eklenmiştir.
***********************************************************************************************