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ünel
im. 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 regi
ster 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 verdi
k
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 b
ir 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 regis
ter (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 registeri
n 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 z
inciri 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 gi
bi 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.