|
Assembly
Nedir?
Assembly
aslında öğrenilmesi zor bir dildir. Uzun zaman ve büyük emek ister.
Fakat iyi bir şekilde anlatıldığında hiçte zor değil. Ama baştan
şunu belirteyim: Diğer programlama dillerinde yaptıklarınızı
assembly altında daha çok satır kod yazarak ve daha uzun bir zamanda
elde edebilirsiniz. Buna karşın diğer dillere göre birçok üstünlüğü
bulunmaktadır. Lütfen bu sayfayı sonuna kadar okumaya devam edin.
Bir
programlama dili öğrenecek insan öncelikle kolay bir dili öğrenmeli
(Bu genelde Basic'dir.) Bu dil vasıtası ile programlama ve algoritma
geliştirme (problemlere karşın geliştirilen hatasız ve tatmin edici
çözümler diyebiliriz) hakkında bilgi ve deneyim sahibi olmalı. Son
olarak ise diğer dilleri tanımalı ve seçtiği dili öğrenmeli,
uygulamalar geliştirmelidir.
0C85:0100 B402
MOV AH,02
0C85:0102 B203
MOV DL,03
0C85:0104 CD21
INT 21
0C85:0106 CD20
INT 20
Yukarıda
ayrıntılı çıktısı alınmış bir kod parçası bulunuyor. (Daha sonra ne
işe yaradığını anlatacağım) Gri renkteki bölümler bellek
lokasyonlarıdır. Yani ilgili kodların şu anda hangi bellek adresinde
bulunduğunu gösteriyor bize. Kırmızı renkteki yazılar assembly
kodlarıdır. Koyu yeşil kısımlar ise bu assembly kodlarının makina
diline çevrilmiş halidir. Bizi ilgilendiren kırmızı bölümler olacak.
Biliyorum o yazılardan hiçbir şey anlamadınız, bu çok normal...
Tam
olarak anlamanız için uzun bir örnek olacak ama başlıyoruz...
Şu anda
bilgisayarınızın başında oturuyor, windows işletim sisteminiz
altında bazı programları kullanıyorsunuz. Muhtemelen Internet
Explorer bunlardan biri. Kullandığınız her program (*.exe *.dll *.ocx
...) hangi programlama dili ile yazılırsa yazılsın hepside derlenmiş
ve ilgili dosyalar haline dönüştürülmüştürler. Bir program Delphi
ile yazılmış ve derlenmiş (exe uzantılı dosya haline getirilmiş)
ise tersi işlem yapılması, yani programın kodlarına geri
döndürülmesi imkansızdır. Derlenmiş program artık işletim
sistemimizin ve makinamızın işlemcisinin anlayabileceği bir
haldedir. Yani bizim makina dili dediğimiz haldedir. Tamamen iç
yapısı elektronik devre elemanlardan oluşan işlemcimiz bu makina
dili halindeki kodları çalıştırabilir. Başka hiçbir şeyden anlamaz.
Bilgisayarlara bu nedenle de aptal makina denmektedir. Çünkü biz ne
emredersek onu işlerler. Makina dili 16 lık sayı sistemindeki
değerlerden oluşur. Bu kodlar sadece sayı kümelerinden oluştukları
için bir insanın bunları anlaması hele hele bu sayılar üzerinden
programlarını yazması nerdeyse imkansızdır...
Fakat
insanların karmaşık sayılar yerine kendi konuşma dillerindeki
kelimelerden oluşan bir komut kütüphanesi ile çalışmaları daha
kolaydır. Bunu Basic dilinde rahatlıkla görebilirsiniz. Çünkü Basic
dilindeki komutlar ingilizce bilen bir insan için öğrenme ve akılda
kalma açısından çok kolay bir dildir. Makina dilindeki sayılar yani
kodlar yani programımız bizim için önemlidir. Çünkü bunlar
işlemcimiz üretilirken geliştirilmiş bir komut setine hitap ederler.
Bir sonraki işlemci teknolojisine geçildiğinde komut seti
genişletilir ve yeni komutlar eklenir. Assembly dili bu komut
setindeki makina dili kodlarının insanların biraz daha kolay
anlayabileceği harfler üzerinden gösterimidir. Bu tamamen
semboliktir ve her assembly komutu yazıldığında birebir makina
diline çevrilirler.
Peki
Assembly bize ne sağladı? :
-Bize
işlemcimiz üzerindeki her komuta aracısız erişim sağladı. Yani kod
yazarken muhatabınız işlemci ve üzerinde çalışmakta olduğunuz
işletim sistemidir. (Bu genelde Windows'tur ama başka bir sistemde
olabilir.) Bu size oluşturacağınız programın kodlanması esnasında
çok yüksek bir kod hakimiyeti sağlar. Böylece diğer dillerde aynı
işi yapsanız dahi sizin assembly ile işlemci üzerindeki
hakimiyetiniz en üst düzeydedir ve bunu sizin kadar temiz
yapabilecek bir programlama dili ve/veya derleyici yoktur.
-Yazdığınız her satır kod 1 ila 4 byte civarında olup çok az yer
kaplar. Diğer dillerde yazılan kodlar aynı işi yapsa dahi sizinle
aranızda derleyici bulunmaktadır ve programın işleyişini sağlama
almak amacıyla komutlar arasında ek bilgiler girilmekte ve
programınızın boyutu büyümektedir. Ayrıca diğer dillerdeki komutlar
işlemci üzerindeki komutlarla uyuşmaz ise derleyici bunu dolambaçlı
yollardan işlemciye anlatır ve yazılan bir tek komut makina dilinde
yüzlerce komuta karşılık gelebilir. Herhangi bir dildeki basit komut
assembly ile yazılsa 2 ila 6 kat daha az yer kaplar.
-Yazdığınız programlar (eğer bir algoritma hatası yok ise) diğer
dillerdeki şekillerine göre her zaman daha az yer kaplar. Programın
az yer kaplaması çalışırken daha az bellek tüketmesi; işlemler daha
az kod ile anlatıldığından daha kısa sürede işlemin bitmesi, yani
programımız daha hızlı çalışması anlamına gelir.
Avantajları :
-
İşlemcinizin gücünü en iyi şekilde ortaya koyabilecek tek
programlama dilidir.
- Cok az yer kapladığı için bilgisayar virüslerin yazımında
kullanılırlar.
- Çok hızlı çalıştıkları için işletim sistemlerinde kernel ve
donanım sürücülerinin programlanmasında, hız gerektiren kritik
uygulamalarda kullanılmaktadır.
- Yapısı itibariyle üç boyutlu ekran kartlarında ilgili çizim ve
efektlerin işlenmesi amacıyla hem oyunlar hem de programlar içinde
ekran kartına hitap eden makina dili kodları kullanılmaktadır.
- Herhangi bir amaç doğrultusunda (genelde programların daha hızlı
çalışması için optimize edilirken), herhangi bir programlama dili
altında, o dilin kodları arasında kullanılabilir. Yani diğer
dillerle beraber de kullanabilirsiniz. Sizin uzman olduğunuz dil
belki Visual Basic dir. Ama işin içinden çıkamadınız, assembly bilen
bir arkadaşınız sizin için birkaç kod ekledi ve tamam.
- İyi öğrenildiğinde diğer dillerde karşılaşılan büyük problemlerin
assembly ile basit çözümleri olduğu görülür. Yani başka programlama
dillerde çalışan insanların bu iş imkansız dediği yerde assembly
devreye girer. Bu üstünlük daima var olacaktır...
- Yazdığınız programın kaynak kodlarını tüm dünya ile paylaşsanız
dahi kodlar o kadar karmaşıktır ki insanlar assembly bilse dahi ne
işe yaradığını çözmeleri çok uzun zamanlarını alır. (Açıklayıcı
bilgiler koymadı iseniz) Assembly bilmeyen bir insanın kaynak kodu
anlaması imkansızdır.
- Her program derlendiğinde makina diline dönüşür. Bunlar assembly
kodlarına dönüştürülebilirler. (Buna disassembly olayı denir, bunu
yapan programlar ise disassembler'lar dır.) Bu sayede assembly bilen
bir insan zor da olsa diger programların çalışma şekillerini
inceleyebilir ve üzerlerinde oynama yapabilir. Bu özellik de başka
hiçbir dilde yoktur. Daha ileri gidip programların şifre isteyen
bölümleri değişik yöntemlerle kırılabilir. Serial Generator türü
programlar bu şekilde yapılabilmektedir.
- Zaman geçtikce ve insanlar üreten yerine tüketen bilgisayar
kullanıcıları haline geldikçe bu dili bilmenin ayrıcalığı
artmaktadır.
Dezavantajları :
- Dilin
öğrenilmesi uzun zaman ve emek ister.
- Yazılan programlar diğer diller göre daha çok satırdan oluşur.
- Yazdığınız programın hangi satırlarının ne iş yaptığını bir süre
sonra anlamayabilirsiniz. Çünkü anlaşılması zor ve karmaşık bir
dildir. Bu nedenle kodların sonuna açıklama bölümleri eklemek iyi
bir çözüm olacaktır.
- İşlemciye birebir bağımlıdır. Yani sonraki sayfalarda anlatılacak
olan kodlar Intel ve uyumlu (AMD, Cyrix, Via) işlemcilerde
geçerlidir. Örneğin Motorola işlemcileri kullanan Macintosh
bilgisayarlarda hem komut setinin hem de işlemci mimarisindeki büyük
farklılıklardan dolayı geçersizdir. Fakat onlarda da bir makina dili
tabii ki bir assembly dili bulunmaktadır. Fakat komutlar daha
farklıdır. Bizim ilgi alanımız intel 386 (i386) üstü ve bunlarla
uyumlu işlemcilerdir.
|
Bilgisayarlar ve Mikroişlemciler
Biraz bilgisayar
tarihine değinelim, daha sonra mikroişlemcilere geçeceğiz.
Aslında abaküs'e
kadar dayanıyor. Öncelikle matematikteki daha sonra fen
bilimlerinde ve teknolojideki gelişmeler ve yeni teknikler bizi
şu anda bulunduğumuz noktaya getirdi. İlk hesaplayıcılardan
sonra Pascal ve Babbage'in mekanik hesaplayıcıları gelmektedir.
Elektronik devre elemanlarının geliştirilmesi ve tüplü
tranzistörlerin sayesinde ilk elektronik hesaplayıcılar, yani
modern bilgisayara giden yol açılmış oldu. 1936-1951 arasındaki
bu donemde 1945 de ENIAC yapıldı. ENIAC ile işlemler soket
bağlantılarının değiştirilmesi ile yaptırılıyor idi. 70bin
direnç, 10bin kondansatör, 18bin tüplü tranzistör ve çalışırken
harcanan 150-200 kilowatt enerjisi ile dev ama sadece dört işlem
yeteneği, çok yavaş çalışması, kablolarla programlamanın zorluğu
ve çok sık arıza yapmasıyla da hantal bir cihaz idi.
1948 yılında
germanyum, 1954 de silisyum tranzistörler bulundu. Artık vakum
tüpleri yerini yarı iletken tranzistörler aldı. Mikroelektronik
geliştikçe tranzistörler küçüldü ve sonunda bunları tek bir
devre parçasında birleştirme fikri gelişti. Artık entegre
devreler yani çipler karşımızda. Harcanan daha az enerji, daha
küçük devreler taşınabilir radyolar gibi ürünleri beraberinde
getirdi.
İntelin ilk
ticari amaçlı işlemcisi 1971 yılının 4004 çipi idi. Dört bitlik
bir işlemci olup sadece hesap makinalarında kullanıldı. Bizi
ilgilendiren 1978 yilinda üretilen 8086. Bu işlemci 8bitlik
basit ve yavaş bir işlemcidir. Fakat şu andaki Pentium 4
mimarisi bile geriye uyumlu olarak bu işlemciyi
desteklemektedir. Farkları hız, ek birçok komut ve yeni
teknolojilerdir. Biz de bu 8086 ile başlayan x86 komut setini
programlıyor olacağız. Aşagıda bir ilerleme yer almaktadır.
8086 -> 80186 -> 80286 -> 80386 -> 80486 -> Pentium -> Pentium
Pro -> Pentium !! -> Pentium !!! -> Pentium 4
İntel işlemciler
80386 den itibaren bazı yenilikler getirdiği için ( 32bit ve
multitasking işlemci ) artık bizim için temel teşkil etmektedir.
Pentium 4 3000C işlemcisi için biraz açıklama yapalım:
Mimari:Pentium 4 İşlemci çekirdek hızı: 3000Mhz (yani bir
saniyede 3 milyar komut işler) C: Veriyolunun 800Mhz olduğu
anlamında. Klasik özellikler: 32bit işlemci, 64 bit veriyolu,
4Gbyte bellek adresleme, Hypertreading teknolojisi ile çift
işlemci gibi çalışma ve gelişmiş mimari özellikleri ile tek
döngüde iki işlem yapabilme özellikleri bulunuyor.
İşlemcimizin 32
bit olması ne demek? Öncelikle bit nedir? 1 byte bilgi tek
karakterden oluşur. Yani notepad içine yazdığınız tek bir 'A'
harfi 1 byte yer kaplar. 1 byte bilgisayarda 8 bit yani 8 tane
'1' veya '0' dan oluşur.
binary
11111111 = decimal
255 eder.
binary 01000001 = decimal
65 eder. Bu değeri içinde saklayan
bir 8bitlik saklayıcı 'A' harfini gösterir. Yani matematiksel
değerlerinin yanında bu 8 bitlik değerler ASCII karakter setine
göre belirli bir karaktere de denk gelmektedir.
İlk sayfada
0C85:0100
B402 MOV AH,02 şeklinde bir
satır bulunuyor idi. Buradaki B402 16lık sayı sistemindeki 2
byte bilgidir. Ve bu makina dili komutu assembly olarak
Mov ah,02 ye denktir. Bu komut ise
ah registerine (işlemcideki 8bit saklayıcılardan biri) 02
hexadecimal değerini atar. İleride bit düzeyinde işlemler de
yapacağımız için 16 lık 10 luk ve 2 lik sayı sistemlerinin
birbirine dönüşümlerini bilmek zorundayız. Örnek bir binary-decimal
dönüşümü aşağıdadır.
binary
01001110 için Decimal karşılığı=0*2^7+1*2^6+0*2^5+0*2^4+1*2^3+1*2^2+1*2^1+0*2^0=64+8+4+2=78
eder.
Daha önce de
belirttiğimiz gibi işlemci sadece matematiksel değerlerden
anlar. İster kod isterse data olsun esas olan bitlerdir. 32 bit
işlemciler 1 döngüde 32 bit yani 4 byte bilgiyi işleyebilme
özelliğine sahiptir. Çarpma, bölme, bellekten işlemciye veri
alınması ve gönderilmesi gibi...
İşlemciler teknik
özellikleri yanında iç frekansları ile de karşılaştırılmaktadır.
Pentium 3 1000Mhz bir işlemci saniyede 1 milyar işlem yapar.
Fakat işlemcide her komut aynı sürede tamamlanmaz. Bu komutun
karmaşıklığı ile alakalıdır. İşlemci içinde bir tür sinyal
üreten saat bulunur. Bu işlemcinin hızını tayin eder. İşlenecek
olan komut saat darbesinin verilmesi ile işlem görmeye başlar ve
bir sonraki saat darbesine kadar tamamlanmak zorundadır.
İşlemcideki tüm komutlar için bu geçerlidir. |
Register'lar
İşlemcimizin bize
sundukları üzerinden program yazdığımız için işlemcimizi de
tanımalıyız. Komut setini yani assembly komutlarını bir sonraki
derste anlatmaya başlayacağız. Registerler işlemci çalışması
sırasında değişik amaçlar için kullanılan değişkenlerdir. Bellekteki
verilere ulaşmak belirli bir zaman gerektirir, fakat registerler
işlemci çekirdeğindedir ve fazladan zaman harcanmadan istenen işleme
göre içerikleri kullanılabilmektedir. Fakat sınırlı sayıda
bulunurlar. Registerler genel amaçlı kullanılabilecekleri gibi
bazıları sadece özel görevleri üstlenmektedir. Bunlar ileride
anlatılacaktır. Şimdi i386 uyumlu bir işlemcinin registerlarına
bakalım.
|
<--- 32 bit register --->
|
EAX |
|
EBX |
|
ECX |
|
EDX |
|
EBP |
|
ESP |
|
ESI |
|
EDI |
|
<--- 16 bit register --->
|
<--- 16 bit register --->
|
<--- 32 bit register --->
|
EIP |
|
EFLAGS |
|
Evet yukarıda işlemci
registerlerinin çoğu görülmekte. İlk iki tablo aslında iç içe
girmektedir, fakat bu şekilde daha iyi anlışılacağını düşündüm.
Aşağıda ise bu registerlerin sınıflandırılmış halini görebilirsiniz.
|
Data Registers |
Pointer Registers |
Index Registers |
Segment Registers |
|
EAX,EBX,ECX,EDX,AX,BX,CX,DX,AH,AL,BH,BL,CH,CL,DH,DL |
EBP,ESP,BP,SP |
ESI,EDI,SI,DI |
CS,DS,SS,ES,FS,GS |
Daha önce söylediğim
gibi yukarıdaki 32 bitlik işlemcimiz 8086 tabanlı olduğu için geriye
uyumlu olarak 8086 nın programlarını da çalıştırabilmelidir. Fakat
8086 işlemcisi 8 bitlik bir işlemcidir. Bu nedenle gerçekte EAX
şeklindeki bir register 32 bittir, fakat bu registerin ilk 16 bitlik
bölümü AX şeklinde ifade edilir. Bu AX şeklindeki 16 bitlik bölüm
ise kendi içinde ilk 8 bitlik bölüm AL, sonraki 8 bitlik bölüm AH
olmak üzere yine alt bölümleri bulunur. Yani ben 4 byte bilgi
depolamak istersem EAX,EBX gibi 32 bitlik; 2 byte bilgi depolamak
istersem AX,BX gibi 16 bit; eger 1 byte bilgi depolamak istersem
AH,AL,BH,BL registerlerini kullanabilirim. Sadece şunu unutmayın, AX,AH,AL
registerleri sadece EAX registerinin alt bölümleridir. Başka yerde
depolanmazlar. Gerekli olduğunda alt registerlere ulaşılabilir fakat
gerçekte esas olan EAX dir. Pointer ve index registerlerinde de alt
bölümler bulunur. Sınıflandırma tablosunu inceleyiniz. Kafa
karıştırma olasılığına karşılık aşağıda registerleri boyutlarına
göre de sınıflandırdım.
|
32bit registers (4 byte) |
16bit registers (2 byte) |
8bit registers (1 byte) |
EAX,EBX,ECX,EDX,EBP,ESP,ESI,EDI,
EIP,EFLAGS |
AX,BX,CX,DX,BP,SP,SI,DI,
CS,DS,SS,ES,FS,GS,IP,FLAGS |
AH,AL,BH,BL,CH,CL,DH,DL |
Şimdi yukarıdaki
registerler ne işe yarar onları öğrenelim ? (Örnekleri bir
sonraki Ders içeriğinde yer alacak.)
AX : Akümülatör Registeri, Dörtişlem
operasyonlarında kullanılmaktadır.
BX : Base Registeri, Bellek
lokasyonlarında baz adres göstericisi olarak kullanılır, yani bir
tür index registeri gibi kullanılabilir.
CX : Counter Registeri, Döngü
işlemlerinde sayaç olarak kullanılır, yani döngü kaç defa daha
dönecek bunun sayısını tutar.
DX : Data Registeri, Donanım ile
yapılan giriş çıkış işlemlerinde kullanılır.
CS : Code Segment Registeri, Segmentler
bellekte bir alt bölümü işaret ederler. Önemli olan içeriklerinin ne
olduğudur. Bizim en önemli segmentimiz kod segmentdir. İçeriğinde
ise tahmin edebileceğiniz gibi yazdığımız programın komutları yer
alır. Hem programcı hemde kullanılan işletim sistemi kod segmentteki
komutlar üzerine başka dataların yazılmayacağının garantisini
almalıdır. Aksi taktirde programımız garip hatalar verir ve kitlenir.
Programların sağlıklı çalışmaları için Code Segment içeriği en
önemli noktadır. (NT çekirdeği taşıyan Windows 2000 ve XP işletim
sistemleri gelişmiş bellek yönetimleri sayesinde Windows 9x
sistemlere göre daha kararlıdırlar. Windows 9x sistemlerde
programlardaki hatalar sistemi çökertebilecek seviyeye çıkabilir.
Mavi ekran hataları bu sebepledir. )
DS : Data Segment Registeri,
programımızı yazarken kullandığımız değişkenler,karakter dizileri
ayrıca çalışma anında oluşturulan değişken tiplerinin tamamı bu
segment altında tutulur.
SS : Stack Segment Registeri, Stack çok
özel bir data segment tipidir. Alt programlardan, dll dosyaları
içinde bulunan fonksiyonlardan, windows api fonksiyonlarından kodlar
yürütülmeden önce gerekli değişkenler bu segmente sadece bu amaçla
kullanılan bir assembly komutu ile alınırlar. Daha sonra çağırma
işlemi yapılır. Önemli olan çalışma prensibini anlamaktır. Burada
diğer data segmentlerden olduğu gibi istenilen adresinden veri
çekilebilmektedir. Farkı ise stack'a yollanan son verinin çağırılma
esnasında ilk olarak geri dönmesidir. Yani son giren-ilk çıkar
mantığı. (ileride daha ayrıntılı anlatılacak) Veriler stackin
sonundan başına doğru alınırlar. Stacka son yollanan veriyi
SP isimli Stack Pointeri işaret eder.
ES : Extra Data Segment Registeri,Data
segment ile aynı özelliklere sahiptir. Özel olarak bu segmenti
kullanan birkaç komut bulunmaktadır.
FS,GS : Bu segment registerleri ihtiyaç
olduğu zaman kullanılmaktadır. Aslında ek olarak kullanılan Data
Segment Registerleridir.
BP : Base Pointer, Stack segmentin
başlangıç noktasını gösterir. Yani genelde içeriği sıfırdır.
SP : Stack Pointer, Stack segment içine
gönderilmiş olan son değerin (byte) adresini göstermektedir. Stack
içine veriler yollandıkça değeri azalır çünkü veri segmetin sonundan
başına doğru alınırlar. Veriler stackdan çekildikçe değeri artar,
böylece eski verileri gösterir, eski veriler silinmez ama
SP değeri değiştiği için işlem hata
vermeden yürümektedir.
***BP ve SP
aslında SI ve
DI gibi segmentler içindeki verinin
adresini gösterirler. Stack Segmentin özel bir çalışma şekli olduğu
için bu Pointer registerleri özel olarak sadece Stack Segmentin
sağlıklı çalışması için görev yapmaktadır.
SI : Source Index, Data segment veya
istenirse başına küçük bir tanımlama eklenerek diğer data
segmentlerdeki verileri de göstermek için kullanılan bir index
(işaretçi) registerdir.
DI : Destination Index,
SI ile tamamen aynı özelliklere
sahiptir. Fakat SI ve
DI bazı string komutları tarafından
kaynak ve hedef işaretçisi olarak da kullanılmaktadır.
IP : Bir işaretçi registerdir. Çok özel
bir işaretçidir. Code Segment içinde
işlenecek bir sonraki komutun yerini işaret eder. Yaptığınız işten
emin olmadan üzerinde oynama yapmayın!
FLAGS : 32 bitlik yine çok özel bir
işlevi olan registerdir. Bu registerin içeriğindeki bitler çok
önemlidir. Bazı bitler anlamsızdır, diğerleri ise daha önce işlenen
komutların sonuçları ile ilgili bilgiler verir. Örneğin
CMP komutu ile iki sayı karşılaştırılır
ise sayıların eşit olma veya birinin diğerine göre büyük olması bu
register içindeki bazı bitleri 1 (set) veya 0 (reset) durumuna
getirecektir. Daha sonra kullanılacak bir dallanma komutu ile bu
flaglar kontrol edilerek sonuca göre belirli adreslere dallanmalar
yapılır. İçeriğini biz direkt olarak kullanmayacağız. Bazı komutlar
işleyişleri sırasında bu registeri gizli olarak kullanmaktadırlar.
Aşağıda hangi segmentlerin hangi işaretçi registerler ile
kullanılacakları verilmektedir:
CS:IP DS:SI DS:DI SS:BP SS:SP
Debug
ve Kullanımı
İster
DOS olsun isterse
Windows, çalıştırılabilir iki tipte dosya bulunur.
Com veya exe
tipindedir. Com tipi dosyalar birebir programın bellekteki şeklinin
aynısıdır. Exe tipi dosyalarda ise programın başında işletim
sisteminin anlayacağı tipte bir başlık bulunur. İşletim sistemi bu
kısımdaki direktiflere göre code, data ve diğer segmentleri bellekte
boş olan yerlere göre hafızaya yükler ve ilgili segmentlerin
adreslerini registerlere aktararak programin ilk komutu ile işlemi
programa bırakır. Com tipi programlarda böyle bir düzenleme
yapılmaz, sadece diskten okunur, boş bir bellek adresine
yerleştirilerek yürütme programa bırakılır. Tabii dos altında olsa
idik tam olarak böyle idi. Windows altında dos için yazılmış olan
tüm programlar bir dos emülasyon penceresi altında sanal bir ortamda
çalışırlar. (Com dosyaları 64k dan daha uzun olamazlar, exe tipi
dosyalarda bu tür bir sınırlama olmayıp gelişmiş özellikler
içerirler. Bu nedenlerle exe tip dosyalar daha çok kullanılırlar.)
Exe tipi programlar
derlenerek hazırlabilirler, fakat com tipi dosyaların derlenmeye
ihtiyacı yoktur ve Debug.exe isimli komut satırı programı ile bunlar
programlanabilir. Debug.exe tüm dos ve windows işletim sistemleri
ile birlikte geldiği için ek bir program kurmanıza gerek yoktur.
Tabii bu programı sadece komutların kullanımlarını görmek,
programımızı satır satır çalıştırarak registerleri nasıl
etkilediğini görmek amacıyla kullanacağız. Fakat kullandıkça
vazgeçmesi zor bir programdır, yani bağımlılık yapar. (Şunu
unutmamalıyız: Debug ile 16 bit registerlere ulaşabilir ve bu
şekilde program yazabiliriz, 32bit ile çalışmalar bir sonraki
bölümden itibaren başlayacaktır.)
Başlat -> Çalıştır yolunu izleyerek
command yazıyoruz. Sonra
debug yazıp enterleyin. Karşımıza
sadece '-' işareti geldi. Artık debug bizden komut girmemizi
bekliyor. Aşağıda ilk programımızı yazdık, şimdi biraz anlatalım.

Komut satırını açarak
debug'a geçiş yaptık. 'a100' şeklinde
bir ifade var. 'a' komutu bizim
assembly kodlarının girişine başlayacağımızı bildirir. sonundaki
'100' ise komutları yazmaya başlayacağımız adrestir. Debug ile
sadece com tipi dosyalar yazılabiliyor ve bu tür programlar 100h
adresinden itibaren yazılırlar. (Debug altında gireceğimiz tüm
matematiksel ifadeler hexadecimal sistemdedir!!! Yani
100h aslında decimal 256'ya denk
gelir.)
mov ah,2 : MOV
komutu bizim en çok kullanacağımız komutlardan biri. Sağdaki
register içeriğini veya herhangi bir hexadecimal sabit değerin
soldaki registere kaydedilmesidir. Tabii bu durumda ah'ın eski
değeri kaybolur, zaten bizim için şu anda bunun önemi yok. (com tipi
dosyalarda program ilk çalışmaya başladığında çoğu registerin değeri
sıfırdır.)
mov dl,3 : Yukarıdaki
MOV komutu gibi burada da bir atama söz
konusu. DL isimli 8bit registere
3h değeri kaydediliyor.
int 21, int 20 : Bu komutlar dos
kullanıldığı zamanlarda geçerli idi. Benzer işlemde şu anda windows
altında API fonksiyonları görev yapmaktadır. Bunları bir tür dos
işletim sisteminin bize sunduğu hazır fonksiyonlar olarak
görebiliriz. Elbette sadece 20 ve 21 bulunmamakta. Fakat bunlara
değinmeyeceğiz. (int=interrupt) Interrupt 20 parametresiz çağırılır
ve bizim com tipi programımızı sonlandırır. Interrupt 21 dos ile
gelen dev bir fonksiyonlar kütüphanesidir. Hangi numaralı
fonksiyonun çağırılacağını ah registerine koymak zorundayız.
Fonksiyonun tipine göre istenen başka değerler bulunuyor ise bunlar
da ilgili registerlara atanmalıdır. Daha sonra interrup çağrısı
yapılır. Biz iki numaralı fonksiyonu (yani ekrana karakter yollama
fonksiyonu) çağırıyoruz, bunun için ah a 2 atadık. Daha sonra ASCII
karakter tablosuna göre hangi değer ekrana getirilecek ise onun
hexadecimal değerini DL registerine atıyoruz. (Burada 3h kalp
şeklinde bir karakter, eğer 41h değeri atanırsa 'A' harfi ekrana
basılır.) (Bunlardan sonraki satırı ENTER ile boş geçin.)
g : ile programa çalışma emri
veriliyor. Gördüğünüz gibi bir karakter ekrana basılıp program int
20 ye geldiğinde normal olarak sonlanıyor.
n : ile programımıza isim veriyoruz.
r cx ile cx
registerinin değerini değiştirerek 8 giriyoruz çünkü programımızın
son satırı 0108 adresini gösteriyor.
Ilk satır 0100 ise arada 8 byte yokmu?
Yani programımız 8byte uzunluğunda ve biz bunu cx'e aktararak diske
kaydedilecek programın boyutunu belirtiyoruz ve w ile işlem
tamamlanıyor. Artık q komutunu verip Debug'dan çıkın ve bulunduğunuz
dizinde 8 byte uzunluğunda bir kalp.com isimli program var mı bir
bakın. Veya hemen kalp yazıp ekran çıktısının debug içinde g komutu
ile aldığımızın aynısı olduğunu görebilirsiniz. Bu bizim ilk
programımız oldu.

Yukarıdaki çalışma
yine aynı program üzerinde yapıldı. Burada t (trace,
izle) komutu ile programımızı satır satır çalıştırdık ve
registerler üzerindeki etkilerine baktık. Interruptlar birer dev kod
bloğu olduğuna göre ve bunları da trace eder isek sayfalar sonra
içinde çıkabileceğimiz için bunları pas geçmek için p kullanıldı.
Ilk komut işlendikten sonra ekrana ilk gelen bölümü inceleyelim. Ilk
komut mov ah,2 idi. Gördüğünüz gibi
AX=0200 bölümünde
AX in üst 8 bitine yani AH'a 2 değeri atanmış. Burada dikkat
ederseniz DS,ES,SS,CS değerleri yani
segment registerları aynı blok adresini gösteriyorlar. Yani com tipi
dosyalarda kodlar ile datalar aynı yerde bulunuyor. Bu da bize
program yazarken dikkatli olmamız gerektiği anlamında önemli bir
not. IP pointeri daima sırada işlenecek olan komıtu gösterir. Ilk
komut işlendiğine göre 0102 değerini gösteriyor. IP nin hemen sağ
tarafındaki ifadeler flagların durumlarını göstermektedir, şu anda
önemli değil. Üçüncü satırda sırayla şunlar yer alıyor:
CS:IP <MAKINA KODU> <ASSEMBLY KARŞILIĞI>
Ikinci trace'de
DL ye 3 değerinin aktarılmış olduğu
rahatlıkla görülebilir. Bundan sonraki p komutu ile
int 21 işleme girer ve ekranda görülen
kalp işareti geliyor. int 20 de
programdan çıkılıyor. Bu basit bir örnek oldu, daha karmaşık bir
örnek inceleyelim.

mov ah,2 : Interrupt 21 içindeki 2
numaralı fonksiyon yani ekrana karakter basmamız için atıyoruz.
mov cx,FF : Şu ana kadar görmediğimiz
döngü işlemi için kullanılacak. FF yani
decimal 255 değeri CX e
atanıyor. Aşağıdaki loop <adres>
komutuna gelindiğinde cx in değeri bir azaltılıyor ve sıfıra eşit
değil ise <adres> değerine atlıyor program. CX içindeki sayı kadar
bu loop döngüsü devam ediyor, yani 255 defa. CX sıfıra eşit
olduğunda ise hiçbir işlem yapılmıyor ve bir alttaki komut ile
programa devam ediliyor. Diyebiliriz ki LOOP komutu gizli olarak cx
registerini kullanmaktadır.
inc dl : inc komutu karşısındaki
registerin değerini bir artırır. Biliyorsunuz ki ekrana gönderilecek
karakter dl içinde. Biz 255 döngü içinde 1 ila 255 arasındaki tüm
ASCII karakterleri ekrana basmak istiyoruz. Bunun için toplama
işlemi de yapabilirdik (add dl,1) fakat bu işlemin okunurluğu daha
iyidir.
int 21 : Ekrana karakter yazma
fonksiyonu çağırılıyor.
loop 105 : CX
sıfıra eşit değil ise 105 adresine zıpla.
int 20 : Programdan çık.
Aşağıda
g (go) komutu ile çıktı alınıyor. Tabii
buradaki karakter seti dos altındaki; windows altındaki ile
uyuşmayabilir. Daha sonra ise ascii.com şeklinde kaydediliyor.
Aşağıda bazı
assembly komutları ve açıklamaları bulunuyor:
MOV : Değer aktarmak için kullanılır.
mov ah,2 mov ax,FFFF mov bx,cx mov dl,bh mov
[200],ah mov dl,[382] gibi. (Köşeli parantez 'adresindeki'
anlamındadır. Yani ah içeriğini 200 adresine transfer et gibi.)
XCHG : İki registerin içeriklerini
karşılıklı olarak değiştirmelerini sağlar.
xchg ah,bh xchg ax,dx gibi.
ADD : İki değerin toplanması amacıyla
kullanılır. Sonuç değer soldaki registere kaydedilir.
add ax,bx add ah,AF add dl,1 add al,ah
gibi.
SUB : Çıkarma işlemi yapar. Sonuç ilk
registere kaydedilir. sub ah,1 sub ax,bx sub
cx,10 gibi.
MUL : Çarpma işlemi yapar. Tabii ki tüm
işlemlerde olduğu gibi değerler ve sonuçlar hexadecimaldir. 3 farklı
durumda incelenebilir.
8bit*8bit yani
byte*byte : Çarpılacak değerlerden biri
al ye kaydedilir
mov al,5F gibi. Daha sonra çarpma
işlemi yapılır, mul bl gibi. Sonuç
ise ax registerindedir. (ax=al*bl
işlemi)
16bit*16bit yani
word*word : Çarpılacak değerlerden
biri ax ye kaydedilir
mov ax,05FF gibi. Daha sonra çarpma
işlemi yapılır, mul bx gibi. Sonuç
ise dx,ax ikilisine aktarılmıştır.
Yani üst 16bitlik kısım dx de, alt
16bitlik kısım ise ax dedir. (dx,ax=ax*bx)
32bit*32bit yani
dword*dword Çarpılacak değerlerden
biri eax ye kaydedilir
mov eax,A055FF10 gibi. Daha sonra
çarpma işlemi yapılır, mul ebx gibi.
Sonuç ise edx,eax ikilisine
aktarılmıştır. Yani üst 32bitlik kısım edx
de, alt 32bitlik kısım ise eax dedir.
(edx,eax=eax,ebx)
DIV : Bölme işlemi için kullanılır.
Ayrıntılarına girmiyorum.
INC : Register içeriğini bir artırır.
inc ah gibi
DEC : Register içeriğini bir azaltır.
dec cx gibi.
NEG : Sayının ikili tamamlayıcısını
bulur. Örneğin al de 11110000 binary
değeri bulunuyor ise neg al işleminden
sonra bu değer 00001111 olur.
PUSH : Stack a yani stack segmente
değer göndermek için kullanılır. push ax
gibi.
POP : Stack dan değer çekmek için
kullanılır. pop cx gibi.
Windows Altında Win32Assembly Programlama
Artık
masm ile nasıl program yazılır bir gözatalım. Aşağıdaki küçük bir
windows uygulaması yer almakta:
.386
.model flat, stdcall
option casemap :none
; case
sensitive
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
.data
baslik
db "İlk
Assembly Programımız",0
yazi db
"Merhaba Dünya",0
.code
start:
push
MB_OK
push
offset baslik
push
offset yazi
push NULL
call
MessageBox
push NULL
call
ExitProcess
end
start
|
;
--------------- Data section
.data
baslik
db "İlk Assembly Programımız",0
yazi db
"Merhaba Dünya",0
; --------------- Code section
.code
; --------------- Program startup
start:
invoke
MessageBox,NULL,offset
yazi,offset
baslik, MB_OK
invoke
ExitProcess,0
end
start
|
Yukarıdaki ilk örnek
qeditor.exe ile derlenebilecek
haldedir. Biz eğer notepad kullanmış
olsaydık bu şekilde kodlayacak idik. Ben derledim ve boyutu 2.5kbyte
olan bir exe dosyası oluştu. Yukarıdaki hem soldaki hemde sağdaki
assembly kodu aynı işi yapıyor. Yaptığı iş baslik ismi ile
belirtilen başlığa sahip, yazi ismi ile yine data segmentte
belirtilen yazıya sahip bir mesaj kutusu gösteriyor. OK'e
tıklandıktan sonra program sonlanıyor. İsterseniz hemen açıklamaya
başlayayım. İlk üç satıra uzun süre hiç dokunmayacaksınız, burayı
geçiyorum. Include satırları ile içinde windows api fonksiyonlarının
ve bazı sabit değerlerin isimleri bulunan inc uzantılı dosyaları
tanımlıyoruz. Includelib fonksiyonu ile kullandığımız api
fonksiyonlarını içeren kütüphaneleri tanımlıyoruz. Windows.inc
dosyasında NULL ve
MB_OK tanımlamalarının matematiksel
eşitlikleri bulunuyor. Birçok özel durumun hangi sayılar ile
belirtildiğini ezberlemektense bunların karşılıklarını bilmek daha
kolaydır, hemde programın okunurluğunu artırır. .data ile data
segment tanımlanıyor ve bu data segment içinde baslik ve yazi
adreslerinde ilgili string ifadeler yer alıyor. Dikkat ederseniz
stringlerin bittiğini belirtmek için ayrıca 0h degeri ekleniyor.
Daha sonra .code ifadesi ile cede segment tanımlanıyor. start: ve
end start ifadeleri arasına kodlarımızı yazıyoruz. Dört adet push
komutu alt alta yazılmış. PUSH komutu
ile stack segmente bazı değerler gönderiyoruz. Bunlar mesaj kutusu
ile ilgili özellikler. İlk özellik mesaj kutusunda sadece OK tuşu
bulunacağı anlamında. İkinci özellik olan mesajın başlık bölümünde
yer alacak yazının adresini stacka yolluyoruz. (adresi anlamına
gelen offset terimi kullanılmıştır) Üçüncü yollanan özellik mesaj
olarak gösterilecek string ifadenin adresi. Son yollanan ifade
NULL yani sıfırdır, buraya bir değer
girmemize şu anda gerek yok. Gerekli parametreler stacka
yollandıktan sonra fonksiyonumuzu çağırıyoruz.
CALL komutu bir fonksiyonun yada bir altprogramın
çağırılmasında kullanılır. Çağırılan yerdeki işler bittikten sonra
programımız bir sonraki ifade ile devam edecektir. Fonksiyonumuz
MessageBox apisidir . Bu api
user32.inc içinde tanımlanmış ve ilgi
kutuphane user32.lib eklenmiştir. Bu
apiyi kullandığımızı belirtmek için yukarıdaki ifadeleri
include ile tanımlamaktayız. Daha
sonraki bölümde programdan çıkış kodları bulunuyor. Öncelikle çıkış
kodu stacka yollanır. Normal sonlanmalarda bu NULL yani sıfırdır.
Daha sonra kernel32.inc ve
kernel32.lib ile tanımladığımız
fonksiyonlardan ExitProcess
çağırılıyor.
İşletim sistemimizde
user32.dll içinde MesageBox apisi,
kernel32.dll içinde ise
ExitProcess apisi bulunmaktadır. Dos
altında yaptığımız interrup çağrıları gibi windows altında api
fonksiyonları bulunur. Bunlar disk erişiminden, ekrana resim
çizdirmeye, buttonlar ve kutucuklar hazırlamaya kadar binlerce
fonksiyon içeririler. Tabii sadece bu iki dll dosyasıyla sınırlı
değil apiler. Ayrıca yukarıdaki komutları yazarken dikkatli olmanız
gerekiyor. Derleyicimiz büyük küçük harf ayrımı yapabiliyor.
Özellikle api fonksiyonların isimlerini aynen yazın.
Soldaki kod ise işlev
olarak aynıdır. Farkı ise MAsmEd altında yukarıdaki gibi yazılması
yeterlidir. Include tanımlamalarını programda sol taraftaki ağaç
şeklindeki kısımdan ekleyerek yapıyoruz. Data segmentin içeriği
aynı. Code segmentde ise küçük bir değişiklik var. Alt alta
yazdiğimiz fonksiyon çağırma işinde call
yerine invoke ile tek satırda tüm
parametreleri yazdık. Böylece daha kolay okunur oldu. Bunu qeditor.exe
içinde de yapabilirdik, yinede eski şekilde kullanılan örnekler daha
çok olduğu için ikisini de göstermek istedim. Yoksa yazım farkından
başka ikiside aynen ilk örnekteki gibi çalışmaktadır. Birde
editörümüz programımız için ikon ve ek bazı versiyon bilgileri
eklemektedir. Bu nedenle editör içinden derlenen aynı program
5.5kbyte boyuta sahip olacaktır.
Kaynak:http://www.enginkuzu.org


|