Asembler Dili (Assembly Language) 1
Assembler
Giris
Assembler
merkezi islemci olan bütün sistemlerde ve isletim sistemlerinde
"kullanici - program - makine" iletisimi saglayan ve sembolik komutlar
sayesinde programlamayi sayilar düzeyinden sembollere çikaran bir
dildir. Normalde merkezi islemci (CPU) komutlari 1 ve 0 olarak anlar ve
buna göre yapilmasi gereken islemleri yerine getirir.Kisacasi Assembler
dili bir makine dilidir diyebiliriz…
ASCII mantigi
Herhangi
bir EXE dosyaya bir editor ile baktigimiz zaman anlamsiz ve karmasik
birçok semboller ve karakterler görürüz. Bunlar o an orada bulunan
sayilara karsilik gelen ve ASCII olarak adlandirilan sembollerdir. Bu
semboller hem sayilari,hem büyük/küçük harfleri,hemde birçok isareti
içerir. ASCII tablosu 256 isaretten meydana gelir ve her sayi karsiligi
bir semboldur. Ilk 32 karakter kontrol karakterleri adi verilen ve
görülmeyen karakterlerdir.Bunlar yazici, string gibi islemlerde tek
baslarina yada birden fazla görev alirlar. Örnegin 7 sayisi ASCII
karakterlerinde 'BEEP' anlamina gelir ve programinizda eger 'deneme
yazisi',7,0 seklinde kullanirsaniz yazi sonrasi bir 'beep' sesi
duyarsiniz. Yada 10 ve ardindan 13 kullanirsaniz yaziniz bir satir
asagiya inip paragrafin en basina gelir. Örnegin: 'deneme
yazisi',10,13,0
48 - 58 arasi ise sayilardir. Buna göre:
48 = '0'
49 = '1'
50 = '2' ....
sayi sekillerine denk gelir ve bu standarttir.
Büyük
harfler 65' den baslar 90' a kadar devam eder. Ayni sekilde küçük
harflerde 97 ile 122 arasindadir. Bundan sonra 256 sayisina kadar
karsilik gelen karakterler özel karakterlerdir. Bunlarla pek çok sekil
olusturulabilir.
Sayi sistemleri
Yukarida
verdigim örnekleri inceleyenler ve herhangi bir hex editör ile
dosyalara bakanlar bir gariplik oldugunun farkina varmislardir. Hex
editörde bütün sayilar iki basamakli ve içinde a,b,c,d,e,f gibi
karakterler içeriyor. Ayrica 1 karakterine karsilik gelen sayi
yukaridaki gibi 48 degil 30 oldugunu görürler. Iste programlar daha
derli toplu oldugu için, ASCII gösterimine daha yatkin oldugu için ve
diger sayi sistemlerine göre daha kullanisli oldugu için "Onaltilik
sistem" diye adlandirilan hex (hexedecimal) sistemi kullanirlar. Burada
sunu açiklamakta yarar var, hex sistemi sadece kullanicilar ve
programcilar içindir. CPU sadece 1 ve 0 dan olusan ikili sistemi
(binary) anlar.
Simdi konuyu toparlarsak üç degisik sayi sistemi oldugunu görürüz:
1 - Onluk sayma sayilari (dec) 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16... seklinde devam eden bildigimiz sayi sistemi...
2
- Onaltilik sayilar (hex) 0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,10,11...
seklinde devam eden ve "a...f" arasi ekstra olarak 6 sayi daha
fazlaligi olan sayi sistemi...
3
- Ikili sistem (binary) 0,1 Bu sistemde ya sifir yada bir olacaktir.
0 kapali 1 ise açik mantigindadir. Bu sistem makina dilinin temelini
olusturur ve programlamasi ve yazmasi çok oldugu için assembler diline
gerek duyulmustur...
Normal
sayilar hepmizin ilkokuldan beri ögrendigi sayi sistemidir. Sayi
dizilimi 0 ile 9 arasidir ve her 9 sonrasi bir alt basamak bir yükselir.
Onaltilik
sistemde toplam basamak yükseltme sayisi 16' dir. Biz normal sayma
sisteminde 9 sayisindan sonra 10 geldigini biliyoruz. Onaltilik
sistemde ise 9 sayisindan sonra 0A,0B,0C,0D,0E,0F ve bundan sonra 10
gelir. Dolayisiyla 0A onaltilik sayisi normal sayi sisteminde 10 sayina
esit gelir. Ayni sekilde 10hex sayisi normal sayi sisteminde 16
sayisina esit gelir. Kisaca onaltilik sayi sistemi 16' nin katlari
seklinde büyür.
Assembler programlarinda kullanilan sayilar su sekillerde simgelenerek birbirinden ayrilirlar:
-
'd' harfi yada harfsiz bu bildigimiz onluk sistemde demektir. Eger bir
kaynak kodunda sayinin yaninda (önceden bütün sayilarin hex olacagini
belirten .radix16 gibi bir komut kullanilmamissa) hiçbir simge yoksa o
sayi göründügü degerinde bildigimiz onluk tabanda bir sayidir. Eger
onaltilik kurallariyla yazilmis (a,b,c,d,e,f içerikli) ve yaninda
hiçbir isaret yoksa derleyici burada 'illegal number' seklinde bir hata
mesaji verir.
-'h'
harfi ise her zaman onaltilik sistemde bir sayi oldugunu belirtir. Bu
durumda normal onluk sistemde bir sayi yazip yanina 'h' koyarsaniz bu
sayi onaltilik olarak kabul edilecek ve yazdiginiz deger degilde
onaltilik sistemin degerini alacaktir. Eger onaltilik bir sayi ve
yanina 'h' isaretini koyarsaniz o sayi onaltilik olarak ve verdiginiz
degerde kabul edilecektir.
-'b'
harfi ise ikilik sistemi belirtir ve adi üzerinde sadece 1 ve 0
kullanabilirsiniz. Aksi halde hata mesaji verir. Asagida hesaplamasini
göreceksiniz ama önbilgi olarak vermek gerekirse yazmadiginiz
basamaklar daima 0 olarak kabul edilecektir.
Örnekler:
323 ----> onluk sayi sisteminde ve degeri 323
323d----> onluk sayi sisteminde ve degeri 323
323h----> onaltilik sayi sisteminde ve degeri 803
02a-----> Hatali sayi. Içinde onaltilik sayi sistemine ait 'a' kullanilmis ama sayinin ne oldugu belirtilmemis
02ah----> Onaltilik sayi onluk karsilik degeri 42
1110b--> Ikilik sistemde bir sayi ve normalde 00001110b olarak kabul edilir ondalik 14, onaltilik 0e sayisina denk gelir
1310b--> Hatali sayi yaninda 'b' ile ikilik oldugu belirtilmesine ragmen içinde 3 sayisi mevcut. 1 veya 0 olmak zorunda.
Çevrimler ve hesaplamalar:
Buraya
kadar herkes bir fikir sahibi olmustur. Burada da bunlarin nasil ve
neden böyle oldugunu görecegiz. Öncelikle sunu belirtmem gerekiyor:
onluk zaten biliyoruz ama onaltilik ve ikilik sistem mutlaka bilinmesi
gerekiyor. Programlarinizda, denemelerinizde hep bu sistemler
kullanilacak. Komutlar hep bu sistemlere göre çalisacak ve bazilari
sadece bu sistemlerdeki degerlere göre hareket edecek. Bunun için bir
assembler programcisinin mutlaka bu sistemleri bilmesi ve kullanmasi
gerekiyor.
Normal
bildigimiz sayma sayilarinin 0 dan baslayip 1,2,3,4,5,6,7,8,9,10,11...
gibi gittigini biliyoruz. Ona onluk sistem adini veren kural ise her 10
sayi sonrasi bir basamak altini basamak atlatmasidir. Onaltilik sistem
ise yine 0 dan baslayip 1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,10,11.. seklinde
devam eder. Burada görüldügü gibi onaltilik sistem her 16 sayi sonrasi
basamak atlatiyor. Ayrica a,b,c,d,e,f olmak üzere 6 tane daha fazladan
sayiya sahip. O zaman onaltilik bir sistemde deger 10 ise onluk
sistemede o sayi 16 demektir. Yani 16 nin katlari olarak artmaktadir.
10h = 16
20h = 32
a4h = 164 gibi...
Onaltilik bir sayiyi onluk sayiya nasil çeviririz?
Önce
en kolay sekliyle baslayalim. Yukaridaki örnekte a4h onaltilik
sayisinin 164 oldugunu görüyoruz. a4h sayisina bakinca iki basamaktan
oldugunu biliyoruz ve yine biliyoruz ki onaltilik sayi sistemi 16' nin
katlari ile büyür ve 'a' harfinin onluk sistemdeki degeri 10 , o zaman
elimizde 0a +0 seklinde bir sayi var:
0a* 16 + 4 olacak. 0a = 10 olduguna göre: 10 *16+4 = 164
Birinci
basamak her zaman kendi degerindedir. Sayi 0ah,0bh,0ch.. olsa bile
onluk sisteme çevirilince birlik basamak olarak digerlerine eklenir.
Diger basamaklarda ayni bildigimiz matematik kurallarinda oldugu gibi
basamak çarpani esas alinarak çarpilir. Nasilki 164 sayisinin 16*10+4
oldugunu biliyorsak onaltilik sistemde de 164 sayisinin 0ah*10+4
oldugunu biliriz.
Daha büyük sayilarda onaltilik sistemden onluk sisteme çevrim söyle yapilir:
Elimizde
03f4ah gibi bir sayi oldugunu farzedelim. Bu sayi "3 - f - 4 - a"
olarak 4 basamakli ve onaltilik sistemde. Her zaman sondan basliyoruz
ve 2'1, 2'2, 2'3 gibi gidiyoruz. Burada çarpan sayilari 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 sayimizi bulmanin zamani 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 alistirma yaptikça daha çok anlasilir. Zaten
çogu kez bende büyük sayilarin çevriminde Windows' un hesap makinasini
kullaniyorum. Bunun assembler rutin yazip çevirmek yada _wsprintf gibi
bir fonksiyonunu kullanmak aslinda daha kolay. Ama mantiginin bilinmesi
gerekir.
Ondalik sayilarin ikili sisteme çevrilisi
Ondalik
bir sayiyi ikilik bir sayi sistemine çevirmek çok kolaydir. Öncelikle
ikilik bir sayinin 1 ve 0 sayilarindan olustugunu hatirlayalim. Bu
düzen nasil olusur? Bir sayi ikiye ya tam bölünür yada kalanli bölünür.
Eger tam bölünüyorsa 0, kalanli bölünüyorsa 1 olur. Kalanli bölünme
drurumunda 1 olur ve sayidan bir eksiltilerek isleme devam edilir.
Örnegin:
22 /2 0
11/2 1
5/2 1
2/2 0
1/1 1
olur ve daima tersden yazilarak ikili sisteme çevrilmis olur. O zaman
22 ondalik sayisi 10110 ikili sistemine esit olur. Bu islem esasen
bundan sonraki konuda görecegimiz bitlere ayirma islemidir.
Bit, Byte, Kilobyte, Megabyte, Gigabyte...
Her
zaman duyariz su program diskte su kadar kilobyte yer kapliyor diye.
Yada yeni bir disk aldim 80gb gibi. Bunlar aslinda datanin uzunlugudur.
Yani verinin kapladigi alandir. Bu büyüklükler temeldir ve disk,hafiza,
program içinde aynidir. Sadece bit büyüklük olarak farklilik gösterir
ancak digerleri hep 1024' ün katlari olarak artar ve birimlenir.
1 bit en fazla 1 birim olabilir,
1 byte 0 ile 256 arasinda deger alir.
1 kilobyte 0 ile 65535 arasinda deger alir.
1 megabyte 0 ile 4294967295 arasinda deger alir.
Birimler sirasiyla su sekilde küçükten büyüge dogru siralanir: bit,
byte, kilobyte, megabyte, gigabyte, terabyte... Burada en küçük birim
olan 'bit' tek basina kendiden sonraki birimin 8/1 ni olusuturur.
Kisaca açarsak 1 byte = 8 bittir. Ayni sekilde 2 byte 16 bittir. Bunu
söyle göstererek daha iyi anlasilir hale getirebiliriz:
Elimizdeki sayi: 256 ve bu sayinin onaltilik sekli 0FFh, bunu ikili sisteme çevirirsek :
256/2 0
128/2 0
64/2 0
32/2 0
16/2 0
8/2 0
4/2 0
2/2 0
1/1 1
sonucunu elde ederiz. bu durumda 256 sayisi 1 byte ve 8 bitten olusur.
Bu 8 tane bitin illaki 1 ve 0 olmasi gerekmez. Sonuçta 8 tane 1 veya 0,
1 byte'i olusturur. Byte ile beraber bütün üst birimler 1024 ' ün
katlari olarak artar.
1024 byte = 1 kb (kilobyte)
1024 kb = 1 mb (megabyte)
1024 mb = 1 gb (gigabyte)
1024 gb = 1 tb (terabyte)
Seklinde açiklanabilir. Makine dili islemleri CPU' a ikili sistemde
gönderildigini söylemistik. Yukaridaki birimlere bakinca sadece ikili
sistemde programlamanin ne kadar zor oludugu görülmektedir. Assembler
dili bunun için gelistirilmistir. Kullandigimiz programlar dili veya
arabirim ne olursa olsun sonuçta assembler kodlarina döner.
Kullandigimiz *.exe ve *.com gibi dosyalar aslinda assembler dilinden
olusan makine dilini içerir ve program hafizada çalismaya baslayinca
CPU' a (merkez islem birimi) 1 ve 0 olarak çevrilerek gönderilir.
Delphi, C++, VB gibi dillerde kullandigimiz hemen hemen bütün komutlar
semboliktir. Bu komutlara karsilik gelen makine komutlari setleri
vardirki bunlar sayesinde tekrar tekrar ayni kodlari yazmak yerine daha
düzenli ve daha optimize bir sekilde makine dili programlari
olusturulabilir. Makine dili assemblerdir. Tüm assembler komutlarina
birden "opkod" olarak isim verilir.
Registerler
Registeler
en basit sekilde söyle anlatilabilir: a=1 Burada "a" isimli bir
degiskenimiz var ve ona bir degerini atiyoruz. Bundan sonra yeni bir
deger atamadigimiz sürece a her zaman 1 olacaktir. Aynen bunun gibi
CPU' nun her türlü islemlerini yerine getirmesi için isimleri sabit
registerleri vardir. Bunlari yukaridaki gibi degiskenlere
benzetebilirsiniz. Bunlar;
AX ,BX , CX, DX = Bunlar matematiksel ve mantiksal islemlerde kullanilir. Bu registerler çalisan özel opkodlar vardir.
SI, DI = Yazi, veri arama veya aktarimi gibi islemler
BP, SP = Yigin islemleri ile ilgili
IP = Komut noktasi o andaki nokta
ES, DS, CS, SS = Segment islemleri ile ilgili
F = Bayrak registeri, Bu registerin bitlerine göre islemler yapilir yada yapilmaz
Bunlardan
baska korumali modu sistem ile ilgili DR(0)-DR(7) ve matematik
islemcisi St(0) - St(7) registerleri bulunmaktadir. Ayrica MMX ile
birlikte yeni registerler gelmistir.
Registerler
kullanim sekline göre yada yapilarina göre deger alabilirler.
Örnegin; AX register diger BX,CX,DX registerler gibi 8 bit 8 bit
seklinde deger alabilir yada tamamen 16 bit seklinde deger alabilir.
Açilimi 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 registerler genel amaçli kullanildiklari gibi islemcinin tahsis
ettigi bazi özel komutlarla beraber veri aktarma, doldurma, silme yada
karsilastirma islemlerini yerine getirir.
SI,
DI, BP, SP, IP,ES,CS,DS,SS, FS, GS,F registerler daima 16 bittir
(2byte) ve yukaridaki registerler gibi düsük-alçak bit seklinde
ayrilmazlar. Ancak komutlarla bu bitlere müdahale ve degistirmek
mümkündür.
AX,BX,CX,DX
registerler matematiksel islemler, lojik islemler, sayma islemleri, ve
bazi komutlarla string islemlerinde kullanilir.
AX register "akümulatör" olarak adlandirilir. Bu register ile çalisan ve sonuçlari bu registere aktaran pek çok opkod vardir.
BX
register "base = taban" register olarak adlandirilir. Örnegin XLAT
gibi komutlar bu register ile çalisir ve genelde AX registere yakin ve
yardimci olarak kullanilan bir registerdir.
CX
register "counter = sayaç" olarak nitelendirilir. Bu register sayaç ve
döngü konularina tahsis edilmistir. Yine pek çok opkod bu registere
göre sayma ve sonuçlandirma islemleri yapar.
DX
register yine diger registerler gibi birçok islemde kullanildigi gibi
bölüm sonu kalan sayisi için AX registerle beraber kullanilir.
Bu registerler DOS ortaminda 16 bit yada 8 bit olarak çesitli interrupt (kesme) fonksiyonlarinida olustururlar.
CS,DS,ES,GS,FS
registerler programin bulundugu hafiza bölgesi ile ilgili tanimlamalari
yaparlar. Örnegin bir yere veri aktarilacaksa ve bu bölge programin
bulundugu bölgenin disinda ise bu registerler ile exstra olarak
gösterilerek aktarma yapilabilir.
Bütün
registerler deger alirken basina 'MOV' komunu alirlar. Bu komut yükle
anlamindadir ve "mov ax,1020" dersek bunun anlami "ax registere 1020
yükle" olur. Buradan sonra ax register degistirilmedigi sürece 1020
degerini almis olur. IP ve F register hariç bütün registeler MOV komutu
ile deger alir veya deger yazar.
SI,
DI registerler CPU' nun onlara tahsis ettigi komutlar ile hizli ve en
yüksek seviyede data aktarma, arama, isleme yetenegi kazanir. Ayni
zamanda diger registerler ile matematiksel ve lojik islemlerde de
kullanilabilirler.
BP,
SP, SS registerler yigin için kullanilir. Yigin kelimesini açiklamak
gerekirse programin çalismasi için saklanan degerlerdir. Yani programda
ayni registeri kullanarak farkli islemler yapmamiz gerekiyorsa önceki
register degerini saklamamiz gerekiyor. Aksi halde bu degeri kaybederiz
ve yeniden kazanmak mümkün olmayabilir. Bunu iki yolla yapariz. Ya bir
yere bu degeri yazariz yada sistemin bize verdigi saklama komutlari ile
hafizada saklariz. BP ve SP registerler iste bu hafizada yigilan
saklanan degerlerin yeni gösterir.
IP
register özel bir registerdir ve direk olarak kullanilamaz. Programin o
andaki çalisma adresini belirtir ve degistirmek için CPU' nun verdigi
özel debug registerler kullanilir.
ES,
DS, CS, FS, GS, SS registerler hafizayi daha iyi ve güçlü kullanmayi
saglamak, dogru veri kontrolu ve aktarimi saglamak ve programin
çalistigi yeri saptamak için kullanilir. Bazilarinin bilinçsizce
degistirilmesi programda büyük sorunlara yol açar ancak hiç bir deneme
yada sorun sisteme kalici bir zarar vermez, reset islemi ile hersey
eskiye döner.
F
register Türkçe adiyla bayrak register sistemin karsilastirmalar, olay
- sonuç - durum gibi hayati hallerine yön verme gibi etkilere sahiptir.
Bütün atlamalar bu registerin bitlerine bagli olarak olusur. Bir
karsilastirmanin sonucunda bu registerin sabit ve belli bitleri set (1)
veya reset (0) olarak durum bildirimi yaparlar. Sartsiz atlama komutu
hariç bütün atlama komutlari bu registerin bitlerine bakarak atlama
yaparlar.
Modlar
8088
islemciler hem 16 bit bir CPU idi hem su anda kullandigimiz
islemcilere göre daha yavas ve daha az özellikliydi. Sanal86 modu
yoktu. Zamanina göre yeterli görülsede sonradan bunun asla yetmeyecegi
anlasildi ve 80286 ile beraber yeni bir16 bitlik islemci ve mod
stratejisi gelistirildi. Buna göre hem eskiye uyumlu olarak 8088 gibi
çalisacak hemde yeni nesil programlari destekleyecekti... Ancak asil
düzenlemenin 80386 sonrasi islemcilerde yapildigi kabul edilir. Uzun
süreli DOS isletim sisteminin hakimiyeti ve korumali modun DOS altinda
zahmetli programlanmasi bizim bu yeniliklerden ve birçok avantajdan
istemeyerekte olsa mahrum kalmamiza neden oldu. Windows ile beraber
80386' dan beri gelen degisim ve avantajlar kullanima geçti. En basta
birden çok program çalistirma, büyük adresleme yapabilme ve zaten var
olan 32 bitlik komutlari tam anlamiyla kullanma bunlardan sadece
bazilari.
Su
an kullandigimiz 386+ islemcili sistemlerde üç mod var. Bunlar: Gerçek
mod, Korumali mod ve Sanal mod. Bunlarin arasinda çalisma, adresleme,
hafiza kullanilimi gibi pek çok farkliliklar var. Bilgisayar ilk
çalistirildiginda gerçek modda çalisir. Daha sonra isletim sistemi onu
istedigi moda geçirir.
DOS
isletim sistemi gerçek modda çalisir. Bu mod tek (single) moddur ve
yardimci emulasyonlar olmadigi sürece ayni anda tek program
çalistirilir. Ayrica adresleme sekli segment ve offset düzenine bagli.
Bir programda bir yerin adresi Seg:Off olarak gösterilir ve segmentler
16' nin katlari seklinde artar. Bir verinin yada programin fiziksel
adresi segment 16 ile çarpilir. Bu onaltilik sistemde 10h ile
çarpmaktir ve kisaca yanina bir 0 eklemek yeterli. Bundan sonrada
offset çikan sayi ile toplanir ve bize fiziksel hafizadaki adresi verir.
Korumali
mod çoklu islemi (multi) destekleyen saglam, ve güçlü bir hafiza
stratejisi olan bir moddur. Gerçek mod ile korumali mod arasindaki en
önemli farklardan birisi hassasiyet derecelendirmesidir. Buna "ring" de
denir ve korumali modda toplam 4 hassasiyet seviyesi vardir. En düsük
seviye 0 aslinda en büyük, gerçek zamanli seviyedir. Gerçek modda bir
program hafizadaki baska bir programa rahatça ulasabilir ve
degistirebilir. Bunun sakincasi ve bilgi kaybi açisindan riski vardir.
Korumali modda ise bu ancak iki programda 0 (ring 0) olmasi durumunda
mümkündür.
Sanal
mod gerçek mod ile korumali mod arasindaki emulasyonu saglar. Böylece
korumali modda gerçek mod programlarinida kullanabilmek mümkün olur.
CS,
DS, ES registerler bir bakima gerçek moda ait segment registerleridir.
Çünkü korumali modda segment kavrami yoktur. Ayrica korumali mod bazi
gerçek mod komutlarinin direkt olarak çalismasina izin vermez. Örnegin
dos isletim sisteminin vazgeçilmez komutlarindan int nn ( interrupt)
komutu ancak bir VXD içerisinde çalisir. VXD dosyalari daima en yüksek
modda çalisan programlardir.
32 bit registerler
Önceki
konularda 16 bir registerleri anlatmistim. Buna göre 16 bit registerler
8 olarak ikiye ayrilabilirler. 32 bit registerlerde registerin 16
bitlik kismi ayni sekilde kullanilabilir ancak eklenen 16 bitlik kisim
böyle 8' er bit yada tek basina 16 bit halinde kullanilamazlar. Ancak
lojik komutlar ile bit kaydirmasi yaparak erisilir. 16 bitlik bir
register en fazla 65535 degerini alirken 32 bitlik bir register
4294967295 degerine kadar alir.
Yukarida anlattigim bütün registerler baslarina 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 yaptigini daha önce
belirtmistim. Buna göre AX registere örnegin 1234h sayini yüklemek
istedigimizde :
mov ax,1234h
yazmamiz yeterli Ayrica 32 bit bir register olarak kullanmak istedigimizde :
mov eax,12345678
seklinde kullaniriz. Bu örnekte görüldügü gibi bütün registerler MOV regname, deger seklinde yüklenirler.
Adresleme
ise bir yerden deger yükleme yada bir yere deger aktarma demektir. Bir
adresten deger alip registerleri yüklemek için [] köseli parantezi
kullaniriz. Ayrica editör ile bir asm dosyasi olusturduksa ve sembolik
olarak bir yeri veri adreslemesi için kullaniyorsak LEA komutunu yada
adresin yanina 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]
seklinde kullanilir. Ayrica sembolik adreslemeler için:
lea ax, string1
lea si, aktarma_start
mov si, offset aktarma_start
olarak kullanilabilir.
Simdi register degerlerinin degistirilmesi ile ilgili örnekleri inceleyelim:
Örnek1: Burada AX registerin düsük 8 biti degistiriliyor
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 yaziliyor. AX = 4467
Adreslemeler
Adresleme
konusu assembler dilinde önemli bir konudur. Adresleme hafizanin bir
bölgesini kullanma yada bir bölgesini gösterme olarak tanimlanabilir.
Herhangi bir degeri saklama, bir hafiza bölgesi üzerinde çalisma yada
kisaca BUFFER olarak kullanma olanagi saglar. Örnegin klavyeden girilen
bir yaziyi inceleyebilmek ve üzerinde arastirma yapabilmek için bir
bölgeye yazilmasi gereklidir. Bunun için kullanilan fonksiyona göre
registerlerle önceden ayrilmis bir adrese aktarma yapilir. Daha sona
burada kullanicinin istedigi sekilde bu yazi üzerinde degisiklik,
hesaplama veya mantiksal islemler yapabilir.
Baslica adreslemeler sunlardir:
- Register adreslemesi : Registere bir degeri yüklemek için registerin biti kadar bir adresten yükleme yapilmasi.
Örnegin: mov eax, [sayi1] veya mov eax,sayi1
Burada eax register sayi1 ile belirtilen hazifa adresinden o andaki degeri alip eax registere yüklüyor.
-
Deger saklamak için adresleme (Dolayli adresleme) : Burada ileride
görecegimiz PUSH ve POP komutuna benzer olarak istenilen bir hafiza
adresine saklama islemi yapilir. Bu bir register olabilecegi gibi
bagimsiz bir degerde olabilir.
Örnegin : mov dword ptr [deneme],ebx yada mov word ptr [04010000],1234h gibi...
-
Hafiza bölgesi adreslemesi : Bu adresleme ile register hafizanin
herhangi bir bölgesi isaretlenir. Bunun için MOV komutu yerine LEA
komutunu kullanabiliriz. MOV komutu registere verilen degeri yükler
yada adresler. LEA komutuda ayni isi yapar. Ancak MOV komutundan önemli
farki vardir. Verdiginiz yerdeki degeri degil adresi yükleme yapar.
Örnegin:
debug 'Yazi1',0
lea
eax, debug seklinde verilince eax registere 'Yazi' degeri degil
bulundugu yerin adresi yüklenir. Böylece biz bu verinin basindan
itibaren istedigimiz gibi çalisma yapabiliriz.
-
Dolaysiz adresleme : Bu adresleme registerler arasindaki degerlerin
hafiza adresi olarak kullanilmasi ile olusan adreslemedir. Buna göre
elimizde hiçbir deger yok sadece registeler vardir. Yaygin bir
kullanima sahiptir. Örnegin:
mov dword ptr [esi] ,eax
mov byte ptr [eax],bl
mov dword ptr [esi],ebx
mov word ptr [edi+esi],ax
gibi sadece registerler arasi adreslemedir.
Yigin (Stack) konusu
Komutunu
vermeden önce yigin konusunun ne oldugundan bahsetmekte yarar var.
Yigin kisaca saklamak istedigimiz degerleri hafizanin ESP (veya 16 bit
olarak SP) registerler gösterilen bölgeye yigilmasi ve dolayisiyla
saklanmasidir. Kullandigimiz degerleri burada örnek olarak 32 bitlik
degerler olarak kabul edersek daha anlasilir bir seklide 4 * 8 bit
anlamina gelirki bunu 4 tane sayi veya simge olarak düsünebiliriz.
Örnegin programimizda eax registerde 12345678 degerinin oldugunu
düsünelim. Ancak yine eax register ile bir islem yapacagiz ve bu deger
degisecek. Bunu saklamamiz gerekiyor. Yukarida örnek verdigim sekilde
adresleme yaparak hafizanin bir yerine yazar ve saklariz ama ya bu
register bir sayaç yada çok sayida degisme gösteriyorsa. Iste bunun
için CPU iki tane register ile bu sorunu en çözümlü ve basit olacak
sekle indirmistir. Normalde gerçek mod segment yapisina sahip oldugu
için yigin isleminde sadece hafiza adresini göstermesi yetmez. Bunun
için yigin bölgesinin tam adresinin bulunmasi amaciyla SS (Stack
Segment) adiyla bir yardimci register daha vardir. Gerçek modda yani
DOS' da yigin bölgesini SS:SP seklinde algilariz ve islemler buna göre
yapilir. Korumali modda böyle bir segmentleme ihitiyaci yoktur. Dosya
hafizaya yüklendigi anda Windows otomatik olarak o program için bir bir
yigin noktasi belirler ve ESP registere bu noktayi yükler. Hiçbir CPU 8
bitlik bir degeri saklamaz. En düsük register veya deger saklamasi 16
bittir. Program içinde ve her modda degerleri bu yigin bölgesine atmak
ve almak için PUSH ve POP komutlari vardir. Bu komutlardan PUSH komutu
saklanilan registeri yada degeri degistirmez, ayni degerin kopyasini
alir. POP komutu ise verilen registerin degerini siler ve yükledigi
degeri uygular. Burada 32 bit registerler kullandigimizi
varsayabilir. PUSH komutu degeri yigin bölgesine yazdiktan sonra ESP
registerin degerini 32 bit (4byte) eksiltir. Eger kullandigimiz
register 16 bit ise 16 bit (2byte) eksilme yapar. Burada önemli konu
ise CPU' nun deger olarak çalisilan moduda dikkate almasidir. Örnegin
32bit korumali modda 16bitlik bir degeri yigina atmak istersek bunu
daima 32 bit olarak kabul edecek, deger vermedigimiz kismi ise 0 olarak
kabul edecektir. Bu yeni degerleri saklamak için uygun adreslemeyi
yapmasidir. Ayni sekilde POP komutu degeri yigin bölgesinden alir ve
ESP register +4 artar. Bu konu biraz karisik gibi görünebilir. Ancak
kullandikça öyle olmadigini göreceksiniz. Önemli bir hususda girilen
degerlerin çikis seklidir. Örnegin sirasiya 10 20 30 degerleri PUSH
komutu ile yiginda saklanmissa geri dönüs daima 30 20 10 seklinde
olacaktir. Yani son giren ilk çikar kurali geçerlidir. Eger bu siralama
bozulursa yigina atilan degerler hep yanlis çikacaktir. Normalde API
çagrilari hariç kullandiginiz her PUSH komutu için POP komutuda
kullanarak kullandiginiz her degeri yigindan almaniz gerekir. Yine
önceden girilmis bir deger ESP register vasitasiyla eksiltmeden yada
POP yapmadan alinabilir. Bunun için örnegin mov eax, [esp-8] gibi bir
komut yeterli.
Simdi daha iyi anlasilabilmesi için
ESP = 643038 oldugu farzedelim
mov eax,12345678h
push eax ; -----> eax registerin 12345678 degeri yigina atiliyor. ESP = 643038 - 4 = 643034 oldu
mov eax,0aa5599aah
push eax ; -----> eax registerin yeni degeri olan 0aa5599aah degeri yigina atildi. ESP=643034-4= 643030 oldu.
pop eax ; -----> son giren ilk çikar. eax = 0aa5599aah oldu ve yigin 643030+4 = 643034 oldu
pop eax ; ----> önceki deger çikti. eax = 12345678h ve ESP = 643034+4 = 643038 oldu.
Burada
görüldügü gibi eax registerin ilk degeri 12345678h saklandi daha sonra
0aa5599aah ile islem yapildi ve tekrar yigindan alinarak bastaki
degerine dönüldü. Burada bir tüyo vermek gerekirse illaki girilen
registerler çikilacak diye bir kural yok. Yani biz yukarida eax
register ile yigina attik. Ancak dönüste ebx, ecx,edx,... gibi bütün
registeleri kullanabiliriz. Örnegin:
mov eax,12345678h ; eax = 12345678h
push eax ; eax yigina atilarak saklaniyor
mov eax,0 ; eax registere 0 degerini verdik
pop ebx ; yigindaki son deger aliniyor ilk basta girdigimiz 12345678 degeri artik ebx registerde yüklü.
Yigin
konusu döngüler içinde döngü sayisini tutmak içinde kullanilir. Bu
anlamda yigin çok önemlidir. Ayrica Bayrak registerde (F - EF) yigin
içinde saklanilabilir. 386+ sonrasi komutlar arasinda tek komutla bütün
registerleri yigina atan ve yine tek komutla hepsini yigindan alan
PUSHA ve POPA komutu eklenmistir.
Kesmeler (Interrupts)
Kesmeler
özellikle gerçek modda çok önemli olan bir konudur. Ancak korumali
modda da en düsük seviyede kesmeler kullanilmaktadir. Kesmeler
programcilar açisindan kisaca sistemin bize sagladigi fonksiyonlardir.
Sistem açisindan ise, kullanilan isletim sistemi ile bilgisayar
arasindaki kontrollü çalismayi saglama ve gerektigi kadar paylasimi
olusturmadir. Buradaki paylasim sistemin ve isletim sisteminin
kaynaklarinin paylastirilmasi. Örnegin bir tusa basilip basilmadigini
ve hangi tusa basildigini ögrenmek için sistem bize sadece klavye
konusunda bir kesme vermistir. Bunu kullanarak program içerisinden
hangi tusa basildigini ögrenebiliriz. Yada hangi ekran modunda
oldugumuzu yine kesmeler vasitasiyla ögrenebiliriz. Kesmelerin olmadigi
bir program düsünürsek o zaman zor ve çok yüksek bir bilgi birikimi
gerektiren "donanim programlamasi" yapilmasi gerekir.
Kesmelerin gerçek modda "kesme tablosu" olarak adlandirilan 0:0
adresinden baslayan bir tablosu vardir. Bu tabloda kesmeler 4 byte
aralikla birbirlerini takip ederler. Bunun nedeni gerçek modda 64kb '
lik segment - offset sistemidir. 16 bitlik segment adresi önce, 16
bitlik offset adreside sonra takip eder. Bu kesme vektörleri
degistirilerek "geridönüm" (callback) yapmak sartiyla
yönlendirilebilirler (Hook). Bundan yararlanarak ortaya TSR (terminate
and stay resident -sonlan ve kal-) ortaya çikmistir. Sistemin istenilen
bir fonksiyonu alinarak filtre edilebilir ve o fonksiyon
kullanildiginda istenilen program parçacigi çalistirilabilir. Ayrica
sistemin debug için kesme 1 ve kesme 3 olarak ayirdigi iki kesmede
mevcuttur. EFLAG bayrak registerininin TR biti (trap biti = kapan veya
debug biti) set edilince CPU her komuttan sonra kesme 1 ' i çagirir.
Böylece debug etme islemi yapilmis olur.
Bayraklar (Flaglar)
Bayrak
komutlari ve olaylari deyince aklimiza hep sayilari olusturan bit'ler
gelmeli. Basit olarak iki sayinin karsilastirilmasinda ortaya o sayinin
ayni oldugu, ayni olmadigi, büyük veya küçük olmasi yada sifir olmasi
gibi durumlar çikar. Bu durumlari ögrenmenin tek yolu o duruma göre
sekil alan bayrak register (f) bitlerinin durumunu ögrenmemizdir. Bir
test veya bir matematiksel islem oldugu durumlarda sonuçlar daima
bayrak registerin ilgili bitlerine yansir.
Bayrak
register gerçek modda 16 bit, korumali modda ise 32 bitliktir. Yani
gerçek modda toplam 16 bit tane sebep-sonuç biti ayrilmis ayni sekilde
korumali modda 32 tane sebep-sonuç biti ayrilmistir.32 bitlik bayrak
registere EFlag adi verilir ve ayni 16 bitlik sekli gibi sadece ilk 16
biti kullanilir.(Bazi bitler dökümante edilmemistir). EFlag registerin
sonraki 16 biti ileride yapilacak CPU güncellemeleri için
ayrilmistir.En baslica olanlari ZF (zero flag), CF (carry flag) ,SF, AF
gibidir. Örnegin bir islem sonucu 0 ise ZF set olur yani 1 olur.
Bayraklar islevleri açisindan 3 gruba ayrililar. Status flaglar (durum bayragi), Control flaglar (kontrol bayragi) ve System flaglar (sistem bayragi).
Durum bayraklari programin içindeki komutsal, mantiksal ve matematiksel
islemlerin sonuçlarini verir. Genelde kullanilan bayraklar bunlardir.
Kontrol bayragi sadece 1 tanedir ve ileride görecegimiz repz komutunun
asagi yada yukari islem yapma seklini ayarlar. Sistem bayraklari ise
sistemle ilgili islemler için kullanilir. Genelde bunlari isletim
sistemi yada kullanacagimiz debugger gibi yüksek düzeyli bir program
degistirir.
Bayrak registerin tam yapisi asagidaki gibidir:
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F | 10 | 11-31 |
| CF | ? | PF | ? | AF | ZF | SF | TF | IF | DF | OV | ? | IO | ? | NTF | RF | V86 | - |
Komut Noktasi registeri (IP)
Bu
register çalismakta olan sistemin yada programin o anda hangi adreste
oldugunu komutun baslangiç noktasindan itibaren belirtir. Örnegin; bir
programi debug ederken her F8 (trace) tusuna basinca o komutu isleyip
bir alt komuta geçer. Iste o anda IP register o komutun islemesi ile
sonraki komutun basina gelmis olur. Böyle kod zinciri saglanmis olur.
Bunu CPU otomatik olarak kendisi düzenler. Yine moda göre uzunlugu ve
ismi degisir, gerçek mod daha önce anlattigim gibi segment:offset
sistemine sahip oldugu için IP register 16 bit olur. Yani sadece
offseti gösterir. Ancak korumali modda bütün 32bit olan registerin
basina aldigi 'E' (Extended) harfini alarak EIP olur. Bu registeri
direkt olarak MOV EAX,EIP seklinde elde edemiyoruz. Çünkü bu registerin
degismesi yada farkli bir deger almasi sistemin çalisma bütünlügünü yok
eder ve sistemde hatalarina neden olur. Bunu bir su yoluna
benzeterek örnekleyebilirim. Örnegin bir nehrin suyunun rasgele baska
bir yere yönlendirildigini düsünelim. O zaman istenmeyen su baskinlari
ve ekinlerin evlerin zarar görmesine hatta ölümlere neden olacaktir.
Yinede sistem debug API' leri ile veya asagidaki gibi basit bir
yöntemle o andaki IP noktasi bulunabilir.
call delta
delta:
pop eax
sub eax,5
Burada
ileride görecegimiz CALL komutunun bulundugu yeri yigin içine
atmasindan yararlaniyoruz. SUB EAX,5 ile o zamana kadar isletilen kodun
uzunlugunu çikariyoruz ve sonuçta elimize bastaki komutun çagrildigi IP
noktasi kaliyor.
IO Portlari
Donamin
bize temel islemler ve sistemin bütün kaynaklarini kullanmak için
kaynak paylasimini ve erisimini saglayan erisim kanallari vermistir.
Aslinda sistemdeki ek aygitlar (ekran karti, ses karti..) hepsi
sistemler bu portlar vasitasiyla haberlesmekte ve islemleri yerine
getirmektedir. Bunlara ilaveten daha önce bahsettigim kesmeler
tamamlayici bir roldedir. Donamin portlari normal programlarda
kullanilmasi hem karisik hemde zor oldugu için genelde çok yüksek
düzeyli uygulamalarda direkt olarak kullanilirlar. DOS altinda korumali
modda 8 ve 16 bitlik degerlerle istenildigi gibi kullanilabilir ancak
korumali modda en yüksek hassasiyet degerinde (ring 0) olma zorunlugu
vardir. Gerçek modda daha dogrusu DOS ortaminda bir *.com programi
disaridan hiç kesme, adres kullanmadan kendi basina bir sistem programi
olusturabilir. Ancak korumali modda bu pek mümkün degildir. Bu isletim
sisteminin istedigi özelliklerden kaynaklanir. Bir *.com dosyasi salt
makina kodudur ve uygun bir programlama ile sistemin her yerinde
çalisabilir (örnegin virüsler) Ancak bir windows programi IO
programlarinin kullanilma kisitliligi ve PE dosya yapisi nedeniyle bunu
pekde basaramaz. Burada IO portlarinin derin bir islevlik gücü ortaya
çikiyor.
IO portlari için 8,16 ve 32 bitlik özel opkodlar
tahsis edilmistir. Genelde asil port numarasindan önce status port
denilen ve ön degerlerin gönderilip - alinan veya kontrol edilen
port(lar) vardir. Hatta bazi donanim sistemlerinde bu portlari 6-7
taneye kadar çikararak kombine çalismayi gerektirir.
Örnek
vermek gerekirse sistenim CMOS hafizasini okumak için 70h portu
kullanilir. Burada CMOS'un 15 ve 16. bytelari okunarak hafiza uzunlugu
ögreniliyor.
mov ax,1516
out 70h,al
in al,71h
mov byte ptr [hafiza_low],al
mov al,ah
out 70h,al
in al,71h
mov byte ptr [hafiza_high],al
ret
Bu örnegi portlar kullanimi hakkinda daha iyi fikir edinilmesi için
verdim. Normalde sistem kesmeleri, API kütüphaneleri ile bu islem
rahatça elde edilebilir. Fakat burada söyle bir fark var. Sistem
kesmeleri (interruptlar) ve Windows API kütüphaneleri vektörleri
kullanici tarafindan ele geçirilerek (hook) istenildigi gibi kisitlama
yada degistirilme olanagina sahiptir. Ancak IO portlari için böyle bir
ele geçirme (hook) mümkün degildir. Bunlar tamamen donanima ait
portlardir.
0 yorum yazılmıştır