You are on page 1of 131

X86 Assembly Programlamaya Giriş

X86 NEDİR ?

X86 Intel’in ilk mikroişlemcilerinden olan 8086 ile ilgili programlama kurallarını ifade
eden bir tanımlamadır. Intel’in önemli özelliklerinden biri olan "geriye dönük yazılım
uyumluluğu" böyle bir tanımlamanın oluşmasını sağlamış.

Şöyle ki; 8086 mikroişlemcisi olan bir bilgisayar sisteminde hazırladığınız herhangi
bir assembly programı X86 uyumlu tüm bilgisayarlarda çalışır. 80286, 386, 486 SX
veya DX, Pentium, Pentium III, AMD nin 286, 386 işlemcilerinde veya Nexgen,
Cyrix’in aynı tür işlemcilerinde ve diğerlerinde bu program çalışacaktır. Bu demek
oluyor ki yukarıda saydığımız tüm mikroişlemciler ve uyumlu olan diğerlerinin ortak
bir yönü var. Bu ortak yönler ortak program kodların olması ve mikroişlemcilerinin
temel mimarileri birbirinin aynı olmasıdır.

Yeni bir mikroişlemci üretildiğinde, eski mikroişlemcilere uyumlu olması büyük bir
avantajdır. Öyle olmasaydı bugün dünyada %90 oranında PC, X86 uyumlu işlemci
kullanıyor olmazdı. Bu yüzden mikroişlemci pazarının bu büyük dilimini birkaç firma
paylaşıyor, aslan payını Intel, daha sonra AMD geliyor, Cyrix ve Nexgen gibi diğer
mikroişlemci üreten firmaların isimlerini pek duymuyoruz.

X86 tabanlı sistemlerin mimarisi birbirine benzer. Birde X86 tabanlı olmayan
sistemler vardır. Apple firmasının iMAC bilgisayarları, yada SUN Microsystems
firmasının sistemleri X86 tabanlı mikroişlemciler kullanmadıkları için bu tür
bilgisayarda çalışan programlar X86 tabanlı sistemlerde çalışmazlar.

Şunu unutmayalım; dünyada yaklaşık %90 oranında X86 uyumlu PC sistemi


kullanılıyor. Bu yüzden bu X86 assembly dili diğer assembly dillerinden daha geçerli
bir programlama dilidir.
ASSEMBLY PROGRAMA DİLİ VE DİĞERLERİ ?

Assembly programlama dilini 3 kelime ile tanımlayacak olursak bunlar; GÜÇLÜ,


HIZLI ve KISA olurdu. Bu demek oluyor ki aynı programı assembly dili ile ve C++
programlama dili ile yaparsak, iki programın boyutlarına baktığımızda assembly ile
yazılan daha kısa olduğunu görürüz, aynı zamanda mikroişlemciyi ve hafızayı daha
verimli kullandığı için assembly dilinde yazılan program diğerlerine nazaran daha
güçlü olacaktır, son olarak assembly dilinde yazılan program diğerine oranla çok
daha hızlı çalışır.

Son zamanlarda mikroişlemcilerin hızları GHz’ler (giga hertz - ciga herz diye
okunur) mertebesine çıktığından assembly dilinin en büyük özelliği olan hızlılığı artık
pek popüler değildir. Kullanıcıya, 2GHz hızındaki bir CPU da hemen hemen her
uygulama aynı hızda çalışıyor gibi görünür. Buna rağmen C gibi yüksek düzey
programlama dilleri ile hazırlanan büyük projelerde işlemciyi çok fazla meşgul
edecek olan kod bölümleri assembly rutinleri çağrılarak yapılmaktadır.
NEDEN ASSEMBLY?

X86 Assembly dilini öğrenmek kolaydır ama bu dilde proje hazırlamak insanı çileden
çıkartabilir. Bu yüzden günümüzde X86 assembly dili yerine, daha çok endüstriyel
alanda kullanılan microcontroller chip’lerin assembly dili kullanılmaktadır. Bir
programcı assembly dilinde büyük uygulamalar hazırlamaz (genellikle). Bunun
nedeni üst düzey programlama dilleri olan delphi, C, C++, Pascal’a göre daha
dikkat gerektirir ve kod yazımı daha zordur. Programcı assembly dilinde proje
hazırlayacaksa kullandığı sistemin mikroişlemcisini ve hafıza haritasının yanında,
sistemin donanımında iyi bilmesi gerekir. Her şeyden önemlisi zamandır ki sadece
assembly dili ile PC uygulamaları geliştirmek iğne ile kuyu açmaya benzer.
Assembly alt düzey bir programlama dilidir yalnız alt düzey kelimesini yanlış
anlamayın, bu kelime bu dilin işlemciye ve hafızaya olan yakınlığını belirtir, üst
düzey diller ise daha çok programcıya yani insana yakındır.

Bu yüzden assembly dilinin özel kullanım alanları vardır. Kısaca bunları söyle
sıralayabiliriz.

1- Bilgisayar sistemini yakından tanımak için,


2- Device driver (cihaz sürücüleri) yazmak için,
3- Chip’lere program yüklemek için (PIC, microcontroller gibi...)
4- İşletim sistemlerinin yapımında (OS),
5- Şifre kırma ve Hacking işlemleri için,
6- Virüs programları yazmak için,
7- Elektronik tablo’lama (Excel gibi) programlarında.

İLK X86 ASSEMBLY KODLARIMIZ

PC platformunda doğrudan hafızaya sembolik kod kullanmadan yazacağımız bu programcık için


herhangi bir editör ve derleyici download etmenize gerek yok. DOS veya Windows işletim sistemine
sahip bir PC kullanmanız kafidir. Sırasıyla şunları yapın;

1- Başlat menüsünden çalıştır’ı tıklayıp cmd programını çalıştırın. Şayet Windows 98 kullanıyorsanız
başlat menüsünden MSDOS komut istemini çalıştırmanız gerekiyor. Hala DOS işletim sistemini
kullanıyorsanız bilgisayarınızın power butonuna basmanız yeterlidir :) yani halihazırda komut isteminde
olan bir trex siniz.

2- Komut istemindeyken cd\ yazın ve root dizinine geçin burada kendinize md asm yazarak asm
adında bir klasör oluşturun ve cd asm komutu ile klasörün içine girin. Tabi ki klasör oluşturmak için bu
kadar eziyet çekmenize gerek yok, amacımız biraz nostalji. Maksat projemiz bir klasörde oluşsun.
3- Debug yazıp enter tuşuna basın. Resimdeki görüldüğü gibi debug programı çalıştırıldığında ekranın
solunda bir - simgesi görülür ve bu programla harikalar yapabilirsiniz :)

4- E 0100 yazıp enter tuşuna basınız.


5- b4 yazıp boşluk tuşuna (space bar) basın, 09 yazıp boşluk tuşuna basın, ba 0b 01 cd 21 b4 4c cd 21
4d 45 52 48 41 42 41 20 41 53 53 45 4d 42 4c 59 24 değerleri içinde aynı işlemi tekrar edin :) Şayet
arada bir yerlerde hata yaparsanız klavyeden enter tuşuna basıp 4. adımdan itibaren yeniden
başlayın. İşlem bitince ekran görüntüsü aşağıdaki gibi olacaktır.

6- Klavyeden Enter tuşuna basın ve ekran görüntüsü 3. adımdaki gibi olunca g tuşuna basıp ardından
son olarak enter tuşuna tekrar basın.
Yukarıdaki ekran çıktısında görüldüğü gibi konsol ekranına MERHABA ASSEMBLY yazdırdık ve debug
programından çıkıp komut istemine geri döndük. Hala hevesiniz kırılmadıysa :) neler yaptığımızı tek-
tek açıklayalım.

Debug programı Microsoft’un tüm işletim sistemlerinde bulunan, hafıza ve CPU’nun içindekileri
görmemizi, düzenlememizi sağlayan bir programdır. Çok ilkel bir program olmasına rağmen beynimizin
derinliklerine PC’nin yapısını kazıyacak olan ve bir assembly programcısının bilgisayara ne şekilde
bakması gerektiği konusunda yardımcı olacak yegane programlardandır. En fazla 2 makale sonra
Debug programını kullanmayı bırakıp daha jan-jan lı debuger programları kullanacağız, şimdilik böyle
idare edelim.

Biraz önce Debug programının kendisi içinde kullanacağımız e (enter) ve g (go) komutlarını kullandık.
e komutu hafızaya kod girişi yapmamızı sağlar ve g komutu ise CPU’ya şu sihirli kelimeyi söylememizi
sağlar; "Ey CPU yazdığım bu kodları sırasıyla çalıştır bakayım". CPU programcının sadık kölesi
olduğundan aynen denileni yapar.

E komutu ile hafızaya giriş yaptığımızı söyledik, komut isteminin en solunda 152F:0100 gibi bazı
rakamlar gördünüz, buradaki 152F sizin bilgisayarınızda farklı olabilir, ama 0100 aynıdır. işte : simgesi
ile ikiye ayrılmış bu 8 rakam hafızanın adresidir. Kodlar hafızaya yazılır, silinir, değiştirilir fakat bu
süreçte değişmeyen tek şey hafıza adresidir. Adresler her zaman bizim onları doldurmamız veya
erişmemiz için hali hazırda beklerler, bunu evinizin adresine benzetebilirsiniz. Eve anne gelir baba gelir
arkadaş gelir bazen tatilde boş kalır yani evin içindekiler değişkendir ama ama ev adresi her zaman
sabit kalır, ta ki ev yıkılana kadar :)

E komutu ile bu örnekte hafızanın 152F:0100 adresi ile 152F:011B adresleri arasını makine kodları ile
doldurduk. Bunu yaparken kod yazacağımız adreste hali hazırda hangi kod’un olduğunu görme gibi bir
lükse de sahiptik, tesadüfen hepsi 00’dı. Makine kodları CPU’nun anlayacağı yegane kodlardır ve 2’lik
(binary) yada 16’lık (hexadecimal) gösterimle ifade edilirler. Zamanla binary gösterim biraz fazla yer
kapladığından hexadecimal gösterim benimsenmiştir. Daha sonra hexadecimal gösterimin anlaşılması
zor olduğundan sembolik kodlar ile yazılan Assembly programlama dili geliştirilmiştir. İnsan oğlu
rahatına pek düşkün olduğundan :) daha sonra, kod yazması ve anlaşılması daha kolay olan B
Programlama dilini geliştirmiş bu da yetmemiş C dilini geliştirmiştir. (Bu sıralamada Assembly
programlama dilide A oluyor tabi ki) Programlama dilleri ABC diye devam derken C harfini çok seven
programcılar buna ++ ve # gibi aksesuarlar takarak değişik varyasyonlar denemişler ve çokta başarılı
olmuşlardır :) Neyse lafı fazla uzatmadan ve "ey programcı geçmişini bilmeyen geleceğinden bir
haberdardır" diyerek konumuz geri dönelim.

X86 PC’lerin hafızası byte adreslenebilir diye bir programcı ata sözü vardır. Bu söz her adrese en fazla
1 byte’lık kod yazabileceğinizi anlatır. Bizde burada öyle yaptık zaten, toplam 28 hafıza konumuna
(memory location) kod yazdık ve göreceksiniz biraz sonra programı derleyip dosya haline getirince
dosya boyutu 28 byte olacak.

Debug’ın g komutu ise meşhur fetch-decode-execute sürecini yani hafızadan al getir - kodunu çöz -
icra et sürecini başlatır. İşte biz buna halk dilinde program çalışıyor diyoruz. Programı mikroişlemci
(CPU) çalıştırır ve programcı tarafından kısmen kullanılabilir. Bu süreç programcıları fazla ilgilendirmez,
daha çok donanımla alakalıdır, bu yüzden ayrıntıya girmiyorum. Ama çok istek gelirse bu şarkıyıda
sizler için söyleyebilirim :)

BU KODLARI ÇOK SEVDİYSENİZ GELİN BUNLARI SAKLAMAK İÇİN BİR PROGRAM DOSYASI
OLUŞTURALIM

Bu işlemi yapmak için öncelikle programda ne kadar kod kullandığımızı bilmemiz yani programın byte
cinsinden uzunluğunu bilmemiz gerekir. Kodları kendi elimizle teker teker girdiğimizden uzunluğunun
28 byte olduğunu biliyoruz. Emin olmak için 5. adımdaki şekilden faydalanınız. Dosya oluşturma işlemi
için ilk önce CPU’nun CX kaydedicisine (CX ne yahu, kaydedicide ne demek kardeşim dediğinizi duyar
gibiyim merak etmeyin bu terimler ileride hiç ağzınızdan düşmeyecek) 28’in hex. karşılığı olan 1C’yi
yazacağız. Daha sonra debug’ın N komutu ile dosyaya isim vereceğiz ve son olarak W komutu ile
harddiskimize kayıt yapacağız. Son olarak debug’tan çıkıp komut istemine geri döneceğiz ve
oluşturduğumuz programı çalıştıracağız. Lütfen aşağıdaki şekli inceleyiniz. (Yalnız komut istemi
penceresini kapattıysanız işiniz yaş 1.adıma geri dönmeniz gerekiyor çünkü kodlar hafızadan silinmiş
olabilir)

Meşhur DOS komutlarından olan "dir" ile tekrar bir nostalji yapıp dosyamızı görelim :)
Görüldüğü üzere 28 byte’lık merhaba.com adlı dosyamız oluşturuldu ve çalıştırılmaya hazır, o zaman
hemen çalıştıralım.

Programımız konsol ekranına MERHABA ASSEMBLY yazdı ve işletim sistemi tarafından sonlandırıldı.
Programımızı bir dosya haline getirdik, Microsoft’un işletim sistemlerinde 2 tür program dosyası
mevcuttur ve bunlar dosya ismini takip eden EXE ve COM uzantıları (file extensions) ile tanınırlar. Bir
burada COM olanından oluşturduk, endişelenmeye gerek yok EXE uzantılı dosya’da oluşturacağız ve
COM ile EXE arasında ne gibi farklar var onları da göreceğiz fakat bunlar bizim için henüz çok ileri
konular, sabretmek lazım :)

Bu programı bir sonraki makalemize kadar silmeyin, çünkü bir sonraki makalemizde bu dosyadan
faydalanıp sembolik kodlardan oluşan assembly diline geçiş yapacağız ve eksik kalan noktaları
tamamlayacağız. Siz bu arada boş durmayıp bu kodlar üzerinde oynamalar yapabilirsiniz özellikle 12.
koddan itibaren son koda kadar olanları değiştirin (son kod hariç) çünkü bunlar MERHABA ASSEMBLY
yazısının ASCII kod karşılığıdır. Bunun için ASCII kod tablosundan faydalanabilirsiniz. Başka bir
programlama dili biliyorsanız aynı yazıyı yazan programı o programa dili ile yazıp dosya boyutunu
buradaki ile karşılaştırınız. Ayrıca Debug programının diğer komutlarını görmek için - işaretinin
görüldüğü modda ? yazıp enter tuşuna basınız.

Sembolik kodlar ve X86 Assembly Dili

Geçen makalemizde debug programını tanımış ve makine kodları ile küçük bir program
hazırlamıştık. Sonra bu programı yine debug ile bir dosya haline getirmiştik. Şimdi bu
dosya ile biraz daha yakından ilgilenelim. Yine komut istemine geçiş yapıp debug
programını şekildeki gibi çalıştıralım.

Not:Geçen makalemizdeki dosyayı kaybettiyseniz buradan indirin ve C:\ dizininde asm


adında bir klasör oluşturun ve bu dosyayı oraya kopyalayın.

Şekil 1 - Debug ile program yükleme

Ekranın solunda bir - işareti göreceksiniz ve başka hiçbir şey olmamış görünecek ama
aslında merhaba.com adlı program dosyasını oluşturan makine kodlarını çoktan hafızaya
yüklediniz bile. Makine kodlarını görmeden önce sizlere küçük bir tavsiye vermek
istiyorum. Assembly programlama dilinde ayrıntılar çok önemlidir ve her bir ayrıntı kendi
çapında bir araştırma konusu olabilir, örneğin burada yaptığınız program yükleme işlemini
windows kullanıcıları, programın simgesine mouse ile tıklayarak yapıyorlar ve hatta bu
şekilde program hafızaya yüklendikten sonra birde işletim sistemi tarafından çalıştırılıyor.
Şimdi aklınıza şu soru gelmeli; program simgesinin üzerine gelip mouse ile tık-tık
yapıldığı andan itibaren, programın yüklenip çalıştırılmasına kadar sistemde neler ve nasıl
yapılıyor? Öyle değil mi? Nelerin yapıldığını geçen makalemizi takip ettiyseniz tahmin
edebilirsiniz ama nasıl yapıldığına gelince o iş biraz sistem programcılığına giriyor yani
bizim için biraz erken. Neyse biz konumuza geri dönelim, program şu anda hafızada ve
0100h offset adresinden itibaren yüklenmiş durumda. Hemen kodları görelim o zaman.

Şekil 2 - Debug�ın "d" komutu

Debug�ın d komutu "dump" anlamına gelip programlama aleminde kodları ekrana yada
kağıda dökmek gibi bir anlamı vardır. Bizde burada kodları ekranda gösterdik. Neden
0100h adresinden itibaren hafızaya yerleşti? sorusunun yanıtı program dosyasının .com
uzantılı olmasından kaynaklanır ve geçen makalemizde açıkladığımız gibi sonraki
makalelerde ele alınacak bir konudur. Burada önemli olan husus sizin 0100 offset adresi
ile 010B offset adresleri arasında kalan makine kodlarından bazılarının komut kodları
bazılarının ise data (veri) kodları olduğunu bilmenizdir. Tabi ki ekrana yazdırılacak olan
MERHABA ASSEMBLY burada data sınıfına giriyor peki komutlar nerede?
Şekil 3 - Datalarımız...

Kırmızı ile işaretli olan kısımdan öncekiler (dikkat bu kısımdan sonraki kodlar bizi
ilgilendirmiyor) yani 0100h ile 010Ah adresleri arasında kalan kısım naçizane komut
kodlarımızdır. Tabi ki bu şekli ile bize pek bir şey çağrıştırmıyorlar şayet bunlar bizlerin
anlayabileceği dile biraz yakın olsaydı anlardık.

Şekil 4 - Makine ve Assembly Dili Yan yana

Yukarıdaki şekilde makine kodlarını sarı assembly kodlarını kırmızı çerçevede


görebilirsiniz. Tabi ki assembly dili bizlere daha yakın bir dildir. Assembly dilindeki bu
gösterim aynı zamanda sembolik kodlar olarak ta bilinir. Kodları assembly dilinde görmek
için debug�ın "u" (unassembly) komutunu kullandık, u dan sonra gelen 0100 010A ise
hafıza aralığıdır, yani biz burada 0100h-010Ah offset adresleri arasınındaki makine
kodlarını assembly dilinde görmüş olduk. Şimdi yukarıdaki şekilde gördüğümüz makine
ve assembly kodlarını karşılaştıralım;

B409 MOV AH,09

Assembly dilindeki MOV AH,09�un makine dilindeki karşılığının B409 olduğu görülüyor ve
assembly programcıları "B409" ile gösterilen makine dilindeki bu ifadeyi iki kısıma
ayırırlar. Bunlar opcode ve operand alanlarıdır.

Opcode Operand

B4 09
Tablo 1 : Opcode ve Operand

Buradaki B4 opcode�u işlenecek olan asıl emirdir ve ancak mikroişlemci (CPU)


tarafından kodu çözüldükten sonra 09 operanıda komut işleme sürecine katılır. Burada
sadece opcode ile operandı ayırmanız yeterlidir assembly dilindeki ifadeleri ne anlama
geldiğini şimdilik önemsemeyin. Intel�in opcode uzunluğu 1 yada 2 byte�lıktır. 2
byte�lık olan opcode�ları 0F ile başlar. Biz şimdilik hep 1 byte�lık opcodelar ile
çalışacağız. Operandlar ise herzaman opcode�lardan sonra gelir ve X86 mimarisinde
1,2,4 veya 8 byte�lık olabilir. Böylece ortaya karma karışık bir komut seti çıkar, bu
yüzdende Intel�in x86 ailesi CISC (Complex Instruction Set Computers) olarak anılırlar.

BA0B01 MOV DX,010B

Burda 3 byte�lık bir komut satırı, opcode 1 byte�lık operand 2 byte�lık. Ama şimdi
kafamızda 2 tane soru işareti var, birincisi neden MOV komutunun makine dilinde 2 tane
farklı opcode�u var? (yani B4 ve BA�dan bahsediyoruz ve aslında MOV komutunun
makine kod karşılığı 2 den de fazladır) ve ikinci soru neden MOV DX,010B için operand
kısmı makine dilinde 0B01 olarak ters bir biçimdedir?

Opcode Operand

BA 0B 01
Tablo 2 : Opcode ve Operand

Bu sorların cevabı X86 uyumlu mikroişlemci mimarisini anlayarak bulabilirsiniz. İlk olarak
B4 veya BA MOV�un karşılığı gibi görünse de öyle değildir, B4= MOV AH�ın BA=MOV
DX�in opcode karşılığıdır. Yani ikisi de farklı assembly ifadeleridir ve farklı
opcode�larının olması son derece normaldir. İkinci sorunun cevabı ise x86 ailesindeki
işlemcilerin hafızaya erişme şekillerinin ters sıralı olmasından kaynaklanır. Tabi ki birde
düz olarak byte�ları yerleştirme olayı var, bunlar kitaplarda little endian byte ordering ve
big endian byte ordering olarak geçer. Aslında byte düzenleri çok çok ileri seviyede
önemli konulardır ayrıca opcode�ları gösterirken seçilen bu hexadecimal değerlerde (B4
ve BA gibi) rastgele seçilmiş değerler değildir. Bu tür konuları öğrencilik yıllarımdayken
araştırmıştım, gerçekten epey zevkli konular, genellikle yüksek lisans ve doktora
sınıflarında instruction anatomy (komut anatomisi) başlığı altında incelenen konulardır.
Programcılık açısından ise sadece byte düzeni önemlidir. Bu yüzden burada sadece bun
iki kavramı açıklayacağım.

Little Endian Byte Ordering: Hafızda yüklenecek olan byte�lar düşük değerlikli kısmından
itibaren yazılır. Örneğin 1234 gibi iki byte�lık bir veriyi hafızaya 0100 offset adresinden
itibaren yazdığınızı düşünelim, bu işlem bitip hafızaya baktığınızda göreceğiniz şey
0100=34, 0101=12 olacaktır.

Şekil 5 - Little endian byte düzeni

Burada debug�ın "a" (assembly) komutunu vererek assembly dilinde program yazma
moduna geçtik ve dw talimatı ile hafızaya 1234 değerini girdik ve hafızaya, önce bu
sayının düşük değerlikli byte�ı sonrada yüksek değerlikli byte�ı yazıldı.

Big Endian Byte Ordering: Hafızaya yüklenecek byte�lar en yüksek değerlikli kısmından
itibaren yazılır aynen bizim kağıt üstüne sayıları yazdığımız gibi. Sizlere burada bir tanede
big endian örneği vermek isterdim fakat şu anda elimde bir Apple PowerPC olmasına
rağmen MAC OSX işletim sistemi için debugger yok :( Aşağıdaki tablo hangi sistemlerin
hangi byte düzenini kullandığını gösteriyor.

Byte
İşlemci Türü İşletim Sistemi
Düzeni

little-
Digital Alpha AXP Tru64 UNIX
endian

Hewlett Packard PA-RISC HP-UX big-endian

IBM RS/6000 AIX big-endian

Intel x86 little-


Linux
endian

little-
Windows
endian
little-
Solaris x86
endian

Motorola PowerPC Macintosh OS X big-endian

SunOS big-endian
Sun SPARC
Solaris big-endian

SGI R4000 Irix big-endian


Tablo 3 : Üreticilerin çoğu big-endian tercih ederken dünyada en çok kullanılan little-endian
byte düzeni :)

CD21 INT 21
B44C MOV AH,4C
CD21 INT 21

INT 21�de bir byte�lık opcode�u ve bir byte�lık operandı olan başka bir komut satırı.
Bu arada MOV AH,4C nin opcode�u ile yukarıda açıkladığımız MOV AH,09�un
opcode�larının aynı olduğuna dikkatinizi çekerim.

Buraya kadar ;

1- Neo�nun Matrix�i gördüğü gibi bizlerde hafızaya baktığımızda orada gördüğümüz


byte�ları nasıl yorumlayacağımızı gördük, bu yolda kendinizi geliştirmeye kalkarsanız
sizde belki bir gün Matrix�i görebilirsiniz :) Yıllardan beri ben hafıza dökümlerine
bakarım onlarda ekrandan doğru bana bakarlar ama program boyutu 10Kb�ı geçince
insan halüsülasyonlar görmeye başlıyor onuda belirteyim :)

2- Makine kodları ile assembly kodları arasındaki bağıntıyı gördük, Borland gibi bir
derleyici (compiler) yazan bir firma kurmak isterseniz, bu alanda en az bir doktora tezi
vermeniz gerekir. Yani for (int i=0 ; i<100 ; i++) gibi bir ifadenin derleyici ile makine
diline dönüşmesi zannettiğinizden daha karışıktır.

Özetle buraya kadar derin konuların giriş kısımları anlatıldı. Şimdi sıra assembly kodlarına
bir hacker gibi değil de bir programmer gözüyle bakmaya geldi.

Assembly Kodlarına Bir Assembly Programcısı Gibi Bakmak

Yazdığımız programda iki tane iş yapılıyor. Birincisi ekrana bir dizi byte�ın ASCII
görünümünü yazdırmak, ikincisi bilgisayarı tekrar kullanıcının ellerine bırakmak yani
işletim sistemine geri dönüş.
Şekil 6 - Programımızın parçaları

Yukarıdaki şekilde algoritmanızın assembly koduna dönüşmüş halini görüyorsunuz.


Tamam, geçen hafta bu programı yazarken algoritma falan hazırlamadık ama önce
ekrana yazdır sonra çık mantığını için de algoritma yazılmaz herhalde :)

Bu program DOS�un meşhur kesmelerinden (interrupt) 2 tanesini çağırdı aslında,


birincisi 9. fonksiyon olan "Display String", ikincisi fonksiyon numarası 4C olan
"Terminate Program". Bunları söyledikten sonra ne desem kafanız karışacak bu yüzden
bu olayı daha iyi anlamanız için aşağıdaki diyalogu hazırladım. Buradaki acemi assembly
programcı (AAP) azıcık assembly bilgisi olan birini, tecrübeli assembly programcısı (TAP)
yeterli derecede bilgisi olan bir programcıyı ima ediyor.

Acemi programcının kafasının içinden geçenler;

AAP- Assembly dilinde ekrana bir şey yazdıracağım!


AAP- Hafızaya dataları girer sonrada bunları ekran kartına gönderirim nasıl olsa ekran
kartı daha sonraki işlemleri kendisi halleder,
AAP- Ama gönderme işlemini nasıl yapacağım, hımmm ben en iyisi bir bilene sorayım.
AAP- Merhaba TAP, sana bir sorum olacak ekrana MERHABA ASSEMBLY yazdırmak
istiyorum ama ekran kartına byte�ları nasıl göndereceğimi bilmiyorum, bana anlatırmısın
lütfen?
TAP- Hangi işletim sistemini kullanıyorsun?
AAP- Windows!
TAP- Byte�ları ekran kartına göndermene gerek yok, onu senin yerine işletim sistemi
yapar zaten.
AAP- Peki nasıl olacak?
TAP- INT 21�in 9. fonksiyonu senin için birebir, işlemcinin AH kaydedicisine 09, DX
kaydedicisine karakterlerin başlangıç adresini yaz, sonrada 21. interrupt servisini çağır.
Ha bu arada son karakterden sonra hafızaya bir $ işareti koy ki interrupt servisi yazılacak
karakterlerin bittiğini anlasın. Anladın mı?
AAP- Anlar gibi oldum? Ama ben bu işi kendim yapamaz mıyım?
TAP- Yaparsın tabi ama çok ekmek yemen lazım :)
AAP- Nasıl yani?(bu arada TAP�ın gülüşüne de kızar tabi)
TAP- Bak AAP kardeş, Windows gibi işletim sistemini yazan programcılar senin gibi
programcıların ekrana bir şey yazmak isteyeceğini, veya klavyeden bir bilgi alacağını
yada yazıcıdan çıktı almak isteyeceğini düşünerek bazı küçük programları yazıp işletim
sistemlerine gömmüşlerdir. Sende bu hizmetlerden faydalanır ve Amerika�yı yeniden
keşfetmeye gerek duymazsın.
AAP- Evet çok akıllıca düşünmüşler doğrusu...
TAP- İşletim sistemi de bu demektir zaten AAP, programcıya, kullanıcıya bilgisayarı
kolayca kullandırabilmek.
AAP- Teşekkür ederim TAP sonra görüşürüz.
TAP- (AAP�nin sonra tekrar gelip başka interrupt servislerini de soracağından
endişelenerek) Hey AAP, bak buradan diğer interrupt servislerine de bakabilirsin, hani
lazım olur diye söylüyorum...
AAP- Teşekkürler TAP.

Daha sonra AAP bilgisayarının başına gider yukarıdaki şekildeki programın ilk üç satırını
yazar ve çalıştırır, tabi ki ekranda MERHABA ASSEMBLY görünür ama komut istemindeki
penceresini kilitlenir, çünkü ekrana yazdırma işleminden sonra programı
sonlandırmamıştır. Bunun üzerine programı sonlandırmak için tekrar interrupt servislerini
araştırmaya koyulur ve interrupt 21�in 4C fonksiyonunu keşfeder. MOV AH,4C ve INT 21
satırlarını da programına ekledikten sonra programı kusursuz çalışır.

Interruplar hakkında şimdilik bu bilgiler umarım sizi tatmin etmiştir. İlerleyen


makalelerimizde interruptları daha yakından ele alacağız, bu konular gerçektende çok
zevkli.

Yukarıdaki diyalog ta kaydediciler (register) diye bir kavram geçti, bunları şimdilik
mikroişlemci içindeki hafıza konumlarına (memory location) benzeyen yapılar olarak
düşünün. Kaydedicilerin listesini görmek için debug programını kullanabilirsiniz.

Şekil 7 - Kaydedicileri debug ile görmek için r (registers) komutunu kullanabilirsiniz.

X86 kaydedicilerinin AX, BX, CX, DX, SP, BP... diye adları vardır ve bunların hepsinin
değişik işlevleri mevcuttur. Program yazarken elbette bunları kullanacağız ama şimdilik
üzümünü yiyin bağını sormayın, çünkü bağın yerini söylersem ve sizde gider o bağa
bakarsanız iştahınız kaçabilir. Kafalar fazla karışmadan yeni bir uygulama yapalım.
10 defa Merhaba Assembly Yazdırıyoruz

Tabi böyle bir işi yaptırmanın değişik yolları mevcut ( bir .bat uzantılı script dosyası
hazırlayıp ilk programımızı 10 defa çalıştırmak bunlara dahil değil tabi :) ) Şimdi
yazacağımız programda ilk programımızın ilk üç satırını 10 defa çalıştırıp sonra programı
sonlandıracağız. C dilindeki for döngüsü gibi. Aşağıdaki işlemleri takip edin.

Şekil 8 - 10 defa alt alta MERHABA ASSEMBLY yazan program

Programı yukarıdaki gibi hafızaya girdikten sonra bir önceki makalemizden faydalanıp bu
programı dosya haline getirin, byte�ları saymada sizler için pratik olur. Burada CX
kaydedicisine (0A)16=(10)10 değerini sayaç olarak yüklüyoruz. LOOP komutu CX�in
değeri kadar operandı ile belirtilen adrese dallanır, burada dallanılacak adres 0103 offset
adresidir ve bu işlem sayesinde ekrana 10 defa MERHABA ASSEMBLY yazılır. Daha sonra
programımız fonksiyon:4C interrupt 21 ile sonlanıyor. Buradaki string�e dikkat, debug
ile assembly modunda hafızaya bir dizi karakter girmek için DB (Define Byte) talimatını
kullanıyoruz. Talimatlar (daha sonra ayrıntılı bir şekilde göreceğiz) derleyiciye verilir yani
opcode olarak dönüştürülmezler, aynı C�deki int, double, string veri tipleri gibi. DB�den
sonra şayet byte�ları karakter olarak girmek isterseniz " " arasına yazmalısınız. Bu işlemi
karakterlerin hexadecimal kodlarını yazarak ta yapabilirsiniz bu sefer her bir karakterden
sonra "," koymanız gerekir. Burada her iki teknikte kullanılmıştır. ASCII kod tablosuna
bakacak olursanız 0A=LF yani Line Feed (bir satır aşağı) 0D=CR yani Carriage Return
(Kursör satır başına) olduğunu görürsünüz, zaten CR ve LF kontrol karakterleri
olduğundan dolayı standart ASCII kod tablosunda A,C,P,Z gibi normal karakter olarak
karşılıkları yoktur mecburen hex. kod karşılıklarını kullanmak zorunda kaldık. INT 21
fonksiyon 9 ile yazdıracağınız karakter dizilerinin sonunda mutlaka 24=$ bulunmalı yoksa
bu interrupt servisi hafızada 24 değerini bulana kadar ekrana yazma işlemine devam
eder.

Bu programın kodları ile oynayarak konuyu daha iyi kavrayabilirsiniz, örneğin 0D


karakterini kodlardan çıkartın yada CX�e yüklenen değeri değiştirin.

Son olarak debug ile bu programı adım adım çalıştırabilirsiniz, bunun için program yazma
işlemi bitince P (Proceed) komutunu kullanın T (Trace) komutu ile programınız içinden
çağrılan interrupt servislerine de girebilirsiniz (ama çıkabilirmisiniz orasını bilemem)
Debug�tan çıkmadan bir daha programınızı çalıştırmak için "r ip" komutunu kullanarak ip
kaydedicisini 0100 yapın, böylece programın başlangıç adresini doğru ayarlamış
olursunuz.

Şekil 9 - Adım adım programı işlemek

Temel Assembler Bilgisi

Assembly programlama dili, kullanılan bilgisayar sisteminin yapısına ve işletim sistemi


gibi platformlara sıkı-sıkıya bağımlı bir dildir. Bu yüzden biri gelirde "ben assembly dilini
biliyorum" derse sakın inanmayın :) Çünkü çok fazla şey bilmiş oluyor.

Assembly dili diye bir şey yok!

Ama o kişi X86 PC Assembly dili, Power PC Assembly dili veya 8051 Assembly dili
biliyorum deseydi o zaman iş değişirdi. X86 PC Assembly dili, Power PC Assembly dili gibi
bir sınıflandırma bile tam olarak her şeyi ifade etmez. Örneğin X86 PC Assembly dili
donanım platformunu tanımlar ve bu donanımı çalıştıracak işletim sistemi Windows,
Linux, Solaris olabilir, her bir işletim sisteminin hafızayı ele alış şekli başka olduğundan
programcılar işletim sisteminin hafıza organizasyonunu göz önüne almak
mecburiyetindedirler. Ayrıca kodlarınızı derlerken kullandığınız program olan
Assembler’da işe ayrı bir sınırlama koyar. Örneğin donanım X86 bir PC, işletim sistemi
Windows olsun, bu durumda assembler olarak Netwide kullanırsanız farklı Turbo
assembler kullanırsanız farklı talimatlarla program kodu yazmak zorunda kalırsınız ve bu
kodlar adı geçen assembler programları tarafından makine koduna çevrilirler. Sonuç
olarak assembly dilinin programcılık açısından en büyük dezavatajı budur. Fakat
assembly’yi hızlı ve özelliklede sağlam bir dil yapan unsurlarda bu dezavantajın
sonucudur.
Bunca donanım ve işletim sistemi içinde acaba hangisi en çok kullanılıyor? Intel’in
işlemcileri ve Microsoft’un işletim sistemleri tabi ki. İşte bizde Intel uyumlu ve
Microsoft’un MSDOS ve Windows işletim sistemlerine uygun bir assembly dili
üzerine makalelerimizi yazıyoruz. Yani, "Bu programa dilini zor mudur?" sorusuna,
programa dilini bırak adı bile zor diyebilirsiniz. Bu yüzden assembly denildikten sonra
genelde "hangi assembly?" kelimesi de konuşulur. Konuların içinde boğulup kalmamak
için biz bu programlama dilinin neresindeyiz ve bundan sonra hangi rotayı izlememiz
gerekiyor gibi soruları sanırım yukarıdaki paragrafta açıklamış olduk.

Umarım bundan sonra makalelerimizde assembly dili kelimesini geçtiğinde neyi


anlamanız gerektiğini anlamışsınızdır. Bundan sonraki makalelerde izleyeceğimiz
assembly dilinin tek bir eksik yanı kaldı işte o da hangi assembler’ı kullanacağımız.

Hangi ASSEMBLER?

Assembler bir çeşit programdır ve assembly kodlarını makine kodlarına çevirir.

Şekil 1 - Assembly dilinden Makine diline

X86 uyumlu (eski tabiri ile IBM uyumlu) PC’ler için en popüler assembler’lar TASM, MASM
ve NASM dır.

Assembler Tam Adı Üretici

Turbo
TASM Borland
Assembler

Microsoft
MASM Macro Microsoft
Assembler

Netwide
NASM LGPL
Assembler
Tablo 1 : En Popüler Assemblerler

X86 ailesi için daha başka assemblerlarda mevcut fakat en çok kullanılanlar yukarıda
saydıklarımızdır. MASM ve TASM .asm uzantılı bir dosyanın içindeki assembly ifadelerini
ve direktiflerini okur ve bunları makine kodlarına dönüştürür. NASM ise TASM ile MASM’ın
bu yaptığına ek olarak Linux ve Unix ortamlarında da çalışır. TASM ve MASM kardeş
assemblerlardır. Yazım kuralları hemen hemen aynıdır. Peki hangi assemblerı seçmeli?
Aslında bu yapacağınız projeye bağlı tabiki, örneğin Visual Studio IDE’si ile C++
uygulamaları geliştiriyorsanız MASM kullanmanız daha akıllıca olur, en azından MSDN’de
desteği mevcut. Biz makalelerimizde hem TASM hemde MASM’ı kullanacağız.
Artık Programlarımızı Assembly Dilinde Yazıyoruz!

Hangi Editörü Kullanmalı?

Sadece Assembly dili için geliştirilmiş Visual Studio.NET gibi çok kullanışlı IDE’ler yoktur.
Eskiden edit.com (DOS zamanından kalma) programını kullanırdık ve bu program hala
windows’un tüm versiyonlarında mevcuttur.

Şekil 2 - Edit programını çalıştırmak için BAŞLAT/ÇALIŞTIR’dan edit.com yazıp tamama


tıklayın.

Şekil 3 - İşte en ilkel editörlerden EDIT.

Yine Windows’un klasik programlarından notepad (not defteri) assembly dili için başka bir
seçenektir. Ama dosyalarınızı ANSI olarak kaydetmeniz gerekiyor.
Şekil 4 - Her derde deva Notepad ile programlarınızı ANSI olarak kaydetmeniz gerekiyor.

Peki bu yazıyı yazan editörün editör seçimi nedir? :) Ben sizlere GVIM’i tavsiye ediyorum.
GVIM hemen hemen tüm popüler assembler söz dizimini (syntax) desteklediği gibi tüm
popüler programlama dillerinin de söz dizimini destekleyen süper bir editör. TASM, MASM
ve GVIM’ı bu makalenin sonundaki download kısmından edebilirsiniz.
Şekil 5 - Gvim ile assembly kodlarını yazmak daha kolay, programı çalıştırdıktan sanra
yazma moduna geçmek için klavyeden i tuşuna basmanız gerekiyor.

Editör programlarını kısaca tanıttık dan sonra artık gvim ile örnek bir assembly program
yazabiliriz.

Nihayet Assembly

Şimdi geçen makalemizde yazdığımız programı tekrar yazacağız ama bu sefer kaynak
kodlarımız .asm uzantılı bir dosyada olacak. GVIM’i çalıştırdıktan sonra klavyeden i
tuşuna basıp aşağıdaki kodları yazınız yada kaynak kod dosyasını buradan download
ediniz.

TITLE Merhaba Assembly (merhaba.asm)


;########################################
;# Bu program ekranda Merhaba Assembly yazısını gösterir #
;# Son Güncelleme: 14/02/05 #
;# Yazan --> Eren ERENER #
;########################################

.MODEL SMALL
.STACK 32
.DATA

Dizi DB "Merhaba Assembly",0Ah,0Dh,24h

.CODE

ANA PROC

MOV AX, @DATA ;Data segment


MOV DS, AX ;ayarlanıyor.

MOV CX, 0Ah ;Sayaç=10.


ONDEFA: MOV AH, 9 ;Ekrana,
MOV DX, OFFSET Dizi ;string,
INT 21h ;yazdırılıyor.
LOOP ONDEFA ;Bu işlem 10 defa tekrarlanacak.

MOV AH,4CH ;DOS’a


INT 21H ;dönüş

ANA ENDP

END ANA

Assembly dili yazım kuralları ile program bu şekilde yazılıyor, nasıl daha önce
yazdıklarımızdan biraz farklı değil mi? Tüm bu farklılıklar sadece assembler için.
Assembler bu dosyayı satır satır okuyacak ve makine koduna çevrilecek kısımları algılayıp
bunları 1 ve 0’lara çevirecek. Vakit kaybetmeden bu kodları program dosyası haline
getirelim. Bunun için ilk önce assembler programına sahip olmamız gerekiyor. Assembler
programlarından dilediğinizi buradan download ettikten sonra aşağıdaki adımları sırayla
takip ediniz.

MASM ile derleme işlemi

1- masm.zip dosyasının içeriğini C sürücüsüne (yada istediğiniz herhangi bir sürücüye


mesela burada E:\ sürücüsü örnek olarak verilmiştir) açınız,
2- merhaba.asm dosyasını da bu klasöre kopyalayınız,
Şekil 6 - Çalışmalarınız bir klasör içinde olsun

3- ml/c dosyaadi.asm şeklinde kaynak kodunuzu assembly ediniz,


4- Bu işlemin sonunda assembler merhaba.obj adında bir obje dosyası oluşturur,
5- Program dosyası oluşturmak için .obj uzantılı dosyayı link16 merhaba.obj komutu ile
link etmeniz gerekiyor. Bundan sonraki adımları enter’ı tuşlayarak geçin.

Şekil 7 - MASM ile assembly ve link işlemleri

6- Programınızı dosya adını yazarak çalıştırabilirsiniz.


Şekil 8 - Programı çalıştırmak için dosya adını yazıp enter tuşuna basmalısınız :)

TASM ile derleme işlemi

1- tasm.zip dosyasının içeriğini C sürücüsüne (yada istediğiniz herhangi bir sürücüye


mesela burada E:\ sürücüsü örnek olarak verilmiştir) açınız,
2- merhaba.asm dosyasını da bu klasöre kopyalayınız,

Şekil 9 - Çalışmalarınız bir klasör içinde olsun

3- tasm dosyaadi.asm şeklinde kaynak kodunuzu assembly ediniz,


4- Bu işlemin sonunda assembler merhaba.obj adında bir obje dosyası oluşturur,
5- program dosyası oluşturmak için .obj uzantılı dosyayı tlink merhaba.obj komutu ile link
etmeniz gerekiyor.
Şekil 10 - TASM ile assembly ve link işlemleri

6- Programınızı dosya adını yazarak çalıştırabilirsiniz.

Şekil 11 - Başardık programımız çalışıyor

Dikkat ederseniz her iki assemblerda aynı kaynak kodunu derledi ve sonunda .exe
uzantılı bir program dosyası oluşturdu bu assemblerları değişik parametrelerle kullanıp
.com türünden dosyalarda oluşturabiliriz. Aslında ben bu kaynak kodunu TASM için
yazmıştım ama önceden de dediğim gibi iki assemblerın da çok fazla ortak yanı vardır
fakat bu aynı oldukları anlamına gelmez ve her zamanda aynı kaynak kodunu
derlemeyebilirler. Buradaki basit bir program olduğundan sorun çıkmadı ve biz uzun bir
süre böyle basit programcıklar yazacağız. Bu yüzden şu anda derleme aşamasında hangi
assemblerı kullandığınızın pek bir önemi yok.
Burada download ettiğiniz assembler ve linker programları TASM ve MASM paketinin
içinden alınmış dosyalardır. Daha sonra tüm paketi vereceğim şimdilik bu kadarı yeterli.

Şimdi kaynak kodlarımızı bir daha gözden geçirelim ve dikkatimizi rakamların sonundaki
h harfine odaklayalım. h harfi burada rakamın veya sayının hexadecimal (16lık sayı
sistemi) olduğunu gösterir. Biz debug ile program yazarken bunu belirtmemiştik çünkü
debug her yazılan sayıyı hex. olarak (daha doğrusu binary olarak) kabul eder. Bu
makalemize sayı sistemlerine kısaca inceleyerek son vereceğiz. Kaynak kodumuzu satır
satır anlatmamı bekliyor olabilirsiniz ama bu işlemci kaydedicileri ve x86 hafıza yapısını
öğrenmeden bu iş biraz zor.

Bilgisayar Aritmetiği

Genelde programcılık derslerinin ilk ve sıkıcı konularındandır sayı sistemleri, bunu nedeni;
bu sayı sistemlerinin ne için kullanıldığının tam kavranamamasından kaynaklanır. Aslında
işlemci sayılardan falan anlamaz, sadece elektrik akımın var yada yok olması temeline
göre çalışırlar. Hani duymuşsunuzdur pentium işlemcinin içinde bilmem kaç milyon tane
transistör var diye işte elektrik akımları bu transistörler aracılığı ile kontrol edilir.
Transistör yarı iletken bir elektronik devre elemanıdır ve her modern elektronik cihazın
içinde bulunur. Birçok kullanım alanı olan transistörler bilgisayarlarda anahtarlama
elemanı olarak kullanılırlar.

Şekil 12 - 1’ler ve 0’lar anahtarın açık yada kapalı olmasını temsil ederler

Elektrik akımlarının göremeyeceğimizden dolayı bizler bu tür olayları sembolize ederek


anlayabiliriz. Elektrik akımının olmayışını 0 var olmasını ise 1 olarak kabul edince ortaya
doğal olarak 2 tane rakamı olan bir sayı sistemi çıkar. Bizler 10 rakamlı sayı sistemini
kullanıyoruz bilgisayar ise 2 rakamlı sayı sistemini. Bu yüzden bilgisayarda işleme
sokacağımız her veriyi ikilik (binary) olarak göstermemiz gerekir. Binary sayı sistemi
hacim olarak kağıt üzerinde veya ekranda fazla yer kaplar bu yüzden binary sayı
sisteminin bir türevi olan 16’lık sayı sistemi daha çok kullanılır. Burada sayı sistemleri
arasında nasıl dönüşüm yapıldığını anlatmayacağım çünkü az çok programlama ile
uğraşanlar bunları zaten bilirler yada bu dönüşümleri hesap makinesi kullanarak yaparlar
:) Yinede çok isteyen olursa anlatabilirim bunun için bu makaleye yazacağınız yorumlarda
bunu belirtin.

Aşağıdaki tablo 1’den 15’e kadar decimal (onluk tabandaki) sayıların hex. ve binary
karşılıklarını veriyor. Rakamlara bakacak olursanız assemblerin bunları nasıl ayırt edeceği
problemini anlarsınız. Bütün rakamlar benzer sembolleri kullanıyorlar bu yüzden
assembly dilinde program yazarken sayı binary ise sonuna b hexadecimal ise h ve
decimal ise d (yada hiçbirşey) getirilir. Aşağıdaki tabloda binary kısım 8 haneli
gösterilmiştir çünkü x86 hafızası byte adreslenebilir yapıdadır yani hafızada en küçük
rakamları depolamak için bile 8 hane (digit) kullanılır. Hexadecimal değerler de binary
değerlerin sanki sıkıştırılmış halidir. Herhangi bir hex. değerin 1 basamağı 4 bit’e denk
gelir (nibble).
Decimal Binary Hexadecimal
0000
0 00
0000

0000
1 01
0001

0000
2 02
0010

0000
3 03
0011

0000
4 04
0100

0000
5 05
0101

0000
6 06
0110

0000
7 07
0111

0000
8 08
1000

0000
9 09
1001

0000
10 0A
1010

0000
11 0B
1011

0000
12 0C
1100

0000
13 0D
1101

0000
14 0E
1110

0000
15 0F
1111

Tablo 2 : Rakam karşılık tablosu

Kaynak kodumuzda sayılardan sonra kullanılan h harfleri sayıların hex. olduğunu


assemblera bildirmek için kullanılmıştır. Ama isterseniz siz örneğin 21h sayısını 33 olarak
doğrudan yada 0010 0001b olarak yazabilirsiniz. Nasıl yazarsanız yazın Assembler bu
değerleri son olarak binary’ye dönüştürecektir, işte .exe ve .com gibi işlemci tarafından
çalıştırılabilir program dosyalarına binary file denmesinin nedeni de budur. Aynı binary file
sözcüğü diğer işletim sistemlerindeki işlemci tarafından doğrudan çalıştırılabilecek
dosyalar içinde kullanılır. Hatta linux, unix gibi işletim sistemlerinde bin klasörü de içinde
binary file’lar olan klasör anlamına gelir buna bazı ftp sitelerinde rastlayabilirsiniz.

Assembly dilinde özellikle bir program dosyasının kodlarını incelerken debugger


programlarında görebileceğiniz yegane sayılar hexadecimal olduğundan dolayı bu sayı
sistemini iyi bilmeniz gerekiyor diyorum ve bu makaleyi de burada sonlandırıyorum.
Dosya Adı Açıklama Dosya

Assembler
tasm.zip Download
ve Linker

Assembler
masm.zip Download
ve Linker

gvim63.exe Editör Link


Tablo 3 : Download

Kaydediciler ve Hafıza

Mikroişlemcinin ve hafızanın fiziksel yapısı ile programcının bu donanımlara bakışı biraz


farklı olabilir. İşte biz bu makalemizde programcı gözüyle bu donanımları inceleyeceğiz.
Ayrıca bu makalemizde yeni bir program yazarak kaydedicilerin nasıl çalıştığını ve
mikroişlemcinin hafızaya nasıl eriştiğini anlamaya çalışacağız.

Assembly programcısı sisteme nasıl bakıyor?

Mikroişlemci bir çok karmaşık birimden oluşur, neyse ki programcılar donanımsal olarak
sistemleri fazla yakından bilmek zorunda değillerdir. Bir assembly programcısı sadece
mikroişlemci içindeki birkaç kaydediciyle ilgilenir. Bunun dışındaki birimler genelde
okullarda bilgilendirmek amacıyla verilir, örneğin CPU içinde bir ALU biriminin olduğunu
bir çok programcı bilir ama bu birimin çalışması ile ilgilenmezler. Bir assembly
programcısı keskin bakışlarını mikroişlemcinin içinden çekip :) sistemin tamamına
baktığında bile, donanımların ve donanımları birbirine bağlayan birimlerin çalışması ile
ilgili fazla birşey göremez yada görmesine gerek yoktur. Programcılık ve elektroniğin sınır
noktası budur aslında. Assembly programcıları kaydediciler, hafıza veya sistemi oluşturan
birimlerde dolaşan elektron akımlarını 1�ler ve sıfırlar olarak görürler ve genelde bu 1 ve
0�lardan enaz 8 tanesini yan yana gördüklerinde bir şeyler anlamaya veya yorumlamaya
başlarlar.

X ışınlı gözlerimiz PC üzerinde?


Şekil 1 - PC�nin kabaca iç organizasyonu

Bir sisteme bilgisayar diyebilmeniz için o sistemin en az 1 CPU, memory (RAM-


ROM/Hafıza) ve I/O (Input-Output/Giriş-Çıkış) birimin olması gerekir. Elbette bu birimle
birbirlerine yollar (bus) ile bağlı olmalı ve sistemin açılabilmesi için hafızasında bir
firmware programının yüklü olması gerekir. Yukarıdaki şekilde bu birimleri görüyorsunuz.
Dikkat ederseniz bütün yollar CPU�ya çıkıyor, diğer birimler arsında doğrudan bir
bağlantı yok, bunun böyle olması gerekir yoksa işler kontrolden çıkabilir. Aslında her
modern bilgisayarda DMA (Direct Memory Access/Doğrudan Bellek Erişimi) adında bir
transfer yapısı mevcuttur fakat bu özellik kullanıcı tarafından kapatılabildiğinden dolayı
birimler arasındaki en temel bağlantı yukarıdaki şekildeki gibidir. Ayrıca DMA özelliği
kullanıldığında CPU veri transferinden elini ayağını tamamen değil kısmen çeker, CD-ROM
kilitlenmelerinde Ctrl+Alt+Del kombinasyonun zorda olsa çalışması bundan dolayıdır. Ne
demişler kontrolsüz güç, güç değildir :)

Konuyu fazla dağıtmadan isterseniz X ışınlı gözlerimizle CPU�ya zoom yapalım :)

X ışınlı gözlerimiz CPU üzerinde?


Şekil 2 - Mikroişlemcinin kabaca iç yapısı

Sistemi içindeki birimleri birbirine bağlayan yollar, mikroişlemcinin içinde de devam eder.
BUS dediğimiz bu yapılar birçok iletkenden oluşur. Kontrol yolunda R/W (oku/yaz) gibi
kontrol sinyalleri, veri yolundan işlenecek veriler gidip gelir. Adres yolu iste tek yönlüdür
ve CPU�dan diğer birimlere doğru sinyal akışı olur, örneğin hafızaya bir veri
yerleştirilmeden önce adres yolundan verinin yerleştirileceği hafıza adresinin bilgisi
gönderilir. Bunu daha iyi anlamak için aşağıdaki animasyonu izleyebilirsiniz yada
animasyonu offline izlemek için download edebilirsiniz.
En temel 3 birimden biri olan ALU aritmetik ve mantıksal işlemleri yapan bir elektronik
devredir. BIU ise komut kodlarının icrası ile ilgilenir yani emirleri icra eden birim olarak
basitçe tanımlanabilir. Kontrol ünitesi ise sistemdeki birimleri harekete geçirmek için
kontrol sinyalleri üretir. Bu üç birime programcının doğrudan müdahalesi söz konusu
değildir. Programcı sadece CPU içindeki kaydedicilere erişebilir.

Şekil 3 - Kaydediciler işlemcideki, aynı zamanda tüm sistemdeki en hızlı depolama


birimleridir. Bu şekil 32 bitlik işlemciler (P4 serisi gibi) için kaydedici yapısını gösteriyor.

Kaydedicileri tam olarak anlayabilmeniz için onları kullanmanız gerekir, yani burada ne
kadar anlatsak boş bu yüzden kısaca yukarıdaki şekilde gördüklerinizi açıklayıp hemen bir
uygulama yapalım.

Genel Amaçlı Kaydediciler

Bu kaydedicilerin 8,16 ve 32 bitlik kullanımı mümkündür. Örneğin AL ve AH


Accumulator�ün 8 bitlik kullanımını AX 16 bitlik EAX ise 32 bitlik kullanımını simgeler.
Genel amaçlı kaydedicilerin hepsi verileri geçici olarak üzerlerinde barındırabilir fakat bazı
x86 komutları buradaki kaydedicilere özeldir. Mesela loop komutu ile CX kaydedicisinin
değeri azalır.

Accumulator (EAX, AX, AH, AL): En sık kullanacağınız kaydedicidir. Çok genel kullanım
alanına sahiptir, daha önceki makalelerimizde yazdığımız kodlara bakarsanız çok değişik
amaçlarla kullanıldığını görebilirsiniz. Bu kaydedici birçok giriş/çıkış işleminde ve aritmetik
işlemlerde (çarpma, bölme ve taşıma gibi) kullanılır. Ayrıca bazı komutlar işlenmeden
önce accumulator�den parametre alır veya bu kaydediciye işlemin sonucunu kaydeder.
Base (EBX, BX, BH, BL): Accumulator gibi genel amaçlı ve hafıza erişiminde indexleri
göstermede kullanılır. Bir başka kullanım alanıda hesaplamalardır.

Counter (ECX, CX, CH, CL): Özel amaçlar ve hesaplamalarda kullanılacağı gibi
genellikle bu kaydediciyi sayıcı olarak kullanılır, daha önce loop komutunun CX
kaydedicisini otomatik olarak değiştirdiğini söylemiştik.

Data (EDX, DX, DH, DL): Bazı giriş/çıkış komutlarında bu kaydedicinin kullanılması
gerekir, ayrıca çarpma ve bölme işlemlerinde accumulator ile birlikte büyük sonuçları bu
kaydediciden okuruz.

Segment Kaydedicileri

Segment kaydedicilerinin hepsi 16 bitliktir ve hafızanın segment olarak adlandırılan


kısımlarını adreslemede kullanılır.

Code Segment Kaydedicisi (CS): DOS işletim sisteminde programları oluşturan kodlar
code segment�e yüklenir. CS kaydedicisi ise IP kaydedicisi ile birlikte programın çalışma
sürecinde, programın oluşturan kodların adreslerini gösterirler.

Data Segment Kaydedicisi (DS): .exe türündeki bir programda kullanılacak olan
veriler data segment denilen hafıza bölümünde tutulur. DS kaydedicisi ise bu bölgedeki
verilerin konumlarını gösterir.

Stack Segment Kaydedicisi (SS): Tüm programlar stack segment denilen bir hafıza
alanını geçici depolama alanı olarak kullanmak zorundadırlar (örneğin dallanma
işlemlerinde). SS kaydedicisi ise SP kaydedicisi ile birlikte bu verilerin adreslerini referans
eder.

Extra Segment Kaydedicisi (ES): Bazı string işlemlerinde DI kaydedicisi ile birlikte
karakterlerin bulunduğu hafıza adreslerini tutarlar.

FS ve GS Kaydedicileri: 80386 ve sonrası CPU�larda bulunurlar ve diğer segment


kaydedicilerinin yetersiz kaldığı durumlarda kullanılırlar.

Özel kaydediciler

IP ve EIP kaydedicileri: IP 16 bitlik DOS programlarının EIP ise 32 bitlik programların


işlenmesi sürecinde, işlenecek olan bir sonraki komutun offset adresini gösterir.

FLAG ve EFLAG kaydedicileri: Flag kaydedicisi 16 Eflag kaydedicisi ise 32 bitten oluşur.
Bildiğiniz gibi mikroişlemci matematiksel işlem yapar, bu kaydedicilerde her işlemden
sonra o işleme özel sonuçları gösterirler. İşlemci durum kaydedicisi olarakta bilinen bu
kaydediciler sonucun sıfır, pozitif veya negatif olduğunu veya işlemin sonucunda elde
üretilip üretilmediği gibi birçok önemli veriyi bitsel olarak programcıya bildirirler.

Index Kaydedicileri

Bu kaydedicilerin E ile başlayanlar 32 bitlik programlarda, diğerleride 16 bitlik


programlarda kullanılır. Hepsi de verilerin offset adreslerini tutmada kullanılır. SP ve BP,
SS kaydedicisi ile birlikte SI ve DI, DS ve ES kaydedicileri ile birlikte hafıza adreslerine
erişmek için kullanılır.

Burada açıklamadığım diğer kaydediciler: X86 ailesindeki işlemcilerde başka


kaydedicilerde mevcuttur. Bunlardan bazılarını programcı kullanamaz bazıları ise
ayrıcalıklı mod, korumalı mod diye adlandırılan özel çalışma modlarında anlam kazanır
hatta bu tür çalışma modlarında yukarıda saydığımız kaydedicilerin birçoğu bambaşka
amaçlar için kullanılırlar.

İşlemciyi kısaca anlatmaya çalıştım, sıra hafızada.

HAFIZA

Şimdi hafızanın fiziksel ve mantıksal yapısını anlatacağım, fiziksel yapısı ile programcı
fazla ilgilenmez hafızanın mantıksal yapısı ise tüm programcılara hemen hemen aynı
görünür. Çok karışık bir konu olmasına karşın basit bir anlatımla bu konuyu kavramanızı
umuyorum.

Hafızanın Fiziksel Yapısı

Şekil 4 - Hafızanı fiziksel yapısı.

Yukarıdaki gibi bir hafıza yapısı, 16 bitlik bir veri yolu bulunan işlemciler için uygundur,
örneğin 8086 işlemcisi için. Şayet veri yolu 32 bitlik ise daha fazla bank gerekir, özetle
data bus�ın (veri yolu) genişliği kullanılacak olan hafıza chip�ini belirler. Hafıza denilince
boyutu 8 bit olan bir dizi hücre ve bu hücrelerin numarası olan adresler akla gelir. Hafıza
chip�leri veri, adres ve kontrol yollarına bağlıdır. Hafızada bulunan veriler hexadecimal
formatta gösterilirler, bu binary formata en uygun sayı sistemidir.

x86 hafızası ne kadar byte olarak adreslense de programcı hafızadan tek bir komut ile 8,
16 veya 32 bitlik bir değer işleyebilir. Böyle bir durumda hafızanın fiziksel yapısını bilmek
işe yarayabilir. Örneğin 0. adresten itibaren 16 bitlik (2 byte veya word) bir değerin
işlemci kaydedicilerine herhangi bir işlem için çekileceğini varsayalım. X86 ailesi
işlemciler, hafızadan verileri düşük değerlikli kısmından itibaren okurlar (little endian). Bu
örnekte hafızaya erişim çift numaralı adresten yapıldığı için ABFFh verisi data bus�a
doğru bir şekilde yerleşecektir ve en hızlı hafıza erişimi bu şekilde yapılır. Birde tek
numaralı bir adresten örneğin 7. adresten okuma yapıldığını varsayalım. Bu durumda
okunması gereken değer 4676h olmasına rağmen data bus�a 7646h verisi yerleştirilir
gibi görünüyor değil mi? Hal böyle olunca işlemci önce Odd Bank�tan daha sonra Even
Bank�tan okuma yapar ve bu byte�ları sıraya koyar, bu tür bir hafıza erişimi daha uzun
zaman alır. Özetle çift numaralı adreslerden 1 byte�tan fazla bir değere erişilirse buna
sıralı erişim (Aligned Memory Access), tek numaralı adreslerden okuma yapılırsa buna
sırasız hafıza erişimi (Unaligned Memory Access) denir. Sıralı hafıza erişimi sırasızdan 2
kat daha hızlı olduğundan programlamada buna dikkat edilmelidir. Bu tür ayarlamalar
assembly dilinde direktiflerle yapılır.

Hafızanın Mantıksal Yapısı:

Bir mikroişlemcinin ne kadarlık bir hafızaya erişebileceği adres yolu ile doğru orantılıdır.
Adres yolu 16 bit olan bir mikroişlemci 216 yani 65536 adet hafıza konumuna erişebilir.
8086 işlemcisi 20 bitlik bir adres yoluna sahiptir ve adresleyebileceği alan 220=1048576
yani 1 MB. a denk gelir. 80286 işlemcisinin adres yolu 24 bitlik�ti ve 224= 16777216
hücreyi, başka bir deyişle 16MB. lık bir hafıza alanını adresleyebilirdir. Günümüzde
kullanılan mikroişlemcilerin adres yolu çoğunlukla 32 bitliktir, buda 4 Milyar küsür hafıza
konumuna denk gelir. Yavaş yavaş 64 bit devri başlıyor, AMD 64 bitlik işlemcilerini çoktan
piyasaya sürdü 264 adet hafıza konumu geleceğin programlarını çalıştırmak için epey
yeterli. Her ne kadar mikroişlemci böyle büyük hafıza alanlarını desteklese de böyle bir
alanı kullanabilmemiz hafızayı nasıl organize edileceğine bağlıdır. Hafıza organizasyonuna
yapılan bu yaklaşıma hafızanın mantıksal yapısı diyebiliriz.

Hafızanın mantıksal yapısı çalışma moduna göre değişiklik gösterir ve çalışma modu
işletim sistemleri tarafından belirlenir. X86 uyumlu tüm PC�ler Real Mod denilen çalışma
modunda açılırlar, daha sonra işletim sistemine göre hafıza farklı bir şekil alabilir. Örneğin
Linux yada Windows işletim sistemi yükleniyorsa hafıza Protected Mod (korumalı mod)
denilen yapıya bürünür. DOS işletim sistemi yükleniyorsa (neydi o günler :)) hafıza real
mod�da kalır. Birde korumalı mod altında DOS programlarını çalıştırmak ve korumalı
mod ile real mod programları arasında geçiş yapmak için windows işletim sisteminin
Virtual Protected Mod�u vardır. Tüm bu modlar tek başına bir makale konusu
olduğundan ve şu an için biraz ağır konular olduğundan fazla ayrıntıya girmiyorum. Şimdi
sadece en basit ve programlarımızda kullanacağımız mod olan Real Mode Adressing yeni
gerçek modda hafıza�yı inceleyeceğiz.

Real mod�da hafıza 64Kb�lık segmentlerden oluşur ve bu segment içindeki her byte�a
erişmek için offset adresleri kullanılır. Bu durumda hafızanın tamamı bir kitap, segmentler
sayfalar ve adreslenebilen her byte ise satır olara düşünülebilir. Protected mod çok daha
karmaşıktır. 80286 ve sonrası işlemciler protected mod�a geçiş yapabilir. Sayfalama
(paging), sanal bellek (virtual memory) ve multitasking (çok görevlilik) korumalı modun
getirdiği büyük avantajlardır.

Mikroişlemci hafızanın herhangi bir konumuna erişirken segment adresini segment


kaydedicilerinden, offset adresini de şayet erişilecek olan bir komut kodu ise IP
kaydedicisinden alır. Erişilecek olan veri (değişken, karakter vs.) ise verinin türüne göre
offset adresini tutacak olan kaydedici değişir, örneğin erişilecek veri stack�ta (yığın
bellekte diyebilirsiniz) ise buradaki verinin offset adresini SP (stack pointer) tutar.

Segment kaydedicileri 16 bitliktir, real mod�da offset adresleri index kaydedicilerinin 16


bitlik kısımlarında (SP,BP,SI,DI) veya 16 bitlik olan IP kaydedicisinde tutulur. Bu durum
xxxx:xxxx formatında gösterilir. Örneğin 3456:76A3 real modda bir hafıza adresini
gösterir, burada 3456 segment adresi iken 76A3 ofset adresidir. 3456:76A3 gibi bir adres
gösteriminde 3456�yı sayfa numarası olarak düşünürsek bu sayfada 0000-FFFF arasında
65536 adet offset adresi (satır) mevcuttur diyebiliriz ve bu konumların her biri 1 byte�a
denk gelir.
Hafızanın gerçek yapısını göz önüne alırsak örneğin 8086 işlemcisi en fazla 1MB. lık
hafızayı adresleyebilir fakat xxxx:xxxx şeklinde bir gösterim 4GB�lık bir alana denk gelir
(FFFF kere FFFF). Hal böyle olunca işlemci real modda çalışırken gerçek adresi bulmak
için bir hesaplama yapar çünkü gerçekte hafıza real mod�da 00000H-FFFFFH arasında
anlamlıdır.

Şekil 5 - Programcı hafızayı adresleri olan byte tablosu olarak düşünür

Real mod�da gerçek adres şu formülle bulunur;

045F:0032 adresi için, segment adresi 4 bit sola kaydırılır veya başka bir deyişle adresin
sonuna 4 adet 0 getirilir (0000)2=(0)16 böylece segment adresi 045F0 olur, sonra buna
offset adresi eklenir ve gerçek adres bulunur. 045F0h+0032h=04622h gerçek hafıza
adresidir.

Gerçek modda bilgisayarınız Pentium 4 bile olsa hafızanın sadece 1MB. lık alanını
adresleyebilirsiniz, ayrıca sadece kaydedicilerin 16 bitlik kısımlarını kullanabilirsiniz. Bu
son model ferrari�nizi otobanda 1. viteste sürmeye benzer.

Şimdi�de hafıza ve kaydedicileri daha iyi anlamak için örnek program hazırlayalım.
TITLE Kaydediciler ve Hafıza (regmem.asm)
;########################################
;# Bu program hafızadaki 2 adet byte�ı toplar ve sonucu #
;# tekrar hafızaya kaydeder. #
;# Son Güncelleme: 05/03/05 #
;# Yazan --> Eren ERENER #
;########################################

.MODEL SMALL
.STACK 32
.DATA

Sayi1 DB 5
Sayi2 DB 6
Sonuc DB ?

.CODE

ANA PROC

MOV AX, @DATA ;Data segment


MOV DS, AX ;ayarlanıyor.

MOV AL, Sayi1 ;AL�ye 5 değeri yüklendi.


ADD AL, Sayi2 ;AL�deki 5 ile 6 toplandı
sonuç tekrar AL�de.
MOV Sonuc, AL ;toplama işlemini sonucu
hafızaya yüklendi.

MOV AH,4CH ;DOS�a


INT 21H ;dönüş

ANA ENDP

END ANA

Bir programın çalışma süreci genel olarak şöyledir, program önce hafızaya yüklenir ve
mikroişlemci ilk komuttan itibaren programı atır-satır icra etmeye başlar. Bu program
hafızaya yüklenirken hafızada mantıksal olarak 3 tane segment oluşturacak 1.si 32
byte�lık stacak segment bölgesi 2.�si 3 byte�lık data segment bölgesi ve 3.sü
kodlarımın kapladığı alan kadar code segment bölgesi. İşlemci kaydedicileride programın
yüklenme sürecinde ilk değerlerini alırlar. Örneğin CS kaydedicisi code segment
bölgesinin segment adresini ve IP kaydediciside bu bölgede bulunan kodların ilkinin offset
adresinin değerini alır. Bu olayları en güzel uygulama yaparak anlayabiliriz, bunun için
sizlere bir video hazırladım. Şimdi kendinize bir kahve hazırlayın ve keyifle bu videoyu
izleyin. Kafanıza takılan bir soru olursa bana mail atabilirsiniz, bu arada olumlu-olumsuz
eleştirilerinizi bekliyorum. Bir sonraki makalede görüşmek üzere hoşça kalın.
x86 Adresleme Modları ve Turbo Debugger

Bu makalede x86 adresleme modlarını anlatmaya çalışacağım, ayrıca sembolik


kodlarımızı, verilerimizi daha rahat görebileceğimiz ve kontrol edebileceğimiz bir program
olan Turbo Debugger�ı inceleyeceleyeceğiz.

Geçen makalemizde x86 kaydedicilerini hafızanın yapısını anlatmaya çalışmıştım. Bu


makaleye kadar çoğunlukla x86 uyumlu sistemlerin yapısı üzerinde durdum. Bundan
sonra yazacağım makaleler daha çok assembly programlama dilini kapsayacak.

ADRESLEME MODLARI

Bildiğiniz gibi programları oluşturan kodlar ve veriler hafızaya yüklendikten sonra işlemci
tarafından satır-satır icra edilirler. Ayrıca CPU tüm giriş çıkış işlemlerini de hafızaya
erişerek yapar. Bazen hafızadan doğrudan bir kod ya da veri alır, işler. Bazen hafızaya bir
veri gönderdiğinizde birde bakmışsınız bu bir yazıcıdan belge olarak çıkmış vs. İşte
bilgisayarın donanım ve yazılım düzeyinde yaptığı bunca çeşitli iş için CPU hafızaya
değişik yollardan erişme ihtiyacı duyar. Sizlerde programlarınızı yazarken CPU�nun
hafızaya nasıl erişeceğini yazdığınız kodlarla belirtmek zorundasınız. Assembly dilinin ilk
basamağı olan adresleme modları da bu konuları kapsıyor.

Her mikroişlemci üreticisi bir mikro işlemci piyasaya sürdüğünde, komut setini ve
adresleme modlarınıda yayınlar. Programcılar da bu belgelere göre programlarını
yazarlar. Intel 8086 işlemcisini piyasaya sürdüğünde oldukça kullanışlı bir dizi adresleme
modu sağladı. Intel�in şu ana kadar ürettiği işlemcilerde bu adresleme modlarını
kullanabilirsiniz. Daha sonraları bir devrim niteliğinde olan 80386 işlemcisi ile ek
adresleme modlarıda geldi. Bu özel adresleme modları sadece 386 ve sonrası işlemcilerde
kullanılabilir. Tabi ki bu ek adresleme modları ile assembly programlama dili daha esnek
bir yapıya bürünmüştü. Bizde bu makalemizde adresleme modlarını 386 öncesi ve sonrası
olarak ikiye ayıracağız.

Şu ana kadar yaptığımız programlarda çok fazla komut örneği görmediniz ama
makalelerimizi takip ettiyseniz MOV komutunu yakından tanımanız lazım. MOV komutu
assembly programlama dilinde en çok kullanılan komutların başında gelir. Çünkü bir
program çalışırken genelde hafızaya yazar yada okur. MOV komutu da bu iş için biçilmiş
kaftandır ve bu kadar çok kullanıldığından dolayı bir çok adresleme modunu destekler,
yani bu komut ile hafızaya çok değişik yollardan erişebilirsiniz. Bu yüzden bu makalede
adresleme modlarını anlatırken MOV komutunu örnek alacağım.

MOV komutunun genel kullanım şekli;

MOV hedef, kaynak

8086 ADRESLEME MODLARI:

8086 Kaydedici Adresleme:

Adından anlaşılacağı gibi kaydediciden kaydediciye yapılan işlemlerde bu adresleme


modları kullanılır. En hızlı adresleme modu�dur, çünkü işlem hafızada değil işlemcinin
içinde gerçekleşir.

Genel amaçlı ve indeks kaydedicilerde kaydedici adresleme modları:


mov ax, bx ; BX teki değeri AX�e kopyalar
mov dl, al ; AL teki değeri DL�ye kopyalar
mov si, dx ; DX teki değeri SI�ya kopyalar
mov sp, bp ; SP deki değeri BP�ye kopyalar
mov dh, cl ; CL deki değeri DH�a kopyalar
mov ax, ax ; Bu da doğru bir kullanımdır!

Kaydedici adreslemede en çok dikkat etmeniz gereken husus hedef ve kaynağın


boyutlarıdır. Örneğin 16 bitlik bir kaydediciden 8 bitlik bir kaydediciye taşıma yapılamaz!

mov al, bx ;yanlış kullanım, derleme anında assembler hata verir.

Küçük boyuttaki kaynaktan büyük boyuttaki hedefe de kopyalama yapılamaz.

mov cx, al ; Yanlış kullanım, AL�ile CX eşit boyutta değil.

Bunlara ek olarak segment kaydedicilerinin kullanımında dikkat edilmesi gereken noktalar


vardır.

1- Segment kaydedicileri arasında bir transfer işlemi ancak genel amaçlı bir kaydedici
vasıtasıyla yapılabilir.

mov ds,cs ; doğru kullanılmayan segment kaydedicisi şeklinde assembler


hata verir!

Bu işi yapmak için,

mov ax,cs
mov ds,ax

komut satırları kullanılabilir.

2- CS ve IP kaydedicilerinin değeri kaydedici adresleme ile değiştirilemez

mov cs, ax ; kaynak genel amaçlı kaydedici olmasına rağmen bu işlem


mümkün değildir!

Segment kaydedicileri programın icrası aşamasında hafızanın segment olarak adlandırılan


bölümlerinin adreslerini tuttuklarından, bu kaydedicileri verilerinizi saklamak veya
taşımak için kullanmanızı tavsiye etmem, kullanılmamalıdırlar. Bunun yerine genel amaçlı
kaydedicileri kullanmanız daha uygun olur.

8086 Hafıza Adresleme Modları

a- Acil Adresleme ( Immediate Addressing )

Herhangi bir genel amaçlı veya indeks kaydedicisine doğrudan bir değer yükleye bilirsiniz.
Yüklenecek olan veri kod segmentten alınacağından bu tür kullanımları şahsen ben pek
tavsiye etmem. İyi bir program organizasyonu için, veriler hafızanın ayrı bir bölümünde
(mesela data segmentte) değişkenler veya sabitler olarak belirtilmelidir.

mov al, 17 ; AL�ye 11h yüklenir.

b- Direkt Adresleme (Displacement Only Addressing )


Acil adreslemenin doğru kullanılmış şeklidir. Bu adreslemede segment:ofset adresi
kullanılarak hafızaya erişilir.

mov al, ds:12 ; ds:000C adresinden 1 byte AL�ye kopyalanır.


mov ds:12, al ; AL�nin içeriği ds:000C adresine kopyalanır.
mov ax, ds:12 ; ds:000C adresinden 2 byte AX�ye kopyalanır.
mov ds:12, ax ; AX�nin içeriği ds:000C adresinden itibaren kopyalanır (2
byte)

Aslında kaynak kod hazırlanırken genelde bu şekilde bir kod yazımı yapılmaz. Değişkenler
ve sabitler sembolik kelimelerle ifade edildiğinden buna gerek yoktur. Ne var ki debugger
programlarında da sembolik değişken isimlerini değil adresleri görürüz. Aşağıdaki kod
parçasını inceleyin;

.data
sayi1 db 5

.code
mov ax, @data
mov ds, ax

mov al, sayi ; 5 değerini al�ye yükler.


mov al, ds:0000 ; aynı işi yapar yani 5�i AL�ye yükler.
; veriler tanımlanırken sayi1 db 5 şeklinde değil de
; sadece db 5 yazılsaydı, bu komutu kullanmak zorunda
kalırdık.

c- Kaydedici Dolaylı Adresleme ( Register Indirect Addressing )

Adının kaydedici olduğuna aldanmayın. Burada operand olarak kullanılan kaydedici köşeli
parantez içine alınır ve bu andan itibaren bir offset adresi oluverir.

mov al, [bx] ; hafızadan AL�ye 1 byte taşınır. Alınacak verinin offset adresi
BX�in değeridir.

mov al, [bx]


mov al, [si]
mov al, [di]
mov al, [bp]

Yukarıdaki 4 örnekte AL�ye kopyalanacak verilerin offset adresleri ilgili kaydedicinin


içindeki değerdir. Segment adresleri ise ilk üçünün DS sonuncusunun SS�dir. BP indeks
kaydedicisi yalnız başına hafıza adreslemede kullanılırsa, daima stack segmentin (SS)
offset adreslerini gösterir. Fakat BP kaydedicisi de dahil olmak üzere bu adresleme
segment kaydedicileri de belirtilerek yapılırsa, o zaman ilgili segment:offset adresine
erişilmiş olunur.

BX=0000, BP=0001, SI=0002, DI=0003 olduğunu varsayalım;

mov al, cs:[bx] ; AL�ye CS:0000�dan kopyalama yapılır


mov al, [bx] ; AL�ye DS:0000�dan kopyalama yapılır
mov al, ds:[bp] ; AL�ye DS:0001�den kopyalama yapılır
mov al, [bp] ; AL�ye SS:0001�den kopyalama yapılır
mov al, ss:[si] ; AL�ye SS:0002�den kopyalama yapılır
mov al, [si] ; AL�ye DS:0002�den kopyalama yapılır
mov al, es:[di] ; AL�ye ES:0003�den kopyalama yapılır
mov al, [di] ; AL�ye DS:0002�dan kopyalama yapılır

d- İndeksli adresleme ( Indexed Addressing )

Kaydedici dolaylı adreslemenin operandına sabit bir değer eklenmiş halidir.

Kullanım şekli;

mov al, disp[bx]


mov al, disp[bp]
mov al, disp[si]
mov al, disp[di]

Not : disp kısaltması İngilizcede displacement kelimesinin karşılığıdır. Buradaki anlamı ise
referans alınan ofset adresidir. Komut setlerinde adresleme modları açıklanırken disp
veya mem kısaltması ile çok sık karşılaşacağınızdan, komutları yazarken İngilizce ifadeler
kullanmak durumunda kaldım.

Mesela BX=2000h olsun, mov dl, 20h[bx] şeklinde bir komut kullanıldığında, DS:2020h
adresindeki 1 byte�lık değer dl�ye kopyalanacaktır. Aynı şekilde BP=3030h olduğunu
varsayalım, mov dh,1020h[bp] gibi bir komut ile, SS:4050h adresindeki 1 byte�lık değer
dh�a kopyalanır.

Bu adresleme modunda da BP segment kaydedicisi daima SS�in ofsetlerini gösterir.


Fakat kaydedici dolaylı adreslemede de olduğu gibi bizzat segment kaydedicisinide
belirterek bu adresleme modunu kullanabiliriz. Bu durumda ofset adresleri komutta
belirtilen segmentin ofseti olur.

mov al, ss:disp[bx] ; BX normalde DS�nin ofsetlerini gösterirken burada


SS�in ofseti olmuş.
mov al, es:disp[bp] ; BP normalde SS�nin ofsetlerini gösterirken burada
ES�in ofseti olmuş.
mov al, cs:disp[si] ; SI normalde DS�nin ofsetlerini gösterirken burada
CS�in ofseti olmuş.
mov al, ss:disp[di] ; DI normalde DS�nin ofsetlerini gösterirken burada
SS�in ofseti olmuş.

e- Taban İndeksli adresleme ( Based Indexed Addressing )

Bu adresleme modu da kaydedici dolaylı adreslemeye çok benzer. Kullanım formatı


aşağıdaki gibidir;

mov al, [bx][si]


mov al, [bx][di]
mov al, [bp][si]
mov al, [bp][di]

BX�in 0500h SI�nın 0360h olduğunu varsayalım,

mov al,[bx][si]

gibi bir komut işlenince AL�ye kopyalanacak veri DS:0860 adresinden alınır.

Aynı şekilde;
BP=1598h DI=1004 ve mov ax,[bp+di] gibi bir komut işleniyorsa; AX, SS:259Ch ve
SS:259Dh adreslerindeki veri ile yüklenir.

SI ve DI kaydedicileri için Intel�in özel komutları vardır, bu yüzden bu kaydediciler


genellikle programlamada indeks değerlerini tutar, arttırır veya azaltırlar.

f- Taban İndeksli artı direkt adresleme (Based Indexed Plus Displacement Addressing)

Bu adresleme modu taban indeksli adreslemeye 8 yada 16 bitlik sabit bir değerin
eklenmiş halidir.

mov al, disp[bx][si]


mov al, disp[bx+di]
mov al, [bp+si+disp]
mov al, [bp][di][disp]

BP = 1000h, BX= 2000h, SI= 0120h, DI = 5 olduğunu varsayalım.

mov al,10h[bx+si] ; AL�ye DS:2130 adresindeki veri yüklenir


mov ch,125h[bp+di] ; CH�a SS:112A adresindeki veri yüklenir
mov bx,cs:2[bx][di] ; CS:2007 adresinden itibaten 2 byte�lık veri yüklenir

TASM ve MASM �ın adresleme modları için esnekliği.

TASM ve MASM assembler�ları indeksli, taban indeksli, ve taban indeksli artı direkt
adresleme için değişik yazım şekillerini desteklerler;

İndeksli adresleme için;

disp[bx] = [bx][disp] = [bx+disp] = [disp][bx] = [disp+bx]

Taban indeksli adresleme için;

[bx][si] = [bx+si] = [si][bx] = [si+bx] ;

Taban İndeksli artı direkt adresleme için;

disp[bx][si] = disp[bx+si] = [disp+bx+si] = [disp+bx][si] = disp[si][bx] =


[disp+si][bx] = [disp+si+bx] = [si+disp+bx] = [bx+disp+si]

Yukarıda yazılan bu 3 adresleme modundaki operandlar aynı işi yaparlar. MASM ve TASM
"[ ]" sembollerine "+" operatörü gibi davranır. ( disp[bx][si] = disp[bx+si] örneğinde
olduğu gibi )

8086 Adresleme Hafıza Adresleme Modlarını Hatırlamak İçin Kolay Bir Yol:

8086 işlemcisi için toplam 17 adet adresleme modu mevcuttur. Bunlar disp, [bx], [bp],
[si], [di], disp[bx], disp[bp], disp[si], disp[di], [bx][si], [bx][di], [bp][si], [bp][di],
disp[bx][si], disp [bx][di], disp[bp][si], and disp[bp][di] adresleme modlarıdır. Aşağıdaki
şekil, bu 17 adresleme modunu ezberlemeniz yerine kolayca hatırlamanıza yardımcı
olacaktır.
Şekil 1 - 8086 adresleme modları için yardımcı şekil.

Bu şekil ile 17 adresleme modunu kolayca görebilirsiniz. Her sütundaki elemanları teker-
teker seçebilirsiniz veya herhangi bir sütunu geçip diğer iki sütunun birleşimi ile doğru bir
adresleme modu yakalayabilirsiniz.

Örnekler;

Bir tek eleman seçin;

disp, [bx], [bp], [si], [di]

3. sütunu yok sayın ve diğer elemanların birleşimini çıkartın

disp[bx], disp[bp]

2. sütunu yok sayın ve diğer elemanların birleşimini çıkartın

disp[si], disp[di]

1. sütunu yok sayın ve diğer elemanların birleşimini çıkartın

[bx][si], [bx][di], [bp][si], [bp][di]

Son olarak tüm sütunların birleşimi ile aşağıdaki geçerli adresleme modlarını kolayca
çıkartabilirsiniz.

disp[bx][si], disp [bx][di], disp[bp][si], disp[bp][di].

Not:Hafıza adresleme modlarında şayet hesaplanan etkin adres 0FFFFh değerinden büyük
olursa, CPU bu hesaplama sonucu oluşan taşmayı göz ardı eder ve FFFFh�e ekleneni 0
dan itibaren ekler. Örneğin BX=10h olsun ve mov al,0FFFFh[bx] komutu işlensin. Bu
durunda AL kaydedicisine ds:1000Fh adresindeki veri değil ds:000Fh adresindeki veri
yüklenir. (FFFFh+1h=0000, FFFFh+2h=0001,�.. FFFFh+16h=000Fh)

Sonuç:

8086 adresleme modlarını 2 bölümde inceledik, ilk olarak kaydedici adresleme modlarını
anlatmaya çalıştım. Kaydedici adresleme de segment kaydedicilerini kendi amaçları
dışında kullanmanız tavsiye edilmez, bu iş için yeterince genel amaçlı ve indeks
kaydedicisi var zaten.

Karışık olan kısım ise hafıza adresleme modlarıdır. Hafıza adresleme modlarında belirtilen
operand daima bir offset adresini işaret eder, buna etkin adres hesaplama da denir.
Hafıza adresleme modlarını unutmamanız için sizlere kolay bir yol göstermeye çalıştım.
Adresleme modlarının isimlerinden ziyade kullanım formatları önemlidir. Her komut her
adresleme modunu desteklemez. Hangi komutun hangi adresleme modunda kullanılacağı
komut setlerinden faydalanılarak bulunur.

Bugüne kadar gerek internetten gerekse Türkçe assembly kitaplarından yaptığım


araştırmalarda, Türkçe olarak yayınlanmış ayrıntılı bir komut seti bulamadım bu yüzden
İngilizce komut setlerinden faydalanıyorum.

�8086 instruction set� veya �x86 instruction set� anahtar kelimelerini internetten
aratacak olursanız karşınıza yığınla komut seti gelecektir. Genelde komut seti kılavuzları
aşağıdaki tablo gibidirler.

MOV - Move Byte or Word


Usage: MOV dest,src

Size
Operands Clocks
Bytes
286 386 486
reg, reg 2 2 1 2
mem, reg 3 2 1 2-4
reg, mem 5 4 1 2-4
mem,
3 2 1 3-6
imm
reg, imm 2 2 1 2-3
segreg,
2 2 3 2
reg16
segreg,
5 5 9 2-4
mem16
... ... ... ... ...
... ... ... ... ...
... ... ... ... ...
Tablo 1 - MOV komutu için örnek tablo

Bu tabloda birinci sütun MOV komutu ile kullanılacak operandın hangi adresleme
modlarını desteklediğini gösteriyor. Örneğin mem,imm (displacement,immediate) acil
adresleme yi desteklediğini gösterir. reg,reg (register,register) kaydedici adreslemenin
yapılabileceğini, segreg,reg16 (segment register, register 16 bit ) bir segment
kaydedicisine 16 bitlik bir kaydediciden kopyalama işleminin yapılabileceğini gösterir.

�Clocks� sütunu bu komutların işlemci tarafından kaç adımda işlendiğini (işlemcinin


tipine ve hızına göre bu zaman birimi değişebilir), �size bytes� sütunu ise hafızada
makine kodlarının byte olarak kapladığı alanı gösterir. Çoğu zaman assembly
programcıları programlarının, işlenme sürecindeki nano saniyeleri ve hafızada kapladığı
alanını byte düzeyinde önemi yoksa bu kısımlarla ilgilenmezler. Yani 1. derecede
�operands� sütunu önemlidir ve ilgili komutu bu sütunda belirtilen formatların dışında
kullanamazsınız.

Son olarak x86 uyumlu hiçbir işlemci mem,mem şeklinde yani hafızadan hafızaya
doğrudan bir adresleme modunu desteklemez, aslında bu güne kadar bunu destekleyen
hiç bir işlemci görmedim, zaten böyle bir adresleme modunun desteklenmesi durumunda
o sistem için yapılan programların çökme olasılığı çok büyüktür.

80386 ADRESLEME MODLARI


80386 Kaydedici Adresleme (80386 Register Addressing)

80386 den itibaren, günümüzde kullandığımız Pentium işlemcilerin 32 bitlik kaydedicileri


vardır. 8086 adresleme modlarına ek olarak bu işlemcilere de kaydedici adresleme
yaparken bu 32 bitlik kaydedicileri de kullanabilirsiniz. Bu kaydediciler geçen
makalemizde bahsettiğimiz;

eax, ebx, ecx, edx, esi, edi, ebp, ve esp kaydedicileridir.

kullanım formatı;

mov eax, ebx

Not: Şayet programınızda 32 bitlik kaydedicileri kullanacaksanız kaynak dosyası


hazırlarken .386 talimatını kullanmanız gerekir.

80386 Hafıza Adresleme (80386 Memory Addressing)

a- 80386 Kaydedici Dolaylı Adresleme ( Register Indirect Addressing )

16 bitlik Gerçek moda (Real Mode) 80386 ve üstü işlemcilerde programlama yapmanız
offset adreslerinin 32 bit olacağı anlamına gelmez. Segmentlerin her zaman gerçek moda
64Kb. olduğunu unutmamak gerekir, bu yüzden offset adresleri gerçek modda 0...0FFFFh
arasını gösterir. Yani 64Kb.�ı geçecek ofset adreslerini gerçek moda değil ancak
korumalı moda (Protected mode) kullanabilirsiniz. Fakat gerçek moda 32 bitlik
kaydedicileri hesaplamalarda kullanmanızda bir engel yoktur. 80386 kaydedici dolaylı
adreslemede kullanabileceğiniz tüm geçerli adresleme formatı aşağıdaki gibidir.

mov al, [eax] ; DS in ofsetleri için


mov al, [ebx] ; DS in ofsetleri için
mov al, [ecx] ; DS in ofsetleri için
mov al, [edx] ; DS in ofsetleri için
mov al, [esi] ; DS in ofsetleri için
mov al, [edi] ; DS in ofsetleri için
mov al, [ebp] ; SS in ofsetleri için
mov al, [esp] ; SS in ofsetleri için

b- 80386 İndeksli, Taban/İndeksli, ve Taban/İndeksli/Direkt Adresleme modları ( 80386


Indexed, Base/Indexed, and Base/Indexed/Disp Addressing )

80386 İndeksli adresleme modu 32 bitlik bir kaydedici ile sabit bir değerin birleşiminden
meydana gelir. Taban indeksli adresleme modu iki adet 32 bitlik kaydedicinin
birleşiminden ve taban indeksli artı direkt adresleme modu ise bir sabit değer ile iki tane
32 bitlik kaydedicinin birleşiminden meydana gelir. Hiçbir zaman unutmamalısınız ki bu
adresleme modları ne kadar 32 bitlik olsa da, şayet 16 bitlik gerçek moda kullanılırlarsa
erişebilecekleri ofset adresleri 16 bit�i geçemez.

80386 taban indeksli hafıza adresleme modlarında taban adres ve indeks adresi diye iki
tane terim kullanılır. Taban adresini ilk operand iken indeks adresini son operand
gösterir. Bu kural kaydedici ismi ne olursa olsun geçerlidir. Fakat 8086 indeksli veya
taban indeksli hafıza adresleme modlarında, taban adresini sadece BX veya sabit bir
değer gösterebilirken, indeks adreslerinide SI, DI, BP ve BX gösterebiliyordu. 80386 ve
sonrası işlemcilerde bulunan bu esneklik şüphesiz ki programcıların işini kolaylaştırır.

Aşağıda 80386 indeksli hafıza adresleme modları için birkaç örnek görülüyor;
mov al, disp[eax] ;İndeksli adresleme
mov al, [ebx+disp] ;modları.
mov al, [ecx][disp]
mov al, disp[edx]
mov al, disp[esi]
mov al, disp[edi]
mov al, disp[ebp] ;SS in ofsetleri için
mov al, disp[esp] ;için.

Aşağıdaki örneklerde ise 80386 taban indeksli adresleme modları için çeşitli örnekler
görünüyor. Bu örneklerde ilk kaydedici taban adresini gösterirken, ikinci kaydedici ise
indeks adresini gösterir. Tabi ki sonuçta bu iki kaydedicinin içindeki değerler toplanarak
etkin olan hafıza adresini işaret edecekler. Kaydedicilerin 32 bitlik olduğunu düşünecek
olursanız 4GB�lık hafızanın istediğiniz bir konumuna erişebilineceğini de görebilirsiniz
(tabi ki korumalı modda). Burada taban adresi olarak esp ve ebp kullanıldığında SS�deki
bir ofset adresini göstereceğini unutmayın. Bununla birlikte indeks adreslerini gösteren
kaydedicilerin türü verinin hangi segmentten alınacağına etkisi olmaz.

mov al, [eax][ebx] ;Taban indeksli adresleme


mov al, [ebx+ebx] ;modları.
mov al, [ecx][edx]
mov al, [edx][ebp] ;DS deki ofsetleri gösterirler.
mov al, [esi][edi]
mov al, [edi][esi]
mov al, [ebp+ebx] ;SS deki ofsetleri gösterirler.
mov al, [esp][ecx] ;SS deki ofsetleri gösterirler.

Yukarıdaki adresleme modlarına sabit bir değer eklerseniz 80386 taban indeksli artı
direkt adresleme yapmış olursunuz.

mov al, disp[eax][ebx] ;Taban indeksli artı direkt adresleme


mov al, disp[ebx+ebx] ;modları.
mov al, [ecx+edx+disp]
mov al, disp[edx+ebp] ;DS deki ofsetleri gösterirler.
mov al, [esi][edi][disp]
mov al, [edi][disp][esi]
mov al, disp[ebp+ebx] ;SS deki ofsetleri gösterirler.
mov al, [esp+ecx][disp] ;SS deki ofsetleri gösterirler.

80386 indeksli adresleme modlarında sadece tek bir kısıtlama söz konusudur; esp
kaydedicisini indeks kaydedicisi olarak kullanamazsınız ama esp nin taban kaydedicisi
olarak kullanılmasının bir kısıtlaması yoktur.

c- 80386 Ölçekli İndeksli Adresleme Modu (80386 Scaled Indexed Addressing Modes)

Programcılıkla az çok uğraşan arkadaşlar diziler konusu bilirler. Yukarıda anlattığım 3


adresleme modu ile dizilerin elemanlarına rahatça erişebilirsiniz. Fakat özellikle dizi
işlemler için bir adresleme modu arıyorsanız 80386 ölçekli indeksli adresleme modunu
kullanmak daha akıllıca olur. Bu adresleme modu ile indeks kaydedicisini 1, 2, 4 veya 8
ile çarparak dizilerin elemanlarına erişmede daha esnek bir yapı sağlar.

Kullanımı;

disp[index*n]
[base][index*n]
veya

disp[base][index*n]

"base" ve "index" 80386�nın herhangi bir genel amaçlı kaydedicisi olabilir. "n" ise 1, 2, 4
ve 8 değerlerini alabilir.

ebx = 1000h ve esi = 4, için aşağıdaki örnekleri inceleyelim;

mov al,8[ebx][esi*4] ;ds:1018h daki veriyi dan AL�ye kopyalar.


mov al,1000h[ebx][ebx*2] ;ds:4000h daki veriyi dan AL�ye
kopyalar.
mov al,1000h[esi*8] ;ds:1020h deki veriyi dan AL�ye kopyalar.

80386 İndeksli, Taban/İndeksli, ve Taban/İndeksli/Direkt Adresleme modlarını da 80386


Ölçekli İndeksli Adresleme Modunun n değerinin 1 olduğu adresleme modları olarak
düşünebilirsiniz

mov al, 2[ebx][esi*1] = mov al, 2[ebx][esi]


mov al, [ebx][esi*1] = mov al, [ebx][esi]
mov al, 2[esi*1] = mov al, 2[esi]

MASM ve TASM 80386�nın tüm bu hafıza adresleme modlarının yazımında değişik


varyasyonları kabul eder. Aşağıdaki tüm operandlar aynı işi yaparlar.

disp[bx][si*2], [bx+disp][si*2], [bx+si*2+disp], [si*2+bx][disp], disp[si*2][bx],


[si*2+disp][bx], [disp+bx][si*2]

Sonuç:

80386 adresleme modları 8086 adresleme modlarına nazaran programcıya daha çok
olanak sağladığından daha çok tercih edilirler. Benim şahsi görüşüm X86 uyumlu PC�ler
gerçek gücünü 80386 işlemcisi ile birlikte gelen bu olanaklardan almışlardır. Zaten CPU
tarihinde 80386 bir devrin kapanıp diğer bir devrin başladığı nokta olarak kabul edilir. Ne
var ki 8086 adresleme modları 80386 adresleme modlarına nazaran daha hızlı çalışırlar.
Bu yüzden söz konusu olan hız ise (mikro saniyelerden bahsediyoruz ki bunlar bazen
birleşip dakikalar oluyor) adresleme modu seçilirken çok dikkat edilmesi gerekir.

80386 taban indeksli ve taban indeksli artı direkt adresleme modları, ölçeği 1 olan (n=1)
80386 Ölçekli İndeksli Adresleme Modu olarak düşünülebilir ve bu hafıza adresleme
modlarında gösterilen ilk kaydedici taban ikinci kaydedici indeks adresini gösterir. Taban
adresini gösteren kaydedici ebp veya esp ise bu adresleme SS�deki bir ofset adresine
diğer kaydediciler taban adresi olarak seçildiğinde DS deki bir ofset adreslenmiş olunur.
İlk yazılan kaydedici ölçekli olarak gösterilirse ("*n") bu bir taban adresi olmaktan çıkar
bir indeks adresi olur. Ayrıca operanda segment kaydedicisini de göstererek adresleme
modunu zorlayabilirsiniz.

[ebx][ebp] ;DS�nin ofsetini gösterir.


[ebp][ebx] ;SS�nin ofsetini gösterir.
[ebp*1][ebx] ;DS�nin ofsetini gösterir.
[ebx][ebp*1] ;DS�nin ofsetini gösterir.
[ebp][ebx*1] ;SS�nin ofsetini gösterir.
[ebx*1][ebp] ;SS�nin ofsetini gösterir.
es:[ebx][ebp*1] ;Zorlanmış,ES�nin ofsetini gösterir.

Şimdi sıra uygulamada. Bu uygulama ile hem adresleme modlarını hem de Turbo
Debugger programının kullanımını açıklamaya çalışacağım.

TITLE Adresleme Modları (admod.asm)


;########################################
;# Bu program 8086 ve 80386 adresleme modlarının #
;# iyi kavranması için yazılmıştır. #
;# Son Güncelleme: 17/04/05 #
;# Yazan --> Eren ERENER #
;########################################

.MODEL SMALL
.STACK 32
.DATA

VerilerByte DB 5h, 17h, 8Dh, 0AFh


VerilerWord DW 1234h, 7h, 0C01Dh
VerilerDoubleWord DD 3DF0178Ah, 11223344h,
12345678h

.CODE

ANA PROC

MOV AX, @DATA ; Data segment


MOV DS, AX ; ayarlanıyor.

MOV AX, 5566h ; Acil adresleme.


MOV BX, AX ; Kaydedici adresleme.

;----------------------------------------
;8086 Hafıza Adresleme Modlarına Örnekler
;----------------------------------------

MOV DX, DS:0000h ; Direkt adresleme.

MOV BX, 0000h


MOV AL, [BX] ; Kaydedici dolaylı
adresleme, DS:0000h adresindeki 5h değeri AL�ye kopyalanır.

MOV AL, 3[BX] ; İndeksli adresleme,


DS:0003h adresindeki AFh AL�ye yüklenir
MOV AL, 3[VerilerByte] ; Yukarıdaki ile aynı işi
yapar,
; zaten assembler VerilerByte
değişken ismini ds:0000h olarak çevirecektir.

MOV SI, 0 ; SI = 0 oldu.


MOV AL, [BX][SI] ; zaten BX=0�dı, şu anda
AL�ye kopyalanacak değer DS:0000h�daki 05h�tır.
INC SI ; SI�yı 1 arttırdık,
MOV AL, [BX][SI] ; şimdi AL�ye
kopyalanacak değer DS:0001h�daki 17h�tır.
INC SI ; SI�yı,
INC SI ; 2 arttırdık, şimdi SI=3
oldu.
MOV AL, [BX][SI] ; şimdi AL�ye
kopyalanacak değer DS:0003h�daki AFh�tır.
; Burada BX taban SI indeks
kaydedicisi oldu.
MOV AL, [BP][SI] ; DS değil SS �in
offsetlerini adresler, şu anda Stack Segment�in içinde ne
olduğunu bilmiyoruz!!!

;Taban indeksli artı direkt adresleme


MOV SI, 0 ; SI=0 oldu.
MOV AX, 4[bx+si] ; BX�i
değiştirmediğimizden hala sıfır ve AX�e getirilecek 2 byte�lık
veri ds:0004h adresinden alınacak,
; çünkü 4+0+0 = 4�tür. Bu
adres VerilerWord değişkenin adresidir ve bu adreste 1234h
vardır.
INC SI ; SI,
INC SI ; 2 kere arttırılıyor ve,
MOV AX, 4[bx+si] ; VerilerWord dizisinin 2.
elemanına erişiliyor.
; çünkü 4+0+2 = 6�dır. Bu
adreste 0007h vardır.

;----------------------------------------
;80386 Hafıza Adresleme Modlarına Örnekler
;----------------------------------------

.386 ; 80386 adresleme


modlarını ve 32 bitlik kaydedicileri kullanmak için bu direktifin
yazılması gerekir!!!

MOV ESI, 0 ; İndeks�i 1 olarak


ayarlandı.
SUB EBX, EBX ; ebx = 0 yapıldı. SUB
(Subtract) yani çıkartma komutu,
; burada "MOV ebx, 0" ile
aynı işi fakat daha hızlı yapar.
MOV AX, 4[ebx][esi*2] ; 80386 ölçekli indeksli
adresleme modu ile VerilerWord dizisinin 1. elemanı (1234h)
AX�e kopyalandı.
INC ESI
MOV AX, 4[ebx][esi*2] ; 80386 ölçekli indeksli
adresleme modu ile VerilerWord dizisinin 2. elemanı (0007h)
AX�e kopyalandı.

LEA EAX, VerilerDoubleWord ; LEA komutu (Load


Effective Address) VerilerDoubleWord dizisinin başlangıç adresini
EAX�e kopyalıyor,
; 80386 kaydedici adresleme
SUB EBX, EBX ; EBX = 0 oldu.

; 80386 taban indeksli adresleme.


MOV ECX, [EBX][EAX] ; ve bu dizideki ilk
eleman (3DF0178Ah) ECX�e kopyalanıyor.

MOV AH,4CH ;DOS�a


INT 21H ;dönüş

ANA ENDP

END ANA

Yukarıdaki program sadece 8086 ve 80386 adresleme modlarına örnek olması için
hazırlanmıştır. Hafızadaki veriler nasıl erişilir sorusuna yanıt arayanların bu programı
dikkatle ve satır-satır incelemesini tavsiye ederim.

Sıra bu programı derledikten sonra Turbo Debugger ile açıp hafızada ve işlemcide hangi
olayların olduğunu incelemeye geldi. Turbo Debugger programının 16 ve 32 bitlik hafızayı
incelemek için iki ayrı versiyonu var şu anda gerçek modda çalışan programlar
hazırladığımızdan dolayı 16 bitlik sürümünü kullanacağız. Programı buradan
indirebilirsiniz. İndireceğiniz bu dosya Turbo Debugger, Turbo Assembler�ı, Turbo Linker
paketini içermekte. Ayrıca bu pakete, içinizde belki hala DOS veya Windows 95 kullanan
vardır diye bir mouse programı ekledim. Şayet durum böyleyse önce konsola "mouse"
yazdıktan sonra Turbo Debugger�ı çalıştırın.

Turbo Debugger Microsoft�un debug�ı ile aynı işi yapar fakat daha kullanışlı bir kullanıcı
ara yüzüne sahiptir. Turbo Debugger ile program dosyalarınızı açtıktan sonra bir çok
işlem yapabilirsiniz.

Not: Turbo Debugger�ı pencere modunda çalıştırırsanız GDI (Graphics Device Interface)
kaynaklarını sömürürcesine kullandığından bilgisayarınız yavaşlayabilir. Bu yüzden tam
ekran modunda başlatmanızı öneririm. Programı pencere modunda başlattıktan sonra
konsol penceresinin ayarlarından tam ekran yapmanızın da bir faydası olmaz. Bu yüzden
önce komut istemini açın, sonra tam ekran yapın ve Turbo Debugger�i çalıştırın.

Turbo Debugger kullanımına ilişkin birkaç ipucu:

İlk olarak konsola "td" yazarak programı çalıştırın. Turbo Debugger�ın ekranına
ulaşacaksınız.
Şekil 2 - Turbo Debugger ekranı.

Turbo Debugger ekranı 5 bölümden oluşuyor. Şekildeki 1, 2 ve 3 nolu kısımlar hafızayı


incelemek için kullanılırlar. 4. kısım mikroişlemci kaydedicilerini ve 5. kısımdada yine
mikroişlemci kaydedicilerinden olan flag register�ı bitsel olarak görebilirsiniz. Genellikle
1. bölge programı oluşturan kodları, 2. bölgede verilerin incelenmesi için kullanılır. 3.
bölgeyi yığın hafızayı gözlemlemek için kullanabilirsiniz.

Turbo Debugger açıldıktan sonra "File" menüsünden derlediğiniz program dosyasını açıp
kodlarını inceleyebilirsiniz. Program dosyanızı açtıktan sonra "Program has no symbol
table" diye bir mesaj gelebilir. Bu mesaj penceresinde "OK" e tıklayıp çalışmaya
başlayabilirsiniz.

Şekil 3 - Turbo Debugger ile dosya açma.


Ayrıca menülerden sırasıyla View/Another/File seçeneklerini tıklayarak başka bir dosyayı
diğer bir pencerede açabilirsiniz. (Mesela kaynak dosyasını)

Şekil 4 - Turbo Debugger ile aynı ekranda kaynak dosyanızı da görebilirsiniz.

Şayet bu pencerede yazı yerine hex kodları görürseniz, pencerenin üzerine sağ tıklayıp
açılan menüden "Display As" ı seçin.

Programı çalıştırma seçeneklerini "Run" menüsünden seçebilirsiniz. F8 tuşu programı


adım adım işletmek için kullanılır, F7 ise adım-adım program işlenirken döngüleri
incelemenizi sağlar. Program adım-adım işlenirken mavi bir çubuk o anda hangi komutun
işleneceğini gösterir. Aynı ekranda işlemcinin kaydedicilerinin nasıl değiştiğini
gözlemleyebilirsiniz. Değişikliğe uğrayan kaydediciler beyaz görünür. Programın tekrar
çalıştırmak için "Run" menüsünden "Program Reset" seçeneğini seçebilirsiniz.
Şekil 2 - Program adım-adım işleniyor.

Verilerinizi görmek için 2. pencereye sağ tıklayıp "goto" yu seçin. Açılan pencereye
görmek istediğiniz adresi yazın.

Şekil 2 - Turbo Debugger ile hafıza pencerelerini kullanma.

Yalnız .exe tipindeki program dosyalarında verilerinizi görebilmek için mov ax,@data ve
mov ds,ax satırlarının işlenmiş olması gerekir.

Programınız satır-satır işlenme sürecinde, adresleme yapılan bir komutun işlenmesi


anında kod penceresinin sağ üst köşesinde hafızanın hangi bölgesine erişildiği görülebilir.
Bu makalemizdeki programı incelerken bu özelliği sıkça kullanmanız gerekecek.
Şekil 2 - Turbo Debugger ile adresleme modlarının işlevleri rahatça görülebilir.

Mesela yukarıdaki şekilde mov al, [BX] komutu işleniyor ve kaydedici dolaylı hafıza
adresleme yapılıyor. Burada AL kaydedicisine kopyalanacak olan veriyi "ds:0000 = 05"
şeklinde görebilirsiniz.

32 bitlik kaydedicileri görmek içinse kaydedicileri gösteren kısıma (4. kısım) sağ tıklayıp
"Registers 32 bit" i seçebilirsiniz.

Turbo Debugger�ın daha bir çok özelliği var ama şu anda bizlere bu kadarı yeter. Şimdi
sizler Turbo Debugger ile bu makaledeki programı inceleyerek pratik yapın, ayrıca bu
güne kadar yazdığımız programları da Turbo Debugger ile inceleyerek programı
kullanmasını daha iyi kavrayın.

Bir sonraki makalemizde veri tipleri ve x86 komutlarını inceleyeceğiz. Daha sonra
prosedür ve makrolarıda gördükten sonra nihayet kendi programlarınızı yazabilir seviyeye
geleceksiniz. Genelde sizlerden makalelerin daha sık aralıklarla yayınlanması ile ilgili
mailler alıyorum. Fakat bir makale hazırlamak en az 1 haftamı alıyor ve üniversitedeki
derslerden ve işlerden dolayı çok az vaktim oluyor. Bu yüzden makalelerimin yayınlanma
aralığı birden 20 güne sıçrayabiliyor. Hal böyleyken sizlerin arayı soğutmamak için başka
kaynaklardan faydalanmasını tavsiye ediyorum. Bu şekilde çalışan bazı arkadaşlar
internetten kaynak kod bulup, derleyip takıldıkları yerlerde bana mail atıyorlar, bunlara
cevap vermesi daha az zamanımı alıyor ve sizler içinde çok faydalı olacağından eminim.
Şayet İngilizce okuduğunuzu anlıyorsanız sizlere Art of Assembly�yi tavsiye ederim.
İnternetten okuyabileceğiniz bedava bir kitap. Mesela bu makaleyi yazarken bu
kaynaktan çok yararlandım ve sizlere de tavsiye ederim.

Evet yine geldi ayrılık vakti. Sizlere bol assembly�li günler dileyerek huzurlarınızdan
ayrılıyor ve bir sonraki makalede görüşmek dileğiyle esen kalın diyorum. (TV programı
sunucusu olabilirdim aslında :))
X86 Assembly Dilinde Değişken Bildirimi – 1

Daha önce üst seviye programlama dilleri ile uğraştıysanız değişkenler hakkında bilginiz
muhakkak vardır. x86 assembly dilinde de programcılar programlarında kullanacakları
bazı verileri, değişken olarak tanımlarlar. Aslında bunlar byte yada bytelardan oluşan
dizilerdir. Hafızadaki bu veriler, programınızdaki kullanım şekline göre değişik haller
alabilir.

DEĞİŞKENLERİ DEKLARE ETMEK

Örneğin 2 sayıyı toplayan program yazmayı düşünelim. Bunu 2 değişik şekilde yapabiliriz.
Birincisi acil adresleme modu ile;

MOV AL,5
ADD AL,4

Bu örnekte hiçbir değişken tanımlanmamıştır ve bu program parçası sadece 5 ile 4 ü


toplar ve sonucu işlemcinin içindeki AL kaydedicisinde saklar. Tabi bu programın ne kadar
kullanışlı olduğunu artık siz tahmin edin. Oysa bu sayıları birer değişken olarak
tanımlasaydık daha esnek bir program hazırlamış olurduk.

.DATA
sayi1 DB 5
sayi2 DB 4
toplam DB ?
.
.
.
.CODE
.
.
MOV AL,sayi1
ADD AL,sayi2
MOV toplam,AL

Yukarıdaki program parçasında 3 adet bir byte�lık değişken tanımlanmıştır. Bunlar sayi1,
sayi2 ve toplam değişkenleridir. Aslında bu yazdığımız satırlar assembler�a bir demeç
şeklindedir. Assembler programımızı derlerken, hafızada bizim kullanmamız için 3
byte�lık alan ayıracak ve bu alanlardan birine 5 diğerine 4 değerlerini yerleştirecektir.
Toplam değişkeninin değeri program çalıştığında önemli olacağından dolayı, onun değeri
henüz atanmamış fakat şundan da emin olun ki o adreste muhakkak bir değer mevcuttur.

Gördüğünüz gibi değişkenlerini isimleri, türleri ve değerleri mevcut. Değişken ismi (sayi1
gibi) aslında bir offset adresidir ve kullanımı seçimliktir. Şayet değişken tanımlarken isim
kullanmazsanız assembler yine hafızada değişkenin türüne göre yer ayırır. Fakat böyle bir
durumda değişkeni programınızın içinde kullanmak için adresini (ds:0000 gibi) bilmeniz
gerekir. Çok sayıda değişken tanımlarken bu adresleri hesaplamak güç olacağından dolayı
değişkenlere isim vermemek pek tavsiye edilmez. Değişkenin türü ise boyutu ile alaklıdır,
örneğin DB ile değişkenimizin 1 byte�lık olacağını belirtiyoruz. Son olarak değişkenin
değeri tabi ki bir rakam yada sayı olmak durumunda. Şayet başlangıçta değer önemli
değilse ? operatörünü kullanabilirsiniz.
Kaynak kodda değişkenleri bu şekilde yazarak assemblera hafızada yer tahsis etmesini
söylemiş, bir başka deyişle değişken deklare etmiş oluyoruz.

İşaret Karmaşası

1 byte�lık bir hafıza alanına ne sığar? Toplam 256 adet farklı değer 1 byte ile
tanımlanabilir. Bunlar onluk düzende 0-255 arası sayılar veya -128�den 127 ye kadar
olan işaretli sayılar olabilir. Kafanız karışmasın bu değerleri aslında binary olarak
düşünmek lazım, çünkü 0...255 ve -128...127 arası sayılar toplam 512 adet ediyor.
Aslında, bunlar sadece 1�lerin ve 0�ların kombinasyonlarıdır. Yani 1 byte 0000 00002 ile
1111 11112 arasında değişen kombinasyonlardan oluşur.

Günlük hayatımızda işaretli bir sayıyı tanımlamak ne kadarda kolay değil mi? Başına -
işaretini getirdiğimiz zaman işlem tamamdır. Fakat bilgisayarlarda 1 ve 0 dan başka
kullanacağınız bir değer yok. İşaretli veya işaretsiz sayılar, harfler, metinler, resimler,
mp3�ler ve Tomb Raider :) tamamen 1 ve 0�lardan oluşur. İşte bu 1 ve 0�ları program
içinde yorumlamak önemlidir, ancak o zaman anlam kazanırlar.

Aslında işlemci (CPU) sadece matematiksel, mantıksal ve veri taşıma işlemleri yapar. Bu
bağlamda bizim için alt seviyede önemli olan 1 ve 0�lardır. O zaman işaretli ve işaretsiz
sayılar neye göre belirlenecek? Boyutlar ve işlemci durum kaydedicisi (flag register)
burada imdadımıza yetişiyor. Örneğin 1111 1111 değeri onluk düzende 255�mi yoksa -
1�mi.

255�tir, çünkü 1111 11112 = 20+21+22+23+24+25+26+27 = 255

-1�dir, çünkü 1 ile toplandığında 1 byte�lık alanda 0000 00002 değeri elde edilir ve elde
biti oluşur.

Şekil 1 - 1 byte�lık bir değer 1 byte�lık başka bir değerle toplanınca sonuç 1 byte�lık olmak zorundadır.

1 byte�lık bir değer 1 byte�lık başka bir değerle toplanınca sonuç bir byte�lık olmak
zorundadır, çünkü ayrılan fiziksel alan bu kadardır. Bu işlemde artan 1 bitlik değer ilkokul
matematiğinden bildiğimiz elde�dir ve bu elde işlemci durum kaydedicisinin C (carry)
bitinde saklanır. Daha önce işlemci durum kaydedicisinin bitsel olarak önemli olduğundan
bahsetmiştim.

Bu kargaşa tamamen boyutların sınırlı olmasından kaynaklanıyor. Ayrıca bilgisayarın bu


hesap mantığı ile pozitif bir sayı bir başka pozitif sayı ile toplandığında negatif bir değer
de elde edilebilir. Bunu tam tersi de olabilir. Biz bunları ilerleyen makalelerimizde, işlemci
durum kaydedicisini anlatırken işaret ve boyut taşması olarak inceleyeceğiz. Şimdilik siz
matematiksel hesaplamalar yaparken alanlarını kısıtlı olduğunu göz ardı etmeyin. Yani 2
sayıyı toplayan program yerine, sonuç olarak maksimum 255 değerini gösteren program
demek şu anda daha doğru olur :)

BASİT DEĞİŞKENLER
Byte Değişkenleri

Bir byte boyutunda değerleri deklare etmek istiyorsak genellikle DB direktifini


kullanıyoruz. DB İngilizce�de Define Byte yani byte tanımla anlamına gelir. Bu direktif ile
hafızada 1 byte�lık bir alan tahsis eder ve bu alana adresi ile erişebilirsiniz. MASM ile DB
direktifi yerine byte yada sbyte direktiflerini de kullanabilirsiniz, fakat sonuçta
assembler�ın yapacağı iş aynıdır.

i db 0
j byte 255 ;MASM için alternatif direktif
k sbyte -1 ;MASM için alternatif direktif

Yukarıdaki üç tanımlama farklı gibi gözükse de assembly dili yazımı değişkenleri


tanımlarken boyut dışında bir sınır koymaz. Örneğin k değişkenine sonradan "mov k,
125" komutu ile pozitif bir değer atayabiliriz. Yapılamayacak olan ise "mov i, 300" veya
"mov j, -150" gibi 1 byte�a sığmayacak değerleri bu değişkenlere atamaktır.

DB direktifi ile;

Ayrılan Hafıza Alanı : 1 Byte


Tanımlanabilecek Değer Aralığı: -128...255

Word Değişkenler

DW 8086�dan 80286 işlemcisine kadar offset adresleri, işaretli ve işaretsiz tamsayılar


tanımlamak için kullanılan en geniş veri tipiydi.

-32768...65535 arasındaki tam sayları DW direktifi ile deklare edebiliriz. MASM için word
ve sword direktifleri de aynı işi görür. Word değişkenler hafızada 16 bit yani 2 byte�lık
alan kaplarlar.

sayi1 dw ?
sayi2 dw ?
isayi1 dw ?
sayi_0 dw 0
sayi_eksi dw -1
sayi_enbuyuk dw 65535
pointer dw sayi1

Özetle DW direktifi ile;

Ayrılan Hafıza Alanı : 2 Byte


Tanımlanabilecek Değer Aralığı: -32768...65535

Double Word Değişkenler

4 byte�lık değişkenler tanımlamak için kullanılır. DD direktifi ile bu türdeki değişkeleri


tanımlayabilirsiniz. MASM ile dword, sdword direktiflerini de kullanabilirsiniz.

sayi1 dd ?
sayi2 dd ?
isayi1 dd ?
sayi_0 dd 0
sayi_eksi dd -1
sayi_enbuyuk dd 4000000000
pointer dd sayi2

DD direktifi ile;

Ayrılan Hafıza Alanı : 4 Byte


Tanımlanabilecek Değer Aralığı: -2,147,483,648...4,294,967,295

6, 8 ve 10 Byte�lık Değişkenler

DF direktifi ile 6, DQ direktifi ile 8 ve DT direktifi ile 10 byte�lık değişkenler tanımlanır.


Fakat bu direktifler aslında kayar noktalı BCD (ikilik düzende kodlanmış onluk sayılar)
sayılar için kullanılır. Bu 3 direktifin asıl kullanım amaçları aşağıda açıklanıyor.

DF: 80386 ve üstü işlemcilerde, 32 bitlik korumalı modda, 48 bitlik pointer (indeksleme
amacıyla, offset adreslerini göstermek için kullanılan bir işaretçi) olarak kullanılır. Bu
direktif ile 6 byte�lık değişkenlerin tanımlanması mümkün olsa da 80386 ve sonrası
işlemcilerde pointer olarak kullanılır.

DQ: 64 bitlik kayar noktalı sayları 64 bitlik tamsayıları deklare etmek için kullanılır. Daha
çok kayar noktalı sayıları deklare etmek için kullanılır, çünkü x86 ailesinin henüz 64 bitlik
genel amaçlı bir kaydedicisi yoktur. Yani bu direktif ile 64 bitlik bir tam sayı deklare
ettiğinizde, bu sayı ile doğrudan işlem yapamazsınız çünkü en büyük kaydedici 32
bitliktir.

DT: Daha hassas (80 bitlik) kayar noktalı sayılar ve 10 byte�lık BCD değerleri deklare
etmek için kullanılır.

Yukarıdaki direktiflerle ilgili ayrıntılı bilgileri kayar noktalı sayılarla işlemler başlığı altında
ilerleyen makalelerimizde vereceğim.

Pointer Veri Tipleri

Pointer işaretçi anlamına gelip adresleme modlarında gördüğümüz indeksli adresleme ile
alakalıdır. x86 uyumlu işlemciler iki tür pointer�ı desteklerler. Bunlar near (yakın) ve far
(uzak) pointer�lardır. Buradaki yakın ve uzak kavramı referans alınan adresin segmenti
(64Kb.) aşmaması yada aşması durumunu ifade eder.

Near pointer�ler 16 bitlik�tir ve bir segmentin offsetlerine erişmek için kullanılırlar. p bir
değişken olsun, p=1000h için aşağıdaki kodları inceleyelim.

mov bx, p ;BX�e p pointerını yükle.


mov ax, [bx] ;p�nin gösterdiği konumdaki veriyi AX�e getir.

Örnekte AX kaydedicisinin değeri pointer�ın işaret ettiği adresten alınacak 2 byte�lık


değer ile değişecektir. Bir başka deyişle DS:1000h ve DS:1001h adresindeki değerler
AX�e yüklenecek. Bu işlem aynı segment içinde olduğundan dolayı, pointer near yani
yakın bölgedeki bir adresi göstermiş oldu. Aslında mov ax,ds:1000h komutu da aynı işi
yapardı fakat pointer değişkeni kullanılmadığından sonradan pointer değerini değiştirip
aynı komut satırı ile hafızanın başka bir bölgesine erişmek mümkün olmazdı.
Far (uzak) pointer�lar ile hafızanın istediğiniz bölgesine erişip işlem yapabilirsiniz. Bunun
için pointer deklare edilirken DD direktifinin kullanılması gerekir, yani pointer 32 bitlik
olmalıdır. BX, BP, SI ve DI kaydedicilerini erişeceğiniz uzak offsetler için herhangi bir
segment kaydedicisini de (ki bu genellikle ES olur) erişmek istediğiniz segment için
kullanabilirsiniz. İsterseniz bunu bir örnek ile açıklamaya çalışalım.

5555:3333 adresine erişip, buraya FFh değerini kopyalamaya çalışacağız. Bu işlemi yapan
program aşağıdaki gibidir. Kaynak kodları buradan download edebilirsiniz.

Şekil 2 - Uzak adreslere erişmek için 32 bitlik pointer�lar kullanılır.

Bu örnekte pointer 32 bitlik�tir ve erişeceğimiz segment:offset adresini gösterir. LES


komutu ES kaydedicisine pointer değişkeninin yüksek değerlikli 2 byte�ını (5555h)
yüklerken, operandında gösterilen kaydediciye de (bu örnekte BX) düşük değerlikli 2
byte�ını (3333h) yükler. MOV ES:[BX], AL komutu ile de 5555:3333 adresine AL deki
FFh değeri kopyalanır. Aşağıdaki şekiller ile bu programın pointer kullanarak nasıl uzak
adreslere eriştiğini görebilirsiniz.
Şekil 3 - LES BX, P komutu işlenmeden önce ES ve BX�in durumu

Şekil 4 - Pointer değişkeni ilgili kaydedicilere yüklendikten sonra


Şekil 5 - 5555:3333 adresine FF değeri yüklendikten sonra

ÇOKLU DEĞİŞKENLER

Bu tür değişkenler birden fazla parçadan oluşur. Tek boyutlu ve çok boyutlu diziler
(arrays) ve yapılar (structures) bu tür değişkenlerdir. Bu makalede sadece tek boyutlu
dizileri anlatmaya çalışacağım, çok boyutlu diziler ve yapıları başka bir makalede güzel
örneklerle, amacına uygun bir şekilde anlatacağım.

Diziler

Dizilerlerde diğer değişkenler gibi hafızaya yerleştirilmiş verilerden oluşur. Ard-arda gelen
byte, word veya double word değişkenler dizileri oluşturabilir. Daha önceki
makalelerimizde "Merhaba Assembly" yazısını ekrana yazdırmıştık, işte bu karakterlerde
hafızada dizi olarak saklanır. Şu anda yazdığım karakterlerde hafızada bir dizi veri olarak
saklanacak.

Dizi kavramının yanında getirdiği bazı terimler vardır, bunlar indeks, taban adresi, dizi
elemanı ve eleman tipi�dir. Burada dikkat edilmesi gereken en önemli nokta diziyi
oluşturacak elemanların aynı türden olma koşuludur. Diğer terimleride isterseniz
aşağıdaki şekille açıklamaya çalışalım.

Şekil 6 - Diziler ile ilgili terimler.


Dizinin tüm elemanlarının aynı türden olması gerektiğini söylemiştik, bununla beraber
dizinin ilk elemanının adresi taban adresidir. Dizinin elemanlarına erişmek için bu adres
referans olarak alınabilir. Son olarak dizinin ilk elemanından itibaren adresler artarak
devam eder ve bu adreslerdeki her elemana indeks değerleri ile erişilir. Dizinin herhangi
bir elemanına erişmek için kullanılacak klasik formül;

Eleman_Adresi = Taban_Adresi + ((Indeks - İlk_Index) x Eleman_Boyutu)

Bu formülde ilk_indeks 0 ise göz ardı edilebilir.

Dizilerin Deklare Edilmesi

Başlangıçta elemanların değerleri önemli değilse;

Dizi DB 100 DUP (?) ;byte türünden 100 elemanlı bir dizi
Dizi DW 100 DUP (?) ;word türünden olduğundan dolayı hafızada 200 byte�lık
alan kaplar.

Buradak DUP direktifi ingilizcedeki duplicate (diğerinin aynısı) kelimesinin karşılığıdır.

Elemanlar aynı olacaksa;

Dizi DB 100 DUP (12h) ; Hafızaya 100 adet 12h yerleştirir.

Elemanlar farklı ise;

Dizi DB 32, 45, 67, 3F, 0, 7, 11 ;7 tane byte türünden elemandan oluşan bir dizi.
Yazi DB �Merhaba Assembly� ;16 elemanlı bir karakter dizisi, hafızada ascii
kod karşılıkları saklanır.

Unutulmaması gerekir ki taban adresi dizinin başında yazdığımız etikettir, örneğin


yukarıda �Merhaba Assembly� karakterlerinden oluşan byte türündeki dizinin taban
adresi Yazi�dır.

Tek Boyutlu Dizilerin Elamanlarına Erişmek

WordDizi DW 1, 45, 1234h, 567Ah, 99, 105Eh

Yukarıdaki dizinin 2. elemanına erişmek demek o elemanın adresini bulmak ile aynı
anlama gelir. Bunun için formülümüz vardı;

Eleman_Adresi = Taban_Adresi + ((Indeks - İlk_Index) x Eleman_Boyutu)

Bu formülü word tipindeki dizimize uygulayacak olursak;

Eleman_Adresi = WordDizi + ((2 - 0) x 2)

yada;

Eleman_Adresi = WordDizi + 2 olarak bulunur.

Tabi ki bu işlemi bir program ile yapmak lazım. Aynı işi yapan program parçası aşağıda
verilmiştir.
mov bx, 2 ; indeks değeri BX�e yükleniyor,
add bx, bx ; 2*bx işlemi yapılıyor,
mov ax, WordDizi [bx] ; AX�e dizinin 2. elemanı (1234h) yükleniyor.

Dizi elemanlarına erişmek için çok değişik teknikler mevcut, fakat bunları ilerleyen
makalelerimizde açıklayacağım. Bazen assembly programcıları dizilerini deklare ederken,
dizinin ilk elemanı olarak indeks değerini koyarlar. Tabi ki bu durumun doğruluğu
görecelidir. Örneğin byte türünden bir dizi için bu teknikle en fazla 255 elemandan oluşan
bir dizi deklare edebilirsiniz. Daha fazla eleman deklare etmek için indeks değerinin
boyutunu arttırmak gerekir ama bu durumda da dizi elemanlarının aynı türden olması
şartını göz ardı etmemek gerekir.

Diziler usta programcıların vazgeçemediği veri yapılarıdır. Önemli olan dizinin


elemanlarına erişmek için kullanılacak olan fonksiyonu assembly dilinde yazabilme
kabiliyetidir. Örneğin;

Yildizlar DB 10 DUP (�*�)

dizisinden aşağıdaki üçgeni ekrana yazdırmak gibi.

*
**
***
****

Alın size ödev, bir daha ki makaleye kadar bu programı yazmaya çalışın, böylece hem
adresleme modlarını hem de tek boyutlu dizileri kavramış olursunuz. Sakın zor demeyin
elin oğlu dizilerden matrix türetip, sonra ona felsefi bir boyut ekleyip filim yapıyor :)

İşlemci Durum Kaydedicisi

Mikroişlemci içinde yapılan işlemlerin durumlarını programcıya bildiren bir kaydedici


mevcuttur. Tüm karar alma mekanizmaları bu kaydedicide gösterilen sonuçlar baz
alınarak yapılır.

İŞLEMCİ DURUM KAYDEDİCİSİ (PROCESSOR STATUS REGISTER)

Diyelim ki klavyeden girilen iki adet değişken var bunlar eşit ise ekrana "eşit" eşit değilse
"eşit değil" yazdırmak istiyoruz. Bunu üst seviye programlama dillerinde basit bir şekilde
if-else mantığıyla yapabilirsiniz. Peki mikroişlemci bu değerlerin eşit olup olmadığını nasıl
gösterecek bize. Cevabı basit bu değerlerin farkını alarak, sonuç sıfırsa girilen değerler
eşittir aksi taktirde eşit değildir. İyi ama assembly programcısı bu sonuç sıfır değerini
nereden okuyacak?
Bir işlemin sonucunun sıfır, negatif, pozitif veya elde ürettiği veya işlemin
gerçekleştirilmesi için borç alındığı gibi durumları gösteren kaydediciye işlemci durum
kaydedicisi diyoruz. Turbo debugger ile işlemci durum kaydedicisinin içeriğini
gözlemleyebilirsiniz.

Şekil 1 - 8086’da işlemci durum kaydedicisi.

Şekilde kırmızı çerçeve içine alınmış kısım işlemci durum kaydedicisini bitsel olarak
gösteriyor. Diğer kaydedicilerin aksine işlemci durum kaydedicisi programcı için bitsel
olarak anlamlıdır. Ve bu bitlerin her birinin bir adı vardır. Örneğin C biti Carry yani taşıma
biti veya Z biti Zero yani sıfır biti’dir. Terminolojide genellikle bu bitler "flag" yani bayrak
olarak adlandırılır. Aslında assembly programcıları işlemci durum kaydedicisi yerine
bayrak kaydedicisi terimini daha çok kullanırlar. Örneğin Z için Zero Flag yani sıfır
bayrağı denir.

8086, 80286, 80386, 80486 ve Pentium işlemcilerin bayrak kaydedicileri biraz farklıdır,
daha doğrusu işlemciler geliştikçe ek bayraklar eklenmiştir, mesela 80286’dan itibaren
işletim sistemlerinin yazımı için gerekli olan ayrıcalıklı mod bayrağı eklenmiştir.
Yukarıdaki şekilde 8086’nın 8 bitlik bayrak kaydedicisini görmektesiniz, aşağıdaki şekilde
ise 8086’dan 80486’ya kadar olan işlemcilerin sahip olduğu bayrakları görebilirsiniz.
Şekil 2 - x86 işlemci durum (bayrak) kaydedicisi.

Bayrak kaydedicisindeki her bir bitin 1 olma durumuna SET, 0 olma durumuna da RESET
denir. İşlemcinin ürettiği sonuçlar hakkında programcıyı uyaran bayrak kaydedicisindeki
durumlar görecelidir. Örneğin carry (taşıma) bayrağı 1 olduğunda bazen işlemin elde
ürettiğini bazen de borç alındığını ifade edebilir. Bazen de iki bayrağın durumu birlikte
değerlendirilerek bazı sonuçlara varılır. Örneğin;

MOV AX, 0FFFEh ; FFFE = 65534 işaretsiz sayıyı temsil ederse


ADD AX, 3 ; C = 1, O = 0 olur ve bu bir boyut taşmasını gösterir.

Bu örneği biraz daha açıklayacağım fakat daha önce matematik dersinde gördüğümüz
bazı olayları hatırlatmak istiyorum. Hani bir fonksiyonun çözümü için sorularda "her x
gerçel sayısı için" gibi ifadeler geçer ya, işte bu tür kabullenmeler assembly dilinde çok
önemlidir. Çünkü 1 ve 0’ları ancak bu şekilde yorumlayabilirsiniz, yani baştan evrensel
kümenizi kafanızda tanımlamanız gerekir. Bu örnekte de FFFE=65534 değerini gösterirse
diye başlıyoruz işe, nitekim FFFE işaretli sayı olarak kabul edilseydi -2 değerini
gösterecekti. Ama burada işaretli değil sadece pozitif tamsayılarla işlem yapıldığı
varsayılıyor. Bu işlemin sonucu AX kaydedicisinde 0001 olarak görülür. İşlemci sayıların
işaretli veya işaretsiz olduğuna bakmadan FFFE ye 3 ekler ve FFFE değeri önce FFFF
sonra 0000 ve daha sonrada 0001 olur. Tabi ki 65534 + 3, işleminin sonucu 1’e eşit
değildir. İşlemin doğru sonucu olan 65537 değeri 2 byte’lık AX kaydedicisinin içine
sığmadığından bu sonuç üretilmiştir. Bu toplama işlemi sürecinde sayı FFFF’den 0000’a
dönerken C=1 olur, bunu elde olarak düşünebilirsiniz (tabi ki pozitif tamsayılar kümesi
baz alınarak işlem yapılıyorsa) Aslında 0001 sonucunun başına bu elde getirildiğinde yani
sonuç C biti ile birlikte okunursa 10001 olur ve bu gerçektende 65537 değerine eşittir.
Fakat programcı doğal olarak AX kaydedicisini içindeki değeri yani 0001’i sonuç olarak
alacaktır. Burada sonuç anlamlı aralık olan 0...65535 arasında olduğundan O=0 olmuştur.
Özetle sonuç anlamlı aralıkta yanlış bir değerdir, bu durumu programcı iki bayrağa birden
bakarak anlayabilir.

Yukarıdaki gibi bir durumda programcı işlem yapıldıktan sonra bazı özel komutlarla C ve
O (overflow-taşma) bitlerinide kontrol eder ve bu bitler sırasıyla 1 ve 0 ise bir boyut
taşması olmuş demektir. Bu durumda programcı kullanıcı ekranına "sonuç çok büyük" gibi
bir mesaj gönderebilir. Hesap makinelerinin belli bir değere kadar işlem yapabildiğini
hatırlayın.

Bu örnek için en başta FFFE işaretli bir saydır deseydik herhangi bir taşma söz konusu
olmayacaktı. İşlemci yine aynı sonucu üretecek fakat bu sefer programcı bunu -2+3
olarak değerlendireceğinden sonuç doğal olarak 1 ve AX’te 0001 değeri doğru sonuç
olacaktı. Yine C=1 ve O=0 olsa da programcı için bu durum bir taşmayı
göstermeyecektir.

İşte assembly programlama dili matematiğin bu basit gibi görünen temellerine bağlıdır.
Hafızadaki 1 ve 0’ları nasıl yorumlarsanız öyle şekil alırlar. Bazen bu 1 ve 0’lar bir resim,
bazen bir mp3 bazen de bir kelime oluveririler. Hani matrix filminde herkes kodlara
bakıyor ama sadece Neo olayı idrak edebiliyordu ya, işte o misal :)

Şekil 3 - bilgisayarlar aslında 1’ler ve 0’lar dünyasıdır.

8086 için bayrak kaydedicisi 16 bitliktir ve bu 16 bit’te sadece 9 adet bayrak mevcuttur.
386 ve sonrası işlemcilerde ise bayrak kaydedicisi 32 bitliktir ve bu 32 bit’in her biri bir
bayrağı temsil etmez, aralarda hiçbir şeyi ifade etmeyen boş bit’ler mevcuttur. Şu anda
gerçek mod programları yani 8086 için programlar yazdığımızdan bu 9 adet bayrağın ne
anlama geldiklerini incelememiz yeterlidir.

Overflow Flag (OF) (Taşma Bayrağı): İşaret taşması olduğunda olduğunda set (1)
olur, mesela 1 byte’lık işaretli sayılarda 100+50=150’dir, fakat sonuç -128...127
aralığında değildir.

Direction Flag (DF) (Yön bayrağı): String işlemlerini yapan komutlar için kullanılır. 1
olduğunda index kaydedicisi otomatik olarak azalır. 0 ise index kaydedicisi otomatik
olarak artar. (mesela bir string hafızanın bir bölgesinden başka bir bölgeye kopyalanırken
işlemin stringin sonundanmı yoksa başındanmı yapılacağını D flag ile belirleyebilirsiniz)

Interrupt-enable Flag (IF) (Kesme Yetki bayrağı): 1 olduğunda maskelenebilir


kesmeler kullanılabilir. (kesmeler hakkında ayrıntı sonraki makalelerimizde!)

Single-step Flag or Trace (TF) (Tek Adım veya İzleme Bayrağı): Genelde debugger
programları için kullanılır, 1 olduğunda bir anda sadece 1 komut işlenir.

Sign Flag (SF) (İşaret Bayrağı): MSB biti (en soldaki bit) 1 olduğunda bu bayrakta 1
olur.
Zero Flag (ZF) (Sıfır Bayrağı): Sonuç sıfır olduğunda 1 olur.

Auxiliary carry Flag (AF) (Ara Elde Bayrağı): AL kaydedicisinin 0-3 arasındaki
bitlerinde yapılan işlemde elde veya borç üretilirse 1 olur.

Parity Flag (PF) (Eşlik Bayrağı): 1 olduğunda sonucun düşük değerlikli baytındaki
(mesela 127Fh gibi word boyutunda bir sonuç üretilmişse bunun 7F kısmı için) 1’lerin
sayısının çift olduğunu gösterir.

Carry Flag (CF) (Taşıma Bayrağı): 1 olduğunda elde üretildiğini veya borç alındığını
belirtir.

Bayrak Kaydedicisini özetleyecek olursak,

1- İşlemcinin yaptığı işlemlerin özel durumlarını gösterirler

2- Bazı işlemler yapılmadan önce bazı bayrakların 1 yada 0 yapılması gerekebilir

3- Programcılar bazı işlemleri anlayabilmeleri için bazen birden fazla bayrağın birlikte
ürettikleri durumları göz önüne almak zorundadırlar.

Uzun bir süre bu bayraklardan OF, SF, ZF, CF olanları bizim için önemli olacak.
Bayrakların geri kalan kısımları ve bayrakları etkilen komutları sonraki makalelerimizde
yeri geldikçe açıklamaya çalışacağım. İsterseniz şimdi bayrak kaydedicisi ve bayraklarla
ilgili kafanızda daha belirgin bir şeklin oluşabilmesi için makalemizin başında
bahsettiğimiz örneği hayata geçirelim.

Şimdi yapacağımız programda kullanıcı klavyeden 2 adet değer girecek ve bu değerler


eşitse ekrana "Girilen Degerler Esittir" yazacak değilse "Girilen Degerler Esit Degildir"
yazacak. İlk bakışta kullanıcı sayıları bilmiyor mu kardeşim ne gerek var böyle bir
programa diyebilirsiniz :) Fakat değerlerin klavyeden değilde paralel porttan alındığını ve
bu port’un bir klimaya bağlı olduğunu ve hava sıcaklığının 24C0 ye eşit olup olmadığını
test eden ve eşitse klimayı durduran değilse çalıştıran bir programında bundan pek farklı
olmayacağını tahmin edebilirsiniz.

Bu örnekte henüz işlemediğimiz konulardan olan "ekran ve klavye işlemleri" ve "şartlı


dallanmalar" konularını kapsayan komutlar kullanacağız. Bunların ayrıntılarını daha sonra
anlatacağımdan şimdilik fazla önemsemeyin. Bu komutlar aşağıdaki gibidir.

Bu komutlardan sonra program kullanıcı giriş yapana kadar durur ve


MOV AH,01
kullanıcı klavyeden değer girdiğinde o değerin ASCII kod karşılığı AL
INT 21h
kaydedicisine yerleştirilir.

JMP
Bu komut ile hedef adrese dallanma gerçekleştirilir.
HedefAdres

JE
Bu komut ile hedef adrese Z bayrağı 1 ise dallanma gerçekleştirilir.
HedefAdres

CMP BL, AL Bu komut BL ile AL kaydedicisini karşılaştırır.


Şekil 4 - Klavyeden girilen değerleri test eden program.

Yukarıdaki programın kaynak kodlarını buradan download edin ve TASM ile derleyin. Bu
programı çalıştırdığınızda sizden iki adet değer girmenizi isteyecek, değerleri girerken
enter tuşuna basmanıza gerek yok. Şayet girilen değerler eşitse ekranda "Girilen
Degerler Esittir" değilse "Girilen Degerler Esit Degildir" mesajını göreceksiniz.

Şekil 5 - Programın ekran çıktısı.

Değerlerin eşit olup olmama durumunu belirleyen işlemci durum kaydedicisinin Z biti (Z
bayrağı) dir. Bu bayrağın durumu karşılaştırma komutundan sonra girilen değerlere göre
1 yada 0 olur. Şayet girilen değerler eşit ise, eşit olan bu değerler karşılaştırılır ve sonuç
sıfır elde edilir, bu durumda Z=1 olur. JE komutu ise ancak Z=1 olduğunda, operandında
belirtilen adrese dallanma işlemini geçekleştirir. Aksi durumda işlemci klasik yolunu izler
ve bir sonraki komutu işler.
Şekil 6 - Z=1 olduğunda 0024h offset adresine dallanılacak.

Klavyeden farklı değerler girildiğinde ise, karşılaştırma işleminin sonucunda z=0 olur. Bu
durumda dallanma gerçekleşmez ve işlemci normal seyrini sürdürerek bir sonraki komutu
işler.
Şekil 7 - Z=0 olduğunda dallanma gerçekleşmeyecek, program sonraki komutu takip edecektir.

Bayrakların durumları bayrakları etkileyen bir komut işlendiğinde değişir, her komut
bayrakları etkilemez. Burada CMP komutu 6 adet bayrağı etkiliyor. Hangi komutun hangi
bayrakları etkilediğine Intel’in komut setinden bakabilirsiniz. Komut setinden CMP
komutunu bulursanız bu 6 bayrağın neler olduğunu görebilirsiniz.
Şekil 8 - CMP ile AF, CF, OF, PF, SF, ZF bayrakları etkilenir.

İsterseniz Şekil7 deki etkilenen tüm bayrakları ve neden dolayı 1 yada 0 olduklarını
inceleyelim. Burada yapılan işlem "CMP BL, AL" dir ve BL=33, AL=34’dür. BL-AL yani
33h-34h işlemini yapan CMP komutu aşağıdaki nedenlerden dolayı bu bayrakları
etkilemiştir.

1- 33-34 işlemi için borç gerektiğinden (Küçük sayı büyük sayıdan çıkartılıyor) CF=1,

2- Bu işlemin sonucu sıfır olmadığından ZF=0,

3- İşlemin sonucu negatif olduğundan SF=1,

4- İşlem sonucunda bir işaret taşması olmadığından OF=0,

5- Sonuçta elde edilen -1 yani FFh, başka bir deyişle 11111111b değerindeki 1’lerin sayısı
sekiz adet yani çift olduğundan PF=1,

borç
6- Binary olarak çıkartma işleminde daha 2. bitte 0’dan 1 alındığından
çıkarılırken AF=1
olmuştur.

Bizim programımızda sadece Z bayrağının durumu önemli olduğundan bu bayrağın


durumuna göre programın akışını kontrol eden JE komutunu kullandık. Aslında burada JE
komutu ile ilerleyen makalelerde ele alacağımız if-else mantığını oluşturduk.

Gördüğünüz gibi program akış kontrolünde bayraklar hayati önem taşır. İşlemci durum
kaydedicisinde bulunan bu bayraklar şu anda aklınızın alamayacağı bir çok işlemde
bizlere yardımcı olacak.

Bu konuyu daha iyi anlamak için Turbo Debugger’da programın her komutunu adım-adım
çalıştırın ve komut setinden faydalanarak, çalıştırdığınız komutun etkilediği bayrak varsa
bunun nedenini araştırın.
80x86 KOMUT SETİ (Bölüm 1)

Bu makalemizde 80x86 gerçek mod komutlarını inceleyeceğiz.

80x86 KOMUT SETİ (Bölüm 1)

X86 tabanlı mikroişlemcilerin icra ettiği makine kodları sabit olmasına rağmen,
programlama dillerinin komut ve ifadeleri farklı olabilir. Assembly programlama dilininde
diğer programlama dillerinde olduğu gibi bir dizi komutu vardır. Bu komutlar genelde
mnemonik’ler (nivmonik diye okunur) şeklindedir. Örneğin LEA mnemoniği Load Effective
Adres kelimelerinin kısaltılmış şeklidir. Bu makalemizde x86 Assembly programlama
dilinin komutlarını anlatmaya çalışacağım ve bu makalede açıklanan komutları
öğrendiğinizde kendi başınıza program yazabilir hale geleceksiniz. Aslında 80386 ve
sonrası mikroişlemciler için daha birçok komut mevcuttur ve bu komutlar assembly
dilinde program yazma işini kolaylaştırır. Bu komutları ilerleyen makalelerimizde
açıklamaya çalışacağım.

80x86 komutları genelde 8 grup altında incelenir.

1) Veri taşıma komutları

mov, lea, les , push, pop, pushf, popf

2) Dönüştürme komutları

cbw, cwd, xlat

3) Aritmetic komutlar

add, inc sub, dec, cmp, neg, mul, imul, div, idiv

4) Mantıksal, kaydırma, çevirme ve bitsel işlemler için komutlar

and, or, xor, not, shl, shr, rcl, rcr

5) I/O (Giriş/Çıkış) komutları

in, out

6) Karakter dizi (String) komutları

movs, stos, lods

7) Program akış kontrol komutları

jmp, call, ret, Jxx (şartlı dallanma komutları)

8) Diğer komutlar

clc, stc, cmc

Veri Taşıma Komutları


Veri taşıma komutları bir değeri bir yerden başka bir yere taşımaya yarar. mov, xchg,
lds, lea, les, lfs, lgs, lss, push, pusha, pushad, pushf, pushfd, pop, popa, popad, popf,
popfd, lahf, ve sahf komutları veri taşıma komutlarıdır.

MOV komutu

Bu komutun kullanım şekilleri aşağıdaki gibidir.

mov reg, reg


mov mem, reg
mov reg, mem
mov mem, immediate data
mov reg, immediate data
mov ax/al, mem
mov mem, ax/al
mov segreg, mem16
mov segreg, reg16
mov mem16, segreg
mov reg16, segreg

MOV komutu assembly dilinde çok kullanılan bir komuttur. Yukarıdaki kullanım şekilleri
için İngilizce ifadeler kullanılmıştır, çünkü internetten erişebileceğiniz komut seti
referanslarında hep bunlar karşınıza çıkacak. Tüm komutlar için geçerli olan bu İngilizce
ifadelerin Türkçe karşılıkları aşağıdaki gibidir.

reg : register à kaydedici,

mem : memory à hafıza (RAM-ROM veya Giriş/Çıkış portları olabilir)

immediate data à acil adresleme ile kullanılan direkt veri

segreg : segment register à segment kaydedicisi

mem16 : memory 16 bit à 16 bitlik hafıza alanı (dw direktifi ile tanımlanan veriler)

reg16 : register 16 bit à 16 bitlik kaydedici (AX, BX .. gibi)

MOV komutunu kullanırken yapamayacağınız iki şey vardır, bunlardan birincisi “mem,
mem” tipinde bir kullanımdır. Yani hafızanın bir konumunda diğer bir konumuna
doğrudan taşıma yapamazsınız. Bu işlemi yapmak için taşınacak veri önce mikroişlemci
kaydedicilerinden birine getirilmelidir.

MOV sayi1, sayi2 ;yanlış kullanım

Yukarıdaki gibi bir komut satırı yazarsanız, derleyiciniz hata mesajı verir. Böyle bir işlemi
yapmak için genel amaçlı bir kaydediciyi kullanmanız gerekir.

MOV AX, sayi1


MOV sayi2, AX ;sayi1 ve sayi2 değişkenlerinin word türünden olduğunu
varsayıyoruz.
MOV komutu ile yapamayacağınız ikinci şey ise segment kaydedicilerine doğrudan bir veri
taşımaktır. Yani acil adresleme modunu segment kaydedicilerine uygulayamazsınız.

MOV DS, 1525h ; bu kullanım hatalıdır.

Segment kaydedicilerine bir değer yükleyebilmek için genellikle genel amaçlı kaydedicileri
kullanılır. Ayrıca segment kaydedicilerine ancak 16 bitlik boyutunda değerler
yüklenebileceğinden genel amaçlı kaydedicilerin 8 bitlik kısımlar değil 16 bitlik kısımları
kullanılabilir.

MOV AX, 1525h


MOV DS, AX

Bunların dışında operandların boyutları eşit olmak zorundadır.

MOV AX, toplam ; burada toplam değişkeninin boyutu kesinlikle word tipinde
yani iki byte uzunluğunda olmalıdır.

Şayet acil adresleme kullanarak bir veri taşıyorsanız işlemci operandın boyutunu
kaydediciye uyarlar.

MOV AX, 15h ; Bu komut işlenince AX’in içinde 0015h değerini görürsünüz.

Dikkat edilemesi gereken diğer bir husus ise hafıza operandlarıdır. Örneğin MOV [BX], 5
gibi bir komut ile hafızaya neyin yükleneceği belli değildir (Burada BX kaydedicisine değil
hafızaya taşıma yapıldığına dikkat edin) MOV [BX], 5 gibi bir komutla acaba hafızaya byte
boyutunda bir 5 değerimi (05) yoksa word boyutunda bir 5 değerimi (0005) yüklenecek?
Bunu kodlarınızda belirtmeniz gerekir. Derleyici bu komut satırına hata verir. Doğru
kullanım aşağıdaki gibi olmalıdır.

mov byte ptr [bx], 5


mov word ptr [bx], 5
mov dword ptr [bx], 5 (*)

(*) 80386 ve sonrası işlemcilerde kullanılabilir

mov byte ptr [bx], 5 satırını açıklayalım. Burada BX=0000 olduğunu varsayalım, böyle bir
durumda ds:0000 adresine bir taşıma işlemi gerçekleşeceltir. Ama bu adresten itibaren
05’mi yoksa 0005’mi yoksa 00000005’mi taşınacaktır? İşte bunu ptr operatörü belirler.
mov byte ptr [bx], 5 komut satırı için ds:0000 adresine 1 byte’lık bir veri yani 05 taşınır.
Şayet operatör byte ptr değilde word ptr olsaydı o zaman ds:0000 ve ds:0001
adreslerine dırasıyla 05 ve 00 değerleri taşınacaktı.

XCHG komutu

xchg (exchange) komutu operandlarındaki değerleri yer değiştirir.

80x86 ailesi için dört değişik kullanım şekli vardır;

xchg reg, mem


xchg reg, reg
xchg ax, reg16
xchg eax, reg32 (*)
(*) 80386 ve sonrası işlemcilerde kullanılabilir

LDS, LES, LFS, LGS, ve LSS komutları

Bu komutlar 32 bitlik bir hafıza bölgesindeki değeri bir segment kaydedicisine ve bir
genel amaçlı kaydediciye bir defada yükler. Kullanım formatı aşağıdaki gibidir;

LxS hedef, kaynak

Bu komutları aşağıdaki gibi kullanabilirsiniz;

lds reg16, mem32


les reg16, mem32
lfs reg16, mem32 (*)
lgs reg16, mem32 (*)
lss reg16, mem32 (*)

(*) 80386 ve sonrası işlemcilerde kullanılabilir

Reg16 genel amaçlı herhangi bir kaydedici olabilir mem32 ise double word boyutunda bir
veri olmalıdır, bunu “dd” direktifi ile oluşturabilirsiniz. Daha önce bu komutlardan biri olan
LES komutu için “X86 Assembly Dilinde Değişken Bildirimi -1” adlı makalede çok güzel bir
örnek vermiştim. Erişmek istediğimiz adresin segment ve ofset bölümlerini birleştirerek
bir değişken oluşturuyor sonrada bunu program çalışırken istediğimiz gibi kullanıyorduk.

LEA Komutu

LEA (Load Effective Address – Etkin Adresi Yükle) sadece offset adreslerini hedef operandına yükleyen bir
pointer gibi düşünebilirsiniz. Genel kullanım formatı

lea dest, source

şeklindedir.
lea reg16, mem
lea reg32, mem (*)

(*) 80386 ve sonrası işlemcilerde kullanılabilir.

MOV komutunu da LEA komutu yerine kullanabilirsiniz, fakat hangi komutu


kullanacağınıza adresleme moduna göre seçmelisiniz. Bazen MOV komutu LEA dan daha
hızlı çalışabilir, tüm bu bilgilere herhangi bir intel komut setinden faydalanarak
bakabilirsiniz.

Daha önceki makalelerimizde ekrana bir karakter dizisini yazdırmıştık, bunun için kaynak
kodumuzda ekrana yazdılıralacak olan veriyi aşağıdaki gibi tanımlamıştık;

Dizi DB "Merhaba Assembly",0Ah,0Dh,24h

Daha sonra bu dizinin adresini DX kaydedicisine yüklememiz gerektiğinde aşağıdaki


komutu kullanmıştık;

MOV DX,OFFSET Dizi

Bu komutun yaptığı işi LEA kullanarakta yapabiliriz;


LEA DX, Dizi

PUSH ve POP komutları

80x86 push ve pop komutları Stack Memory (Yığın hafıza bölgesi) ile ilgili işlemlerde
kullanılır. Yğın hafıza bölgesini sizler .exe tipi program hazırlarken .Stack direktifi ile
belirliyorsunuz. İşte bu bölge genellikle programdaki dallanma veya altrutinlerin çalışması
sırasında, dönüş adreslerinin ve bayrak kaydedicisinin durumlarını saklamak için
kullanılır. Push komutu bu yığın olarak adlandırılan hafıza bölgesine verileri iterken, pop
komutuda bu bölgeden veri almada kullanılır.

push reg16
pop reg16
push reg32 (**)
pop reg32 (**)
push segreg
pop segreg (CS hariç)
push memory
pop memory
push immediate_data (*)
pusha (*)
popa (*)
pushad (**)
popad (**)
pushf
popf
pushfd (**)
popfd (**)
enter imm, imm (*)
leave (*)

(*) 80286 ve sonrası işlemcilerde kullanılabilir


(**) 80386 ve sonrası işlemcilerde kullanılabilir

Push ve pop komutları kullanıldığında yığın hafıza bölgesinin işaretçisi olan SP kaydedicisi
değişir. Tabiî ki bu yığına itilen veya yığından çekilen değerin boyutuna bağlıdır. Bu
komutlar 2 veya 4 byte�lık değerler ile işlem yaptığından yığına 2 bytelık bir değer
itildiğinde (mesela bu AX kaydedicisinin içeriği olabilir) SP�nin değeri 2 byte azalır. Şayet
yığına 4 byte�lık değer itilirse SP�nin değeri 4 azalır. Unutulmaması gereken önemli bir
hususta yığın hafıza bölgesine itilen en son değerin çekilecek olan ilk değer olmasıdır.
Tabiî ki yığına birden fazla word ya da doubleword itildiyse aralardaki değerler ile işlem
yapmak adresleme modlarıyla mümkündür fakat bu SP�de herhangibir değişiklik
yapmaz. Yığın hafıza bölgesi ile ilgili unutulmaması gereken üç önemli kural vardır.
Segment kaydedicilerinden olan SS yığın hafıza bölgesinin segment adresini gösterir.
Yığına bir şeyler itildikçe SP azalır eökildikçe artar. SS:SP her zaman yığının tepesi olarak
tabir edilen noktayı gösterir.

LAHF ve SAHF Komutları

Bu komutlar bayrakları AH kaydedicisine yükler veya AH’a yüklenen bayrak


kaydedicilerinin durumlarını kayar nokta kaydedicisine ( floating point register ) yükler.
Bu komutlar 8086 zamanından kalma ve günümüzdeki modern assembly programlarında
pek kullanılmayan komutlardır.

Genişletme İşlemleri
Bazen byte boyutundaki bir değeri word boyutuna veya word boyutundaki bir değeri
doubleword boyutuna genişletmek gerekebilir. Bu gibi durunlarda aşağıdaki komutlar
kullanılır.

movzx hedef, kaynak ;Hedef kaynağın iki katı büyüklüğünde olmalıdır.


movsx hedef, kaynak ;Hedef kaynağın iki katı büyüklüğünde olmalıdır.
cbw
cwd
cwde
cdq
bswap reg32
xlat

MOVZX, MOVSX, CBW, CWD, CWDE, ve CDQ Komutları

cbw (convert byte to word) AL kaydedicisinin 1 byte’lık içeriğini AX’e genişletir. Şayet
AL’deki değer pozitifse AH’ın tüm bitleri ‘0’ değerini alır. AL’deki değer negatifse AH’ın
tüm bitleri ‘1’ olur.

cbw

cwd (convert word to double word) komutu AX’in değerini DX:AX’e genişletir. CBW
komutundaki kurallar bu komut içinde geçerlidir.

cwd

Bu komut 80386 ve sonrası işlemcilere özeldir. CWD komutunda olduğu gibi word
boyutundaki bir değeri double word boyutuna genişletmede kullanılır. CWD AX’i DX:AX’e
genişletirken bu komut AX’i EAX’e genişletir.

cwde

cdq komutu EAX kaydedicisindeki 32 bit’lik değeri EDX:EAX ‘e genişletir. Bu komutda


80386 ve sonrası işlemlerde kullanılır.

cdq

Örnekler:

; AL’ deki 8 bitlik değeri 32 bitlik dx:ax’e genişletmek için

cbw
cwd

; AL’ deki 8 bitlik değeri 32 bitlik eax’e genişletmek için

cbw
cwde

; AL’ deki 8 bitlik değeri 64 bitlik edx:eax’e genişletmek için

cbw
cwde
cdq
movsx komutuda yukarıdaki komutlara benzer iş yapar, kullanım formatları aşağıdaki
gibidir.

movsx reg16, mem8


movsx reg16, reg8
movsx reg32, mem8
movsx reg32, reg8
movsx reg32, mem16
movsx reg32, reg16

Örnekler:

movsx ax, al ;CBW komutunun yaptığı işi yapar.


movsx eax, ax ;CWDE komutunun yaptığı işi yapar.
movsx eax, al ;CBW ve CWDE komutlarının birlikte yaptığı işi yapar.

movzx komutu movsx komutu gibi kullanılır fakat negatif değerleri genişletmek için
kullanılmaz, çünkü movzx komutu genişletme işleminde sadece bitleri ‘0’ yapabilir. Bu
komutun sonundaki zx harfleri İngilizcede zero extend yani sıfır ile genişlet gibi bir anlam
taşır.

Tüm bu genişletme komutları genellikle aritmetik işlemlerde ve özellikle bölme


komutlarında kullanılır.

BSWAP Komutu

Bildiğiniz gibi x86 hafızası little endian yapıya sahiptir, bununla beraber big endian
hafızaya sahip bilgisayarlarda çok sayıda mevcuttur. Örneğin Apple’ın Machintosh
bilgisayarları big andian hafıza yapısına sahiptir. BSWAP komutu bu farklı hafıza
sistemlerine sahip olan bilgisayarlar arasında veri haberleşmesi yapılması için kullanılır.
BSWAP operandında belirtilen 32 bitlik kaydedicinin içindeki değeri byte-byte ters çevirir.
Düşük değerlikli sekiz biri en yüksek değerlikli bölgeye, 8-15 arasındak bitleri 16-23
arasına, 16-23 arasındaki bitleri 8-15 arasına ve son olarak 24-31 arasındaki bitleride 0-7
arasına yerleştirir.

BU komut sadece 80486 ve sonrası işlemcilerde kullanılabilir. Kullanım formatı aşağıdaki


gibidir.

bswap reg32
XLAT Komutu

Genellikle tablo olarak tasarlanan dizilere erişmek için kullanılır. AL kaydedicisine


tablonun elemanlarından birini yükler. Bu komutu aşağıdaki örneğe bakarak daha iyi
anlayabilirsiniz.

Tablo DB 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F

Bu tablonun 11. elemanını AL’ye yüklemek istersek;

MOV AL, 0Ah ; İndeks değeri (0 dan 0Ah’a kadar 11 tane değer var)
LEA BX, Tablo ; BX’e (taban kaydedicisi) Tablonun ofset adresi
yükleniyor
XLAT ; Tablonun 11. elemanına erişilip ASCII karakterin hex
karşılığı AL’ye yükleniyor (AL=41h)
Şimdi yukarıda açıkladığımız komutlardan birkaçını kullanarak bir program yazalım ve
daha sonra da inceleyelim. Öncelikle Push ve Pop komutlarını DS kaydedicisinin değerini
belirlemek için kullanabilirim. .exe tipi programlarda DS kaydedicisinin değerini
belirlemek için bu güne kadar hep aşağıdaki iki satırı kullandık,

MOV AX, @data


MOV DS, AX

Yığın komutlarınıda veri taşımak için kullanabileceğimizden,

MOV AX, @DATA


PUSH AX
POP DS

Yukarıdaki üç satırda DATA segmentin adresini önce AX kaydedicisine yükledik, sonra bu


kaydedicideki değeri yığına ittik son olarakta yığından bu değeri çekip DS kaydedicisine
yükledik.

Kullandığım sistemin saat bilgisini ekrana yazdırmak istiyorum. Tabiî ki bu gibi işlemler
için hali hazırda DOS’un kesmeleri mevcuttur. Fonksiyon 2Ch sistem zamanı ile ilgili
bilgileri işlemcinin kaydedicilerine getirir.

MOV AH,2Ch
INT 21h

Bu komutlar işlenince CX ve DX kaydedicileri saat bilgisi ile yüklenirler,

CH = saat CL = dakika DH = saniye DL = salise

Kaydedicilerin içindeki değerlerin binary olduğunu ve debugger programlarında bu


değerlerin hexadecimal formatta göründüğünü unutmayalım. Biz bu programda kullanıcı
ekranına desimal formatta değerler yazdıracağımızdan ya çevirme işlemi yapacağız yada
tablo oluşturup hex bilgilerin karşılığına tablodan bakacağız.

Çevirme işlemleri için çarpma, bölme gibi komutları bilmeniz gerekir ama henüz o
komutlarla ilgili örnekler çözmedik. Bunun yerine biz hex-decimal tablolar oluşturup bu
tablolardan desimal değerleri bulalım ve ekrana yazdıralım. Fakat tabloların sınırlarını
belirlemek için elimizdeki bilgiyi iyi tanımamız gerekir.

En büyük saat bilgisi 23:59 yani 0 ile 59 elemanlarını kapsayan bir tablo oluşturmam
yeterli.

SaatTablo DB
"00$","01$","02$","03$","04$","05$","06$","07$","08$","09$"
DB
"10$","11$","12$","13$","14$","15$","16$","17$","18$","19$"
DB
"20$","21$","22$","23$","24$","25$","26$","27$","28$","29$"
DB
"30$","31$","32$","33$","34$","35$","36$","37$","38$","39$"
DB
"40$","41$","42$","43$","44$","45$","46$","47$","48$","49$"
DB
"50$","51$","52$","53$","54$","55$","56$","57$","58$","59$"
Yukarıdaki tablo bize 60 elamanlı gibi görünebilir fakat hafızada 60*3=180 byte yer
kaplar, çünkü x86 hafızası byte adreslenebilir ve her rakam veya karakter hafızada 1
byte’lık yer kaplar.

Diyelim ki CH’taki saat bilgisi 0Ah, bu saat 10 demek. Tabloda “10$” olan kısım acaba
tablonun hangi adresidir? Sayacak olursanız 30. elemanın 1 31. elemanın 0 yani 30
elemandan itibaren 10 değerinin mevcut olduğunu görürsünüz. Tabloda her saat değeri
için 3 byte’lık değer ayrılmıştır ve bu programda CH veya CL deki değerler yardımıyla
indeks adresi hesaplanırken 3 ile çarpmak gerekir.

CH’ta saat 10’u temsilen 0Ah değeri bulunuyorsa 0Ah*3 = 1Eh = 30 hesabı yapılmalıdır.
Tüm bu işlemleri yapıp ekrana yazdırılıcak değeride DX kaydedicisine yüklemeliyiz. Çünkü
ekranda bir karakter dizisini yazdırmak için INT 21h’ın 9. fonksiyonu karakter dizisinin
başladığı adresi DX kaydedicisinde bulunmasını ister. Tüm bu işlemleri yapan kod satırları
aşağıdaki gibidir.

MOV AL, CH ;Şimdi saat bilgisi,


MUL Uc ;3 ile çarpılarak tablodaki desimal karşılığı bulundu.
MOV DI, AX ;Bu adres indeks olarak düşünüldüğünden DI ya
yüklendi.
LEA DX, SaatTablo[DI] ;Ve BX’e saat bilgisinin desimal karşılığı olan tablo
konumu yüklendi.

MUL komutunu önümüzdeki makalelerde inceleyeceğiz, çok fazla kullanım formatı


mevcuttur. Bizim için burada, sadece CH veya CL deki değerleri 3 ile çarpsın yeter. Fakat
MUL komutu sadece AL deki değeri bir değişken yada AL kaydedicisi ile çarpabildiğinden
önce CH yada CL deki bilgileri AL’ye taşımamız gerekiyor. Daha sonra 3 değeri ile çarpıp
tablodan adresi buluyoruz. Tabiî ki Uc bir değişken ve data segmentinde tanımlı olmalıdır.

Uc DB 3

Al deki değer 3 ile çarpıldığında sonuç 16 bitlik AX kaydedicisinde saklanır. Bu noktadan


sonra bu adresi DX kaydedisine yükleyip ekranda string yazdırma kesmesini çağırırsak
işlem tamam olacaktı ama, SaatTablo taban adresine göre hesapladığımız indeks adresi
halen AX kaydedicisinin içinde ve LEA DX, SaatTablo[AX] gibi bir komut satırı
kullanamam çünkü böyle bir adresleme modu mevcut değil. Indeks olarak kullanılacak
adresler 8086 assembly dilinde SI, DI veya BX kaydedicilerinin birinde olmalıdır, bu
yüzden bizde hesapladığımız indeks değerini DI ya yükledik. Son olarak ekranda bir
string yazdıracağımızdan DX kaydedicisinin içine yazdıracağımız stringin başlangıç
adresini atarak ve daha önce de kullandığımız INT 21h fonksiyon 9h ı kullanabiliriz.

Bununla beraber “:” karakterinide saat bilgisini yazdırırken saat ile dakika arasına
yerleştirelim, tabiî ki bu bilgide data segmentinde bir adreste saklı kalsın.

Ayirac DB ‘:’

Birde saat kelimesini data segmentte tanımlayalım,

Saat DB ‘Saat $’

Şimdi kabataslak ne yapacağım belli oldu, programımın algoritmasını da yazdıktan sonra


kodlarımı yazmaya başlayabilirim.

1- Ekrana “Saat” yazdır


2- Sistem saatini ilgili kaydedicilere yükle

3- Tablodan saat bilgisinin desimal karşılığını bul

4- Ekrana yazdır

5- : karakterini ekrana yazdır

6- Sistem dakikasının karşılığını bul

7- Ekrana yazdır

8- Dur
Şekil 1 - Saat bilgisini ekranda gösteren program.
Şekil 2 - Programın ekran çıktısı.

Elbette bu program daha kısa veya pratik bir şekilde yapılabilirdi. Bunun için komut
bilgimizi genişletmeliyiz. Ayrıca değişken bildirimlerini de şu ana kadar tam olarak
anlatmadım. Bu konularla ilgili makalelerimiz yolda fakat şu anda sizlerin mevcut
bilgilerinizle bu programa birde salise kısmını ekleyebilmeniz gerekir.

Bu programa ait assembly kaynak kodlarını buradan, kodların derlenmiş halini buradan
download edebilirsiniz. Bir sonraki makalede görüşmek üzere.

80x86 KOMUT SETİ (Bölüm 2)

Bu makalemizde 80x86 komut kümesinin bir kısmını daha inceleyeceğiz.

80x86 KOMUT SETİ (Bölüm 2)

Aritmetik ve mantık (lojik) işlemler mikroişlemcinin ALU (Arithmetic Lojic Unit) denen
kısmında yapılır. ALU bir dizi elektronik toplama, çıkarma ve mantık devrelerinden
oluşmuştur. Bu devrelerin çalışma mantıkları ise sayma temelinden geçer. Bizlerde ilkokul
sıralarında temel işlemleri parmaklarımızla sayarak yapardık. 3 ile 5’i toplarken 3’ün
üzerine 5 tane parmak sayardık. Mikroişlemcide her saykılda (saat darbesinde) ALU’da bir
sayma işlemi yapar. Bu saat darbesi ne kadar hızlı olursa işlemler o kadar hızlı
gerçekleşir. Örneğin 1 GHz. hızında bir işlemci saniyede 1 milyar elektronik darbe
üretebilir ve bu saniyede milyonlarca işlem yapabileceği anlamına gelir.

Aritmetik komutların genel kullanım formatları aşağıdaki gibidir. Bu komutları kullanırken


de adresleme modlarına dikkat etmemiz gerektiğini unutmayalım.

ADD ve ADC komutları:

Toplama ve elde ile toplama komutlarıdır. ADD komutu işlemci durum kaydedicisinin C
bitini hesaba katmazken ADC toplama işlemini C bitinide dahil ederek yapar.

MOV AX, 5
ADD AX, 6

Bu işlemden sonra AX kaydedicisinde 11’in karşılığı olan 000Bh değeri görülür.

MOV AX, 5
ADC AX, 6

Bu işlemden sonra şayet C=0 ise sonuç 000Bh C=1 ise sonuç 000Ch olacaktır.

x := y + z + t işlemini;
MOV AX, Y
ADD AX, Z
ADD AX, T
MOV X, AX

şeklinde yapabilirsiniz. Tabiki bu x,y,z,t’ler birer hafıza konumu veya kaydedici olabilir.

x := x + z işlemini düşünelim. x ve z hafızadaki birer değer olsun yani değişkenlerimiz.


Bunu en hızlı şekilde işlemciye nasıl hesaplatabiliriz?

1.yol

MOV AX, X
MOV BX, Z
ADD AX, BX
MOV X, AX

Yukarıdaki şekilde bu işlemi yapabilrsiniz ama bu çokta iyi bir yol değildir.

2.yol

MOV AX, X
ADD AX, Z
MOV X, AX

Bu yol daha iyi gibi görünsede bundan daha iyi çalışacak kodlar aşağıdaki gibidir.

3. yol

MOV AX, Z
ADD X, AX

Adresleme modlarını akıllı bir şekilde kullanabilirseniz çok hızlı çalışan programlar
hazırlayabilirsiniz. Yukarıdaki üç program parçası aynı işi yapmasına rağmen en hızlı
çalışanı 3. südür. Günümüzde kullanıcıya daha yakın ve program yazması daha kolay olan
üst seviye programlama dillerine göre assembly dilinin en büyük avantajı budur.

ADD ve ADC komutları işlemcinin bayrak kaydedicindeki bitlere şöyle etki ederler.

1. İşaretli sayılarla işlem yaparken işaret taşmalarını göstermesi


amacıyla V (overflow) bitine. Çünkü bazen negatif bir sayı ile negatif
başka bir sayı toplanır ve pozitif bir sonuç elde edilebilir.

2. İşaretsiz sayılarda işlem yaparken boyut taşmalarını göstermesi


amacıyla C bitini. Örneğin 2 byte’lık bir değişken olan FFFFh
(65535) ile 1 i toplarsınız sonuçta 0 elde edersiniz. Sonucun alana
sığmadığı bu gibi durumlarda programcıyı haberdar etmesi amacıyla
C=1 olur.

3. Şayet sonuç negatif bir değerse S=1 değilse S=0 olur.

4. 0 sonucu bir çok yerde önemli olabilir. Bu yüzden toplama komutları


ile işlem yapıldığında sonuç 0 olursa Z=1 aksi halde Z=0 olur.

5. Binary değerler ile desimal sayıları kodlamak gerekebilir (BCD). Bu


durumda düşük değerlikli 4 bitte taşma olursa bayrak kaydedicisinin
A biti 1 olur. Örneğin 0000 1111b değerine 1 eklendiğinde sonuç
0001 0000b olacaktır ve düşük değerlikli 4 bitteki en büyük sayı
olan 1111 birden 0 a düşecektir. Bu yüzden A=1 olur ve işlemci
programcıyı bu durumdan haberdar eder.

6. Elde edilen sonuçtaki binary 1 ler çift sayıda olursa, örneğin 0000
0101 değerinde 2 tane 1 vardır bu durumda P=1 olur.

INC Komutu:

ADD X, 1 gibi çalışır. X kaydedici veya hafıza alanı olabilir. Kısaca hedefi 1 arttırır.
Döngülerde çok kullanılan bir komuttur. Bu yüzden çok önemlidir. INC komutunun 1,2
veya 4 bytelık operandı olabilir. Yani bu komutu aşağıdaki formatlarda kullanabilirsiniz.

INC AL ; 1 bytelık kaydedici


INC AX ; 2 bytelık kaydedici
INC EAX ; 4 bytelık kaydedici
INC HAFIZAADRESI ; Byte word veya doubleword boyutundaki değişkenler
olabilir.

INC komutu genelde ADD mem,1 veya ADD reg,1 formatına tercih edilir çünkü daha
hızlıdır, buna rağmen peşpeşe 1 den fazla inc komutu kullanmak gerekirse komut setinin
incelenmesinde fayda vardır. Çünkü bu işi ADD reg,2 veya ADD mem,2 şeklindede
yapabilirsiniz ve bu durumda sadece 1 adet komut satırı yazarsınız. Ayrıca INC bayrak
kaydedicisinin C bitine etki etmez. Bu yüzden dizi işlemleri için çok uygun bir komuttur.
Oysa ADD ve ADC C bitine etki ederler ve büyük dizilerde bu komutlar kullanılırsa dizinin
içindeki elemanları işaret etme işleminde bazen yanlış sonuç gösterebilirler.

XADD Komutu:

80486 ve sonrası işlemciler için geçerli bir toplama komutudur. Bu komutu aşağıdaki
örnek ile daha iyi anlayacağınız kanaatindeyim.

; AX = 07h ve BX = 03h olsun

XADD AX, BX ; Bu komuttan sonra

; AX = 0Ah yeni toplama işleminin sonucunu gösterir,


; ve BX = 07h olur, yani AX’teki kaybolan operand buraya taşınır.

Çıkartma işlemini yapan komutlar:

SUB ve SBB komutları:

SUB (Subtract) yani çıkartma SBB ise borç ile çıkart (SuBtract with Borrow) anlamına
gelir. Her iki çıkartma işlemi bir çıkartma sonucu üretmenin yanında bayrak
kaydedicisinin C bitinide etkilerler. Bu komutların genel kullanım formatları aşağıdaki
gibidir;

sub reg, reg


sub reg, mem
sub mem, reg
sub reg, immediate data
sub mem, immediate data
sub eax/ax/al, immediate data

Bu komutların ne yaptığını örnekler ile daha iyi anlayabiliriz;

MOV AX, 0Ah


MOV BX, 04h
SUB AX, BX

Yukarıdaki komutlar ile işlemci 0Ah-04h işlemini yapar ve 6 sonucunu AX kaydedicisinin


yani hedef kaydedicide saklar. Bu işlemde büyük değerden küçük değer çıkartıldığından C
bitinin durumunda bir değişiklik olmaz.

MOV AX, 04h


MOV BX, 0Ah
SUB AX, BX

Yukarıdaki komutlar işlenince 04h-0Ah işlemi yapılır ve 2 byte’lık AX kaydedicisinin içinde


FFFAh sonucu görülür. Bu işlemde ayrıca C biti set edilir ve C=1 olur. Programcı C’nin bu
durumunu göz önünde bulundurmalıdır. Çünkü sonucu işaretsiz bir tamsayı gibi
değerlendirirse yanılır. Böyle bir sonuç elde edildiğinde sonucun tümleyeni alını ve 1
eklenir.

1111 1111 1111 1010 ; FFFAh’ın binary karşılığı


0000 0000 0000 0101 ; tümleyeni
0000 0000 0000 0110 ; 1 fazlası yani 6

Ayrıca bu sonuç incelenirken, kaydedicideki FFFAh değerinin 15. biti 1 olduğundan sonuç
negatif olarak değerlendirilmeli ve yukarıdaki işlem yapılarak sonucun gerçek değeri
hesaplanınca;

-6 değerine ulaşabilirsiniz.

Aslında FFFAh sonucunun sağlamasını yaparsanız, yani çıkana bu sonucu eklerseniz;

FFFAh
000Ah
-------
0004h

yukarıdaki 4h sonucuna erişirsiniz.

SUB komutunun kullanımını SUB hedef, kaynak şeklinde genellersek;

hedef = hedef - kaynak;

SBB komutu ise;

hedef = hedef - kaynak - C işlemlerini yapar.

Çıkartma komutları toplama komutlarında da olduğu gibi bayrak kaydedicisinin, Z, S, V,


A, P ve C bitini etkilerler. Tabiki bu etkilenen bayraklar yapılan işleme göre programcı
tarafından değerlendirilmelidir.

Çıkartma Aslında Toplamadır!


3 - 4 aslında 3 + (-4) değil midir? Bu tür basit bilgileri unutmamak bazen sizin işinizi
kolaylaştırabilir. Aşağıdaki örneği inceleyelim.

x = x - y -z işlemini yapmak için;

MOV AX, X
SUB AX, Y ; x-y işlemi yapılıyor, sonucu AX’e yükleniyor.
SUB AX, Z ; x - y - z işlmi yapılmış oluyor
MOV X, AX ; sonuç x’e yüklenerek x = x - y - z işlemi yapılmış oluyor.

Fakat bu işlem aslında x = x - (y + z) değilmidir?

MOV AX, Y
ADD AX, Z ; y - z işlemi yapılıyor
SUB X, AX ; x - y - z işlemi yapılıp sonuç x’e yükleniyor.

DEC komutu:

Decrement yani azalt anlamına gelir. hedef operandını 1 eksiltir, başka bir deyişle -1
ekler. Kullanım formatları aşağıdaki gibidir.

DEC reg
DEC mem
DEC reg16

C biti hariç çıkartma komutların etkilediği bayrakları etkileyen bir komuttur. INC komutu
gibi genelde döngülerde her iterasyondan sonra sayacı azaltmak için kullanılır.

CMP komutu:

SUB komutu ile aynı işi yapar fakat çıkarma işleminin sonucunu herhangi bir kaydediciye
yüklemez. Bu komut genelde şartlı dallanma komutlarından önce bayrakları etkilemek
için kullanılır. CMP’nin anlamı "compare" yani karşılaştır demektir. Bakın neleri
karşılaştırabiliyoruz;

genel kullanım formatları,

cmp reg, reg


cmp reg, mem
cmp mem, reg
cmp reg, immediate data
cmp mem, immediate data
cmp eax/ax/al, immediate data

Bu komut A, C, O, P, S ve Z bayraklarını etkiler. Programcı etkilenen bu bayrakları


göreceli olarak yorumlayabilir, Şöyle ki;

A ara elde biti yani işlem yapılırken 3. bite gelindiğinde eldenin olup olmadığı hakkında
bilgi verir ve P işlem sonucundaki değeri binary olarak düşündüğümüzde 1’ler tekmi
yoksa çift mi durumunu gösterir. A ve P bayraklarından ziyade programcılar Z, C, O ve S
bitlerinin durumları ile ilgilenirler. Bu bayrakları değerlendirirken de işlemlerin işaretli
yada işaretsiz sayılar ile yapıldığının bilinmesi büyük önem taşır.

1- Z bayrağı sayılar ister işaretli ister işaretsiz olsun eşitlik yada eşit olmama durumunu
gösterir.
mov ax,5
mov bx,5
CMP ax,bx ; Z=1 yani operandlar eşit.

2- C bayrağı işaretsiz sayılarda;

C=1 ise 2. operand 1.operand dan büyük demektir. C=0 ise 1. operand büyüktür.

C bayrağının işaretli sayılarda bize verdiği sonuçların bir anlamı yoktur.

3- S ve O bayrakları işaretsiz sayılarda anlamsız olurken işaretli sayılarda 2 değişik


durumu gösterirler. Bunlar;

a- S=0, O=1 veya S=1, O=0 ise 2. operand 1. operand’tan büyüktür.

b- S=0, O=0 veya S=1, O=1 ise 1. operand 2. operand’tan büyüktür.

Şartlı Dallanma Komutları:

İngilizce karşılığı Conditional Jump Instructions’dır. Bu tür komutlar işlendikten sonra


program ya normal akışına yani komutları satır-satır işlemeye devam eder ya da normal
akışından sapıp başka bir adresteki komutu işler. Karar alma mekanizmaları bu komutlar
ile yapıldığından çok önemli komutlar olduğunu sanırım tahmin edebilirsiniz.

Şartlı dallanma komutlarının ilk harfi J ile başlar ve takip eden 1,2 yada 3 harf şartı
gösterir. Bu tür komutları bundan sonra JXXX komutları olarak kullanacağım.

JXXX komutlarının da CMP komutları gibi işaretli ve işaretsiz değerler için farklı anlamları
vardır. Tüm bu anlamlar ve komutları aşağıdaki 3 tabloda özetleyebiliriz.

Bayrakların Durumunu Test Etmek İçin Jxxx Komutları


Eş Karşıt
Komut Açıklama Şart
Komut Komut
JC Jump if carry (carry (taşıma) bayrağı 1 ise) Carry = 1 JB, JNAE JNC
JNC Jump if no carry (carry (taşıma) bayrağı 0 ise) Carry = 0 JNB, JAE JC
JZ Jump if zero (zero (sıfır) bayrağı 1 ise) Zero = 1 JE JNZ
JNZ Jump if not zero (zero (sıfır) bayrağı 0 ise) Zero = 0 JNE JZ
JS Jump if sign (sign (işaret) bayrağı 1 ise) Sign = 1 - JNS
JNS Jump if no sign (sign (işaret) bayrağı 1 ise) Sign = 0 - JS
JO Jump if overflow (overflow (taşma) bayrağı 1 ise) Ovrflw=1 - JNO
JNO Jump if no Overflow (overflow (taşma) bayrağı 0 ise) Ovrflw=0 - JO
JP Jump if parity (parity (eşlik) bayrağı 1 ise) Parity = 1 JPE JNP
JPE Jump if parity even (sonuçtaki 1’ler çift ise) Parity = 1 JP JPO
JNP Jump if no parity (parity (eşlik) bayrağı 0 ise) Parity = 0 JPO JP
JPO Jump if parity odd (sonuçtaki 1’ler tek ise) Parity = 0 JNP JPE

İşaretsiz Sayılarda Jxxx Komutları


Eş Karşıt
Komut Açıklama Şart
Komut Komut
JA Jump if above (>) (Yukarısındaysa) Carry=0, Zero=0 JNBE JNA
JNBE Jump if not below or equal (not <=) Carry=0, Zero=0 JA JBE
(Aşağısında değil yada eşit değilse)
JAE Jump if above or equal (>=) (Yukarısında veya eşitse) Carry = 0 JNC, JNB JNAE
JNB Jump if not below (not <) (Aşağısında değilse) Carry = 0 JNC, JAE JB
JB Jump if below (<) (Aşağısındaysa) Carry = 1 JC, JNAE JNB
Jump if not above or equal (not >=)
JNAE Carry = 1 JC, JB JAE
(Yukarısında değil veya eşit değilse)
Carry = 1 or Zero =
JBE Jump if below or equal (<=) (Aşağısında veya eşitse) JNA JNBE
1
Carry = 1 or Zero =
JNA Jump if not above (not >) (Yukarısında değilse) JBE JA
1
JE Jump if equal (=) (Eşitse) Zero = 1 JZ JNE
JNE Jump if not equal (!=) (Eşit Değilse) Zero = 0 JNZ JE

İşaretli Sayılarda Jxxx Komutları


Eş Karşıt
Komut Açıklama Şart
Komut Komut
Sign = Ovrflw or
JG Jump if greater (>) (Büyükse) JNLE JNG
Zero=0
Jump if not less than or equal (not <=) Sign = Ovrflw or
JNLE JG JLE
(Düşük değilse yada eşit değilse) Zero=0
Jump if greater than or equal (>=)
JGE Sign = Ovrflw JNL JGE
(Büyükse veya eşitse)
JNL Jump if not less than (not <) (Düşük değilse) Sign = Ovrflw JGE JL
JL Jump if less than (<) (Düşükse) Sign Ovrflw JNGE JNL
Jump if not greater or equal (not >=)
JNGE Sign Ovrflw JL JGE
(Büyük değilse veya eşit değilse)
JLE Jump if less than or equal (<=) (Düşükse veya eşitse) Sign Ovrflw or Zero = 1 JNG JNLE
JNG Jump if not greater than (not >) (Büyük değilse) Sign Ovrflw or Zero = 1 JLE JG
JE Jump if equal (=) (Eşitse) Zero = 1 JZ JNE
JNE Jump if not equal (!=) (Eşit değilse) Zero = 0 JNZ JE

Tabiki yukarıda kullandığım büyük, küçük, yukarısında veya aşağısında kelimeleri


sayıların değerleri açısındandır. Tablolarda aslında aynı işi yapan hatta makine düzeyinde
aynı kodu üreten komutlar vardır. Bunlara örnek olarak JA ve JNBE’yi örnek verebiliriz.
Assembler her iki komut içinde aynı makine kodunu üretir, fakat programcıyı assembly
kodlarını yazarken birazcık olsun rahatlatması için Intel bu tür yazımı uygun görmüştür.

Tüm programlama dillerinde kullanılan if, for, while vb. deyimler aslında şartlı dallanma
komutları ile düzenlenmiş bir kaç assembly komutuna benzetilebilir. Hatta işi biraz daha
netleştirelim C dilinde kullandığımız "if" deyimi aslında bir adet cmp ve bir adet Jxxx
komutundan oluşur, xxx kısmı ise şarta göre değişir. Aşağıdaki örnekle bu olayı daha iyi
anlayabilirsiniz.
Şekil 1 - C Dili ile yazılmış bir program.
Şekil 2 - Programın assembly kodlarına baktığımızda if deyimine karşılık CMP ve JLE komutlarını görüyoruz.

1. şekilde C dilinde yazılmış basit bir program görüyorsunuz. Bu programda tamsayı


türünden 2 adet değişken (a ve b) ve karakter türünden 1 adet değişken mevcut (sonuc).
IF deyimi ile şayet a, b’den büyükse (ki zaten öyle) sonuç değişkenine büyüğü temsilen b
karakteri aksi durumda küçüğü temsilen k karakteri yüklenecek. Şimdi burada bizi
ilgilendiren C dilinde IF acaba assembly’de neye karşılık geliyor?

Şekil 2 ye baktığınızda bu sorunun cevabını hemen görüyorsunuz. IF aslında CMP ve JLE


komutlarının bir kombinasyonuymuş. JLE yerine başka bir şartlı dallanma komutu
gelebilirdi, bu tamamen IF ile kullandığınız şarta bağlıdır, bu programda IF (a>b)
kullanılmıştı. Assembly karşılığında a’nın değeri olan 5 önce eax kaydedicisine yükleniyor
ve sonra hafızadaki b değişkeni değeri ile yani 4 ile karşılaştırılıyor.

JLE satırına dikkat edin, JLE’nin anlamı less or equal yani düşük yada eşitse. Yani a’nın
değeri olan 5 b’nin değeri olan 4’ten düşük yada eşitse, program 00411A3E adresinden
devam edip, önce eax’e k karakterini yükleyip (daha doğrusu k’nın ASCII kod karşılığını
yükleyecek), bunu hafızadaki "sonuc" adlı yere (sonuc da aslında bir adres :))
kaydedecektir. Tabi 5 4’ten büyüktür. Hal böyle olunca bu programda JLE komutu
işlendikten sonra bir dallanma söz konusu değildir. Yani program normal akışını devam
ettirecek ve eax’e "b" karakterini ardından da sonuc adlı adrese kopyalayacaktır.

Bu programın assembly kodlarını daha önce görmediğimiz bir biçimde birazcık farklı
görmeniz doğaldır. Bunun nedeni Visual Studio.NET’in C derleyicisinin 32 bitlik
olduğundandır. Yani bu güne kadar biz 16 bitlik TASM veya MASM’ı kullandık. Bu yüzden
eax kaydedicisini göremedik veya adresleri buradaki gibi 32 bitlik değil de hep 16 bitlik
offsetler halinde gördük. Bu yüzden assembly kodları biraz farklı. 32 bitlik kodlama bizim
için henüz çok erken bir kavram, bu yüzden bundan sonraki makalelerimizde 16 bitlik
kodlamaya devam edeceğiz.

80x86 KOMUT SETİ (Bölüm 3)

Bu makalemde 80x86 komutlarını değişik örneklerle anlatmaya devam edeceğim. Bu


makalede toplama ve çıkartma işlemlerine bir nokta koyup çarpma ve bölme işlemlerine
bir giriş yapmak istiyorum.

80x86 KOMUT SETİ (Bölüm 3)

NEG komutu

NEG negatif kelimesinin kısaltmasıdır. Tek operandı vardır. Kullanım formatı aşağıdaki
gibidir.

neg reg
neg mem

yani operandı herhangi bir kaydedici veya hafıza adresi olabilir. Yaptığı iş operandın
değerinin negatifini almaktır. Daha doğru bir deyişle operandını 0’dan çıkartır. Binary
düzende düşünecek olursanız 1’lein yerine sıfır 0’ların yerine 1 getirir ve bu sonuca 1
ekler.

mov al,0fh
neg ax

yukarıdaki işlemden sonra AX’in içeriği F1h olacaktır.

0Fh = 0000 1111


tersi = 1111 0000
1111 0000 +1 = 1111 0001 = F1h

Bu komutun ne amaçla kullanılabileceğini makalenin sonlarına doğru anlayacaksınız.

MUL ve IMUL Komutları

MUL çarpma IMUL ise işareti dikkate alarak çarpma işlemlerini yapar. Kullanım formatı
aşağıdaki gibidir.
İşaretsiz çarpma:

mul reg
mul mem

İşaretli çarpma:

imul reg
imul mem
imul reg, reg, imm (*)
imul reg, mem, imm (*)
imul reg, imm (*)
imul reg, reg (**)
imul reg, mem (**)

*- Sadece 286 ve sonrası işlemcilerde.


**- Sadece 386 ve sonrası işlemcilerde.

IMUL komutunun 286 ve 386 dan sonraki kullanım formatlarına bakacak olursanız, bu
güne kadar gördüğümüz formatlardan biraz farklı olduğunu görürsünüz. Bu kullanım
şekilleri programcıların kafasını biraz karıştırmakla beraber tek bir komut satırı ile
çabucak çarpma işlemini yapmasını sağlar. Nede olsa Intel karmaşık komut setini
benimsemiştir.

Çarpma komutları bayt-bayt, word-word veya Doubleword-Doubleword çarpma yapabilir.


Tabi ki Doubleword çarpım için 386 ve sonrası işlemci kullanmanız gerekiyor, çünkü 32
bitlik kaydediciler 386 ile birlikte gelmiştir.

Ayrıca çarpma işlemi toplamadan daha büyük sonuçlar çıkarabilir. Yani 2 basamaklı bir
değeri başka bir 2 basamaklı değer ile toplarsanız sonuç en fazla 3 basamaklı çıkarken
çarpmada bu 4 basamağa kadar çıkabilir. Daha fazla basamaklı sayıların çarpımında
sonuç çarpılan veya çarpandan çok daha fazla basamaklı çıkabilir. Bu gerçeği göz önüne
alarak işlemci tasarımcıları sonucu her zaman çarpan ve çarpılanın boyutundan daha
büyük bir kaydedicide saklama yoluna gitmişlerdir. Bunları aşağıdaki şekil ve
açıklamalarla daha iyi anlayacaksınız.

Byte Çarpma

27h ile 17h’ı çarpmak için;

mov al, 27h


mov dl, 17h
mul dl

komutlarını kullanabilirsiniz. Burada "mul dl" komutu ile DL*AL işlemi yani bu
kaydedicilerdeki değerler olan 17h ve 27h sayıları çarpılır. Peki sonuç nerede? Yukarıdaki
şekle baktığınızda sonucun AX içinde olacağını görebilirsiniz. Bu çarpma işleminden sonra
AX’te 0381h değeri görülür.

Pratik olarak 8 bitlik bir değerin karesini almak için;

mov al,sayi
mul al

komutlarını kullanabilirsiniz.

Word Çarpma

Bu tür bir çarpma işleminde operand AX ile çarpılır ve sonuç DX-AX kaydedicilerinden
okunur. DX’te daha önce ne olduğu önemli değildir çünkü çarpmadan sonra buraya
sonucun yüksek değerlikli byte’ı yerleşir. Sonucun düşük değerlikli byte’ı ise AX
kaydedicisinde saklanır.

Burada dikkat ederseniz çarpma işlemiyle birlikte AX kaydedicisindeki "çarpılan" da


kaybedilecektir. Benim tavsiyem bu tür çarpmalarda çarpan ve çarpılanı birer değişken
olarak programınızın data segmentinde tanımlamanızdır. Aşağıdaki örnekleri inceleyin,

100h ile 2345h değerlerini çarpalım;

mov ax, 2345h


mov bx, 100h
mul bx

bu işlemden sonra DX=0023h ve AX=4500h olur. Yani asıl sonuç olan 234500h değerinin
yüksek değerlikli word’u DX’te düşük değerlikli kısmıda AX’te görülür. Fakat çarpılan
değer yani 2345h bu işlemden sonra kaybolacaktır. Şayet bu çarpılan değer sizin için
önemliyse;

carpilan db 2345h
..
..
..
mov ax, carpilan
mov bx,100h
mul bx

böylece 2345h değeri daima "carpılan" ismi ile hafızada korunur. Aynı şeyi tabi ki çarpan
için yani 100h değeri içinde yapabilirsiniz.

carpilan db 2345h
carpan db 100h
..
..
..
mov ax, carpilan
mov bx, carpan
mul bx

Double Word Çarpma

Double word boyutundaki verilerin çarpımında da word dekine benzer bir yapı kullanılır.
Çarpılan değer EAX kaydedicisine yerleştirilip, MUL veya IMUL komutunun peşinden gelen
operand ile bu değer çarpılır. Daha sonra elde edilen sonucun yüksek değerlikli
doubleword’u EDX’te düşük değerlikli doubleword’ü ise EAX’te saklanır. Yani sonuç 64
bitliktir.

carpilan dd 12345678h
carpan dd 34522344h
..
..
.386
..
..
mov ax, carpilan
mov bx, carpan
mul bx

Yukarıdaki örnekte sonuç olarak işlemci 03B878D610295FE0h değerini hesaplar. Bu


çarpma işleminden sonra EDX=03B878D6h ve EAX=10295FE0h olur.

Buradaki .386 32 bitlik kaydedicileri kullanmak için assembler’a verilen bir direktif
(talimat) dır. EDX ve EAX gibi 32 kaydedicilerin 32 bitlik alanlarını kullanmak için nu
talimatı vermeniz gerekir. 32 bitlik programlama, 16 bitlik programlama nedir bunlar?
Şimdilik sadece 32 bitlik programların 16 bitliklere göre daha avantajlı olduğunu
görebilirsiniz. Çünkü 32 bitlik programlama ile kaydedici boyutlarımı 2 katına çıkıyor ve
bir kaydedicide hesaplayabileceğimiz değerlerde aynı oranda artıyor, bu işlemi 16 bitlik
bir programlama ile de halledebilmemize rağmen 2 katı daha fazla komut yazmamız
gerekir.

MUL komutu bayrak kaydedicisinin C ve O bitlerini etkiler. Bu bayraklar beraber


değerlendirildiğinde aşağıdaki sonuçlar çıkartılır.

1- Byte boyutundaki bir operand AL ile çarpılırsa sonuç AX’te görülür. AH=0 ise C ve O
sıfır olur, aksi durumlarda bu bayraklar set (1) olur.
2- Word çarpmada C ve O sıfır ise DX’te sıfır demektir, aksi durumlarda bu bayraklar set
olur.

3- Double word çarpmada ise C ve O sıfır ise EDX’te sıfır demektir, aksi durumlarda bu
bayraklar yine set olur.

Yukarıdaki üç durum soldaki sıfırların çarmada bir değeri olmadığından, sonucu optimize
etmenize yardımcı olacaktır.

IMUL ile Diğer Çarpma Formatları

IMUL (Integer Multiplication) komutu ile yukarıdaki MUL komutu için verilmiş kalıpları
kullanabilirsiniz, bununla beraber IMUL komutuna özel çok operandlı kullanım
formatlarıda mevcuttur. Tüm kullanım formatları bu makalenin başında verildiği gibidir
fakat kaydedicilerin 8 16 ve 32 bitlik durumları da göz önüne almamız gerekir. Şimdi
henüz açıklamadığımız 286 ve 386 sonrası işlemcilerde kullanılabilen komut formatlarını
aşağıdaki örneklerle inceleyelim.

imul operand1, operand2, imm ;Genel kullanım formatı

imul reg16, reg16, imm8


imul reg16, reg16, imm16
imul reg16, mem16, imm8
imul reg16, mem16, imm16
imul reg16, imm8
imul reg16, imm6
imul reg32, reg32, imm8 (*)
imul reg32, reg32, imm32 (*)
imul reg32, mem32, imm8 (*)
imul reg32, mem32, imm32 (*)
imul reg32, imm8 (*)
imul reg32, imm32 (*)

* Sadece 80386 ve sonrası işlemcilerde kullanılabilir

Yukarıdaki komut formatlarının 3 operandlı olanları

operand1 := operand2 x imm

ve 2 operandlı olanlarıda

operand1 := operand1 x imm

şeklinde çalışır. Her zaman son kullanılacak olan operandın "imm" yani sayısal bir değer
olduğuna dikkatinizi çekerim.

mov bx, 4 ; BX = 0004h


imul ax, bx, 3 ; AX = 4 * 3 = 000Ch

Bu komutlar ile 8x8 bit çarpım söz konusu değildir, imm8 olarak yukarıda gördüğünüz
operand sadece komutunun makine kodunun olmasını sağlar. Ayrıca bu çarpma
işlemlerinde sonucun boyutu operandta belirtilen kaydedicilerin boyutuyla aynıdır, yani
makalenin başında anlattığımız mul komutu gibi sonuç operandın 2 katı olmaz. Bu
durumda sonucun hedef kaydediciye sığmaması durumuna karşı C ve O bitleri birlikte
kontrol edilmelidir, bu durumlara Intel’in komut setinden bakabilirsiniz.
Bununla beraber bu formattaki çarpma komutları Z bitini her zaman doğru bir şekilde
etkilemeyebilir, şayet sonucun sıfır olup olmadığı sizin için önemli ise ancak sonucu sıfır
ile karşılaştırdıktan sonra Z bitini kontrol etmelisiniz. Aynı şekilde sonucun işaretini
öğrenmek için işaret bayrağı yerine C ve O bitlerinin sıfır olup olmadığı kontrol
edilmelidir.

IMUL komutu için Intel’in 80286 ve sonrası işlemcilere koyduğu bu adresleme biçimleri
çok boyutlu diziler ile yapılan işlemleri hatırı sayılır biçimde kolaylaştırmıştır. Bu konuya
çok boyutlu dizileri ve karmaşık veri yapılarını anlatırken bir daha değinmeyi
düşünüyorum.

DIV ve IDIV Komutları

DIV division yani bölme kelimesinin kısaltmasıdır. Bölme işlemi çarpmanın tersine
bölünen’e göre küçük sonuç üretir, bu yüzden bu komutları kullanırken bölünen’in boyutu
bölen’in boyutunun iki katı olmak zorundadır, an azından Intel bu komutlar için böyle bir
form öngörmüştür. Bu yüzden bölünenin boyutu en az word türünde olmalıdır, çünkü x86
Assembly dilinde en küçük veri tipi byte’dır. Bu durumda byte türünden bir değeri
bölmek isterseniz bunu CBW komutu ile word boyutuna dönüştürmeniz gerekir,
hatırlarsanız bu tür komutları daha önceki makalelerimizde açıklamıştık.

Div ve idiv komutlarının genel kullanım formatları aşağıdaki gibidir.

div reg ; İşaretsiz çarpma


div mem

idiv reg ; İşaretli çarpma


idiv mem

Word’u Byte’a Bölmek

Örnek: 10h:3h işlemini yapmak istiyoruz, bu değerlerin ikisinin de byte türünden


olduğunu varsayalım;

mov al, 10h


mov bl, 03h ; bl yerine başka bir kaydedicide olabilir!
cbw ; 10h şimdi 0010h ve AX’te
div bl ; ax, bl’ye bölündü

bu işlemden sonra AX, 0105h olur. AH’taki 01h kalan ve AL’deki 05h ise bölümdür.
Nitekim 16’nın (yani 10h) 3’e bölümü ile de bu sonuç üretilir. Bu tür bölme işleminde elde
edebileceğiniz en büyük bölüm 255 (FFh işaretsiz değerler için) yada 127 (7Fh işaretli
değerler için) değerleridir.

Doubleword’u Word’e Bölmek


Bu tür bölme işleminde elde edebileceğiniz en büyük bölüm 32767 (FFFFh işaretsiz
değerler için) yada 16383 (7FFFFh işaretli değerler için) değerleridir.

Quadword’u Doubleword’e Bölmek

Peki ya sonuç tam değilse?

Mesela 11h’ı (yani 17’yi) 3’e bölerseniz sonuç normalde 5,666... şeklinde olur, bu işlemi
div komutu ile yaptığınızda ise AX’te 0205h değerini görürsünüz, yani bölüm 5 ve kalan
2. Özetle 17’nin içinde 5 tane 3 ve 1 tane’de 2 vardır ve div ve idiv komutları tamsayı
bölme işlemlerini gerçekleştirebilir. Ondalıklı bölme işlemleri için floating point
kaydedicileri kullanılır ve bu kaydediciler matematik işlemcisinin içindedir. 80486 DX
işlemcisine kadar matematik işlemci normal işlemcinin yanına opsiyonel olarak konulurdu
mesela işlemci 80386 ise matematik işlemcisi 80387 olurdu. Artık matematik işlemcisi
normal işlemcinin içine gömülü olarak geliyor. Bu arada floating point ünitelerini
kullanmak için yüksek seviyeli assembly kodları yazmak gerekir ve bu iş bizim için henüz
çok erken. Fakat kalanı 10 ile çarpıp sonrada bölünen ile karşılaştırıp şayet bölünenden
büyükse tekrar bölene bölme işlemine gidebilirsiniz, aynı kağıt üzerinde normal bölme
işlemi yapar gibi, fakat floating point ünitelerini kullanmak inanın bu işten daha pratiktir
ve daha kolay sonuç verir.

Malesef Tüm Sonuçlar Binary

Toplama, çıkartma, çarpma ve bölme komutlarını gördük, artık bu komutları kullanarak


basit bir hesap makinesi programı yazmak isteyebilirsiniz, böyle bir programı yazmaya
başladığınızda karşınıza sonuçları ekranda desimal formatta göstermek gibi bir problem
çıkacaktır. Evet ekrana yazdırmak için daha önce programlar yazdık fakat bunu sadece
stringler ile gerçekleştirdik. DB direktifi ile deklare edilen kelime katarları (stringler)
hafızada byte-byte ve ardışık olarak saklanır aynı zamanda bu byte’lar elbette harflerin
veya sayıların ASCII kod karşılıklarıdır. 8086 komut seti (bölüm 1) başlıklı makalemizde
saat programı yapmıştık ve kaydedicilerde elde ettiğimiz sonuçları bir tablo vasıtasıyla
ASCII karakterlerini ekranda göstermiştik. Bu yöntem sıkça kullanılmaz, hatta daha önce
bu iş için bu yöntemi kullanan bir program görmedim diyebilirim.

Representation yani gösterme veya sunum işlemi çok geniş bir yelpazede incelenebilir, bu
tamamen kullanıcının hayal gücüne kalmış bir olaydır. Benim burada anlatmaya
çalışacağım olay ise ekran text modunda iken hafızadaki binary ifadelerin ASCII karakter
karşılıklarını ekranda göstermek olacaktır.
Diyelim ki bir işlem yaptınız ve sonucunu 20h (32) olarak AL kaydedicisinde saklamayı
başardınız ve bunu ekranda göstereceksiniz, bunu direk olarak ekrana yazdırırsanız
sadece imleci 1 kez ilerletmiş olursunuz çünkü 20h ascii kod tablosunda space yani
boşluk karakterine karşılık gelir, klavyedeki en büyük tuş yani. Peki 32yi nasıl
yazdıracağız? Unutmayın ki ascii kod tablosunda sadece rakamların, harflerin ve bir dizi
kontrol karakterinin kod karşılıkları vardır sayıların kod karşılıkları yoktur. Bu bağlamda
biz 32 yi değil 3 ve 2 yi yan yana ekranda göstermeyi düşünmeliyiz. Ama şu anda AL’de
ne 3 ne 2 var sadece 20h var. Diğer bir gerçek 3’un ascii kod karşılığı 33h ve 2 nin ki ise
32h dır. 30h ile 39h arası ascii kod tablosunda rakamlar için ayrılmıştır.

şimdi ekrana sırasıyla 33h’ı ve 32h’ı gönderirsek kullanıcı 32 yi görecektir. Problemi


özetleyelim elimizde bir baytlık 20h var ve bizim bunu 2 byte lık 3332h dizisine
dönüştürmemiz gerekiyor. Keşke bir komut bu işlemi bizim yerimize yapsa!

Böyle bir komut varmı yokmu oraya geleceğiz ama 20h ile 3332h arasında güzel bir bağ
var. 20h ı 0ah yani 10’a bölsek zaten hex’den decimal’e dönüşüm işlemi yapmış oluruz;

mov ax, 20h


mov bl, 0Ah
div bl ;AX=0203 yapar.

Keşke AX’teki 0203h değerinde 0’ların yerine 3 gelseydi;

or ax,3030h ;AX’teki 0203h artık 3233h oldu.

birde al ile ah’ı yer değiştirsek! Acaba buna gerçekten gerek var mı? Hatırlarsanız ekrana
bir string yazdırmak için DOS kesmelerinden 09h nolu fonksiyonunu kullanmıştık ve bu
fonksiyon hafızadaki stringleri yazdırıyordu, yani biz AX’teki bu 3233h değerini önce bir
hafızaya atalım sonra 32h ile 33h’ın yerini değiştirmek gerekiyor mu düşünürüz;

mov sonuc, ax ; sonuc=3332h olur.

x86 tabanlı işlemciler hafızayı adreslerken little endian byte sıralamasını kullanırlar, bu
yüzden al’deki düşük değerlikli byte hafızanın düşük numaralı adresine ax’teki yüksek
değerlikli byte’ta hafızanın yüksek numaralı adresine yerleşmiş olur, böylece ax ile al’nin
içeriklerini takas etmemize de gerek kalmaz. Artık sonuc değişkenini referans göstererek
ekrana yazdırma kesmesini kullanabilirsiniz.

lea dx,sonuc
mov ah,9
int 21h

AAA, AAS Komutları

Klavyeden giriş yapıldığında, basılan tuşa ait kod hafızada ascii formatta saklanır,
mikroişlemci hesaplamaları binary yapar bu yüzden ascii’den binary’ye dönüşüm yapmak
gerekir. Şayet ekrana bir karakter basılacaksa bu karakter ekrana gönderilmeden önce
ascii forma dönüştürülmelidir.
Bizler günlük yaşamımızda desimal değerleri kullanırız, bu desimal değerleri hafızada
binary rakamlar olan 1 ve 0’ları kullanarak gösterebiliriz, buna BCD (Binary Coded
Decimal) kodlama diyoruz. Desimal numaralar hafızada BCD olarak gösterilmenin
yanında ASCII olarakta gösterilebilir. İşte AAA, DAA, AAS gibi komutları bunun için
kullanıyoruz.

Örneğin klavyeden girilen 1234 hafızada 31 32 33 34 olarak ASCII formda saklanır. BCD
gösterimin ise iki farklı çeşidi vardır, bunlar packed BCD (paketlenmiş BCD) ve unpacked
(paketlenmemiş) BCD’dir.

1234 hafızada 01 02 03 04 byte dizisi olarak saklanırsa bu paketlenmemiş BCD’dir, şayet


aynı değer hafızada 12 34 byte dizisi olarak saklanırsa bu da paketlenmiş BCD’dir. AAA,
AAS, AAD ve AAM komutlarının hepsi ascii değerlere dönüşüm için kullanılır. Bu
komutların operandı yoktur AL kaydedicisindeki değerleri dönüştürürler.

AAA (Ascii Adjust After Addition) komutu toplama komutundan sonra sonucu ascii ye
ayarlar. Aşağıdaki örnekleri inceleyin;

34h = 0011 0100b


35h = 0011 0101b
+_______________
69h = 0110 1001b

Sonuç 09 olması gerekir AAA komutu 6 değerini siler. Sonuç AL�de 09 olarak görülür.

36 = 0011 0100
35 = 0011 0101
+_______________
6B = 0110 1011

Sonuç 11 olması gerekir AAA komutu B değerinin yerine 1 koyar ve toplama sonucu 9
değerini aştığından AH kaydedicisine de 1 koyar, yani AX = 0101 olur.

Tüm bu işlemlerden sonra OR komutunu kullanarak sonucu ASCII gösterim için


hazırlayabiliriz.

SUB AH, AH ; AH temizleniyor


MOV AL, 6 ; ilk değer AL’de
ADD AL, 8 ; AL + 08h işlemi yapıldı sonuç 0Eh
AAA ; AX = 0104h
OR AL,30h ; AL = 34h

34h artık ascii olarak 4 demektir. Tabi burada AH kaydedicisini de kontrol edip şayet 01
ise bu değeri de 30h ile OR işlemine tabii tutmalıyız. Bu tür işlemler için genelde ekrana
tek bir karakter basma fonksiyonu kullanılır yani AL’deki değer teker teker ekrana
bastırılır. Henüz mantıksal komutları görmedik bu yüzden aşağıdaki programı tam olarak
anlamayabilirsiniz, bu yüzden açıklamalara dikkat edin.
Şekil - Makine dilinden insan diline dönüşüm

Pekte güzel bir ekran çıktısı olmasa da bu program dönüşümleri anlamak için iyi bir
örnektir. Yukarıdaki şekilde program ilk çakıştırıldığında klavyeden sırayla 2 ve 3 girilmiş
ve sonuç 5 olarak ekrana basılmış. Daha sonra 7 ve 8 değerleri girilmiş ve sonuç 15
olarak ekrana basılmış. Gerçektende sonuçlar doğru :) Unutulmaması gereken bir nokta
AAA komutunun C bitini etkilediğidir, şayet AAA komutundan önceki bayrakların durumu
sizin için önemliyse bayrakların durumunu saklamanız gerekir.

AAS (Ascii Adjust after Subtract) komutuda AAA gibi çalışır, çıkartma komutundan sonra
sonucu ASCII’ye ayarlamak için kullanılır. AAS komutu sekiz bitlik AL kaydedicisinin
yüksek değerlikli 4 bitini kontrol eder, şayet AF bayrağı 1 ise (başka bir deyişle bu 4 bit
Ah...Fh arasında ise) AL’den 6, AH’tan 1 çıkartılır. AH’tan 1 çıkartmak normalde 00 olan
AH’ı FF yapmak yani rakamın negatifliğini ayarlamak demektir.

mov al,35h ; ascii 5


sub al,31h ; 5 - 1 = 4
aas ; AF=0 olduğundan bir değişiklik olmaz sonuç hala 04h
or al,34h ; ascii 4

sonucun negatif çıktığı bir örnek;

mov al,34h ;ascii 4


sub al,38h ;ascii 8 -- sonuç FCh (negatif)
aas ; AX = FF06 (yanlış sonuç)

yukarıdaki gibi bir durumda SUB komutu ile A ve C bayrakları set (1) olur. Bu durumda
sonucu doğrudan 30h ile OR işlemine tabi tutmak hatalı olacaktır. FCh sonucu desimal -4
değerine eşittir bu yüzden burada programcı OR komutu ile doğru ascii değeri ekrana
yazdırmadan önce NEG komutu ile tersini alabilir. Bu durumda sonuc 4 olacaktır. Tabiki
bu 4 değeri ekrana yazdırılmadan önce önüne - işareti konulmalı.

mov al,34h
sub al,38h
jnc devam
neg al
devam: aas
or al,30h

yukarıdaki kod parçası sonuç negatif olsa da pozitif olsa da, sonucun mutlak değerini
doğru bir şekilde ascii değere dönüştürür.

Unutmayalım ki klavye bize ekran text moundayken daima ascii değerler verecektir, bu
çarpma ve bölme işlemlerinde de problem oluşturur. Klavyeden girilen değerlere sanki 30
eklenmiş gibi geleceğinden çarpma işleminin sonucu girilen değerde bir modifikasyon
yapmadığımız sürece yanlış hesaplarız. Bu tür modifikasyonları yapmak için başka
mantıksal komutlara ihtiyacımız olacak, bu yüzden bu komutları anlatmadan DAA, DAS,
AAD ve AAM gibi komutları açıklamak istemiyorum.
80x86 KOMUT SETİ (Bölüm 4)

Bu makalemde 80x86 mantıksal (logical) komutlarından bazılarını anlatmaya çalışacağım.


Ayrıca VGA 80x25 text mode ekraın hafız alanına direk erişim yapacağız.

80x86 KOMUT SETİ (Bölüm 4)

Mantıksal (logical) komutlar AND, OR, XOR ve NOT adıyla bilinen ve matematiksel
hesaplamalarda çok kullanılan komutlardır.

AND komutu

Yapı olarak AND (VE) mantığı 1 ve 0’lar ile ifade edilirse;

1 ve 1 = 1
1 ve 0 = 0
0 ve 1 = 0
0 ve 0 = 0

sonuçlarını üretir.

Bu komutu assembly programcıları genelde maskeleme işlerinde kullanırlar. Örneğin 1


byte’lık değerin 7, 6, 5, ve 4. bitlerini göz ardı etmek için aşağıdaki gibi bir program
parçası yazılabilir.

mov al, A5h


and al, 0Fh

bu işlemlerden sonra AL’in yüksek değerli 4 biti (nibble) sıfırlanacaktır yani AL binary
olarak ifade edersek 0000 0101 olacaktır. Buna düşük değerlikli 4 bite dokunmadan diğer
bitleri sıfırlamakta denilebilir.

Bu komutu elektronikçiler çok kullanırlar, mesela paralel porttan alınacak olan verinin
sadece 5 bitini kontrol etmek için;

mov dx,378h ; paralel port adresi


in al, dx ; bu adresten bilgiyi al
and al, 0010 0000b ; 5. biti kontrol et (maskele)
jnz devam ; 5. bit 1 ise "devam" a dallan
.
.
.
.
devam: .
;devam komutları
.

Diyelim ki AL’ye IN komutu ile alınan byte 1011 0001 olsun, bu durumda;

1011 0001
0010 0000
v
__________
0010 0000

yukarıdaki işlem yapılır ve AND komutu bayrak kaydedicisinin Z bitini 0 olarak kurar,
çünkü sonuç 0’dan farklıdır. İşte burada maske olarak sadece 5. biti 1 olan bir byte
seçilmiştir.

Not: Windows XP altında paralel port’a doğrudan erişim işletim sisteminin kerneli
tarafından engellendiğinden bu programı windows XP öncesi işletim sistemlerinde
çalıştırabilirsiniz. IN ve OUT komutlarının kullanımı sonraki makalelerin konusudur.

AND komutunun kullanım formatları;

and hedef, kaynak ;hedef := hedef & kaynak

and reg, reg


and mem, reg
and reg, mem
and reg, imm
and mem, imm
and eax/ax/al, imm

OR komutu

Mantıksal veya işlemini gerçekleştirir,

1 | 1 = 1
1 | 0 = 1
0 | 1 = 1
0 | 0 = 0

doğruluk tablosu yukarıdaki gibidir. Kullanım formatları AND komutundaki gibidir.

XOR komutu

XOR olmadan şifreleme işlemleri sanırım çok zor yapılırdı. Çok fazla kullanım alanı
olmakla beraber veri paketleme ve şifreleme işlemleri için hayat kurtarıcı bir komuttur.
eXclusiveOR (özel veya) kelimesinin kısaltmasıdır. Doğruluk tablosu;

1 ^ 1 = 0
1 ^ 0 = 1
0 ^ 1 = 1
0 ^ 0 = 0

Ben genelde öğrencilerime bu komutun yaptığı işi anlatırken "aynılarda 0 farklılarda 1"
sonucunu veren mantıksal ifade derim.

XOR komutu ile şifreleme ve paketleme işlemleri için örnekler şu anda belki de sizin için
biraz ağır kaçabilir, bunun yerine swap (yer değiştirme) ve kaydedici sıfırlama örneklerini
XOR komutunu ile neler yapılabildiğine örnek teşkil edeceğini düşünüyorum.

xor ax,ax ; ax=0 olur ve "mov ax,0" dan kat kat hızlı çalışır

;AX = 1234h ve BX=9876h olsun


xor ax, bx
xor bx, ax
xor ax, bx

; bu 3 komut sonrasında AX = 9876h ve BX = 1234h olur.

XOR komutunun kullanım formatları AND ve OR komutlarınınkiyle aynıdır.

Bayrakların Durumu

Yukarıda anlattığım bu üç komut bayrak kaydedicisini aşağıdaki gibi etkiler;

Carry bayrağını 0 yaparlar,


Overflow bayrağını 0 yaparlar
Zero bayrağını şayet sonuç 0 ise 1 yaparlar ve aksi durumlarda bu bayrağı
0 yaparlar
En Yüksek değerlikli bit’i Sign bayrağına kopyalarlar
Parity bayrağını (sonuçtaki 1’lerin sayısı çift ise) 1 yaparlar
Auxiliary carry (Ara Elde) bayrağının durumunu değiştirirler.

NOT komutu

NOT komutu operandının mantıksal tersini alır.

Kullanım formatları aşağıdaki gibidir;

not reg
not mem

; BL kaydedicisinin değeri 15h olsun;

not bl

;bl = 0001 0101b iken bu komuttan sonra 1110 1010b olur.

NOT komutu hiçbir bayrağı etkilemez.

Şimdi Uygulama Zamanı

Bu makalemde sizlere örnek teşkil etmesi için konsol ekranındaki görüntüyü (tüm
yazıları) önce karma karışık bir hale getiren daha sonrada tekrar orijinal haline çeviren bir
programı anlatmayı düşünüyorum. Öncelikle Windows’un cmd.exe yada komut istemi ile
açılan pencerenin görüntüsünün aslında B800:0000 adresinin bir yansıması olduğunu
hatırlatmak isterim. Debug ile bu alana göz atalım.
Şekil 1 - Aslında ekran yansıması da hafızadaki byte’lardan ibarettir.

Konsol penceresi açılınca şayet işletim sisteminiz Windows XP ise "Microsoft Windows
XP....." ile başlayan bir yazı görülür. Debug’ı çalıştırıp b800:0000 adresini ekrana
döktüğümüzde bu yazı hala aynı yerindedir. B800h segmenti de burada görünen yazının
bulunduğu text video alanıdır. Şekilde B800:0000 adresindeki 4Dh değerinin aslında M
harfi olduğunu ve C karakterininde yine aynı segmentte bir değer olduğunu
görüyorsunuz. Yukarıdaki resimdeki hafıza dökümü, en üst satırın tamamını ve sonraki
satırın yarısından biraz fazlasını gözler önüne seriyor. Hafıza dökümünde tek numaralı her
offsette (0001h,0003h,00F3h gibi) 07h değerini görüyorsunuz. Bu text mode özelliği olup
siyah zemin üzerine beyaz yazı anlamına gelir. Gerçektende ekrandaki karakterlerin hepsi
beyaz ve zemin siyah değil mi?

Konsol ekranının tamamı karakterlerle dolsa, ekranda kaç karakter görürüz? Tabi ki
80x25=2000 adet. Şimdi bu karakterlerin tek tek adreslerine erişip (özellik içeren 07h
byte’larını atlayarak) bunları lojik bir işlemden geçirsem ekranın o anki görüntüsü değişir,
hem de bizim boş olarak tabir ettiğimiz ama aslında 20h olan yerler bile. Böyle bir
durumda ekranda karman çorman anlaşılmaz bir görüntünün belirmesi olası bir
durumdur.

Peki değiştirdiğim her byte’ı tekrar eski haline getirebilir miyim? Bütün bunların cevabı
aşağıdaki programda saklı.
Şekil 2 - 80x25 text mode da görünen ekrana müdahele.
Şekil 3 - Program çalıştırılmadan önceki durum.

Şekil 4 - Ekrandaki yazılara ne oldu böyle?


Şekil 5 - Klavyeden bir tuşa basıldığında görüntü tekrar normale döner.

Aslında video işlemleri için daha pratik interrupt’lar mevcuttur, burada anlatmaya
çalıştığım, "şayet assembly dili ile hafızanın her yerine erişebilecek deneyime sahipseniz
yapamayacağınız şey yoktur" gerçeğiydi.

Bu programda özellikte, text video alanının segment adresini DS kaydedicisine yüklenişini


ve bu alanın kaydedici dolaylı (register indirect) adresleme modu ile sanki kendi
tanımladığımız bir data alanı gibi kullanılışını incelemenizi tavsiye ederim.

Düşük seviyede sağlam ve hızlı kodlar yazmanız dileğiyle... :)

80x86 KOMUT SETİ (BÖLÜM 5)

Makalemizde, 80X86 komut sistemine ait aritmetik ve lojik komutları açıklamaya devam
edeceğiz.

AAM ve AAD Komutu:

AAM komutu, çarpma işlemi sonucunda, AX registerinde oluşan değeri ASCII formata
dönüştürme amaçlı kullanılır. Daha önce anlatılan AAA ve AAS komutlarında olduğu gibi
AAM ve AAD komutlarıda operandsız yazılırlar. AH ve AL registerlerini gizli operand olarak
kulanmaktadırlar.
AAM komutu, çarpma işlemi ile ax registerinde oluşan değerin ardından kullanılır. Al’nin
değerini desimal 10 ile böler, elde edilen değeri ah’a aktarır. Bölümden kalan değeride
tekrar al’ye aktarır.Yapılan işlemin sonucunda, elde edilen sayının sol dijiti ah’da, sağ
dijiti ise al’de yer alır.AAM komutu, parity, sign ve zero flaglarını al’ye aktardığı değere
göre uygun şekilde etkiler.
Örneğimizde mul komutu, çarpım sonucu olan 00 51 değerini ax registerine aktarırken,
aam komutuda bu değeri 00 81 olarak ASCII formatına dönüştürmüştür.

mov al,09 ;al’nin değeri 9


mov bl,09 ;bl’nin değeri 9
mul bl ;ax = 00 51h
aam ;ax = 08 01h

51h = 0101
I. adım
0001b
al
registerinin
_________________
değerini
= 08h
0Ah’a böl
0Ah = 0000
0Ah=d’10’
1010b
II.adım
bölümün
sonuç
ah = 08h
değerini
al = 01h
ah’a ,
kalan’ı
al’ye aktar
III.adım
Sonuç ax = 0801h

AAD komutu ise bölme işleminde bölünenin sonucunda, AX registerinde oluşan değeri
ASCII formata dönüştürme amaçlı kullanılır.AAD komutu, bölünen değerin yüksek seviyeli
kısmını (ah registerinin içeriğini) desimal 10 ile çarpar ve al (düşük seviyeli kısmı) ile
toplayarak al içerisine aktarır.
Yapılan işlem sonucunda ah registeri sıfırlanır, al registerinde bulunan BCD sayısı, başka
bir BCD sayısı ile bölünebilir duruma getirilmiş olur. AAD komutu, parity, sign ve zero
flaglarını al’ye aktardığı değere göre uygun şekilde etkiler.

Örneğimizde desimal 35 sayısı, desimal 5’e bölünmekte ve sonuç yine desimal olarak
elde edilmektedir. AAD komutunu kullanarak, ax üzerine paketlenmemiş olarak aktarılan
sayıyı, bölme işlemi için uygun hale getiriyoruz.

mov ax, 0305 ;ax = 03 05


aad ;ax = 00 23
mov bl,05 ; bl’ye al değerini koy
div bl ;ax = 00 07

I. adım
ah 03h = 0000 0011b
registerinin 0Ah = 0000 1010b
içeriğini *_________________
0Ah ile çarp 1Eh = 0001 1110b
0Ah=d’10’
II.adım 1Eh = 0001 1110b
çarpım 05h = 0000 0101b
sonucundaki +_________________
değere 23h = 0010 0011b
al’nin
değerini
ekle ve
al’ye yaz
23h = 0010 0011b
III.adım
_________________
Sonuç
=7
05h = 0000 0101b

DAA ve DAS komutu:

DAA komutu ile yapılan toplama işlemi sonucunda her bir byte’da iki adet desimal dijit
bulunur. ADD komutunun arkasından kullanılan DAA komutu, al’nin içeriği olan değere ve
AF (auxilary flag) ile CF (carry flag)’nin durumuna bağlı olarak iki farklı şekilde işleme
sokulur.
I. Durum: al registerinin düşük seviyeli 4 biti 9’dan büyük veya AF set ise, al registeri 6
ile toplanarak, AF set edilir.
II.Durum: al registerinin yüksek seviyeli 4 biti 9’dan büyük veya CF set ise, al registeri ile
60h toplanarak CF set edilir.

Örneğimizde 9 ve 5 değerlerini toplayarak komutumuzla istenilen sonucu elde edeceğiz.

mov al,09 ; al = 9
add al,05 ; al = 0E
daa ; al =14

I. adım
09h = 0000 1001b
al
05h = 0000 1001b
registerlerini
+__________________
topla
0Eh = 0000 1110b

II.adım
al
registerinin
düşük
seviyeli biti 0Eh = 0000 1110b
0’dan küçük 06h = 0000 0110b
olduğu için +_________________
6 değerini 14h = 0001 0100b
ekledik ve
sonuç
değere
ulaştık

DAS komutu ile yapılan çıkartma işleminin al registeri üzerindeki sonucuna ve auxuiliary
ile carry flaglarının durumuna bağlı olarak iki farklı şekilde kullanılır.
I. Durum: al registerinin düşük seviyeli 4 biti 9’dan büyük veya AF set ise, al
registerinden 6 çıkartılarak, AF set edilir.
II.Durum: al registerinin yüksek seviyeli 4 biti 9’dan büyük veya CF set ise, al
registerinden 60h çıkartılarak CF set edilir.
DAS komutu, auxiliary, carry, parity, sign ve zero flaglarını etkiler.
Örneğimizde, desimal 13 ’den 5 ’i çıkartma işlemini al registerinde yaptığımız için sonuç
0E oluyor. al registerindeki değerin desimal sayı olması için çıkartma komutunun
ardından DAS düzenleyici komutu kullanılıyor.

mov al,13 ;al=13h


mov bl,05 ;bl=05h
sub al,bl ;al = 0Eh
das ;al = 08

13h = 0001
I. adım 0011b
al 05h = 0000
registerindeki 1001b
değerden bl -
registerindeki __________________
değeri çıkart 0Eh = 0000
1110b
II.adım
al
registerinin
düşük 0Eh = 0000 1110b
seviyeli biti 06h = 0000 0110b
0’dan küçük -
olduğu için 6 __________________
değerini 08h = 0000 1000b
çıkardık ve
sonuç değere
ulaştık

CBW ve CWD Komutu:

CBW:Byte’ın worde çevrilmesi.


CWD:Word’un double worde çevrilmesi

Aritmetik işlemler sırasında operandlar farklı uzunluklara (byte,word veya double word)
sahip olabilirler. Bu gibi durumlarda, işlem öncesinde operandların uzunluklarının
düzenlenmesi gerekir.Küçük uzunluğa sahip operandın uzunluğunu, büyük uzunluğa
sahip operandın uzunluğuna denkleştirilmektedir.İşaretsiz sayılarla temsil edilen
operandlar, rahat bir şekilde düzenlenir. Fakat işaretli sayılarla temsil edilen operandlarda
değişim o kadar rahat olmamaktadır.
İşaretli sayıları içeren operandların uzunluklarının düzenlenmesi için CBW ve CWD
komutları geliştirilmiştir. CBW komutu al ve ah, CWD komutu ise ax ve dx’i gizli operand
olarak kullanırlar.
CBW komutu, al içerisindeki işaretli sayının işaret bitini ah’ın tüm bitlerine, CWD komutu
da ax içindeki sayının işaret bitinin değerini dx’in tüm bitlerine aktararak sonuca
ulaşmamızı sağlar.

TEST Komutu:

Test komutunun çalışma prensibi ve flagları etkileme biçimide dahil olmak üzere And ile
aynıdır. Farklı olarak, komut ile birlikte kullanılan operandların değerlerini değiştirmez.
Diğer lojik komutlarında olduğu gibi, carry, owerflow bayrakları test işleminin sonrasında
reset edilirler, auxiliary dışındaki diğer bayraklarda işlem sonucuna uygun şekilde
etkileneceklerdir.

Farkı olmamasına rağmen, hangi durumda and, hangi durumda test komutunu
kullanmalıyız sorusu aklımıza gelecektir. Örneğimizi inceleyelim;

and
al,01
I. Durum jz *
...
...
test
al,01
II.Durum jz *
...
...

I. ve II. Durumda, al registerinin değeri desimal 1 ile karşılaştırılmaktadır; al


registerindeki değer desimal 1’e eşit ise
I.Durumda: al’nin değeri korunacak ve zero bayrağının reset durumuna gelmesine sebeb
olacak, bu sayede jz’yi izleyen komut ile program devam etmiş olacaktır. Fakat değer
1’den farklı ise sonuç 0 olarak al’ye yerleştirilecek ve zero bayrağı set edilecektir,
sonucunda ise * yerine kullanılan adrese dallanılacak ve program bu adreste bulunan
komut ile sürecektir.
Fakat al’nin değeri 1’e eşit değil ve başka işlemler için bize gerekli ise sorun
çıkacaktır.Sonuçtan da anlaşıldığı gibi, al registerinin karşılaştırma öncesi değeri,
karşılaştırma sonrası adımlarda da gerekli ise test komutunu tercih etmeliyiz.

SHR ve SHL Komutu:

SHR Komutu ile, soldaki operandın en düşük seviyeli biti carry bayrağına kopyalanır ve
operandın tüm bitleri sağa doğru 1’er bit kayar. En soldaki bitin değeri 0 yapılır. Bu işlem
SHR ile verilen ikinci operandın değeri kadar tekrarlanır.

Örneğimizde, 30h değerini iki kere 1’er biti sağa kaydırarak sonucun nasıl değiştiğini
inceleyeceğiz.

mov al,30h ;al=30h


shr al,01 ;al=18h
shr al,01 ;al=0Ch

Şekil 1 - Shr komutu ile bitlerin sağa kaydırılması

Adımlar Hexadesimal Binary Desimal CF


I. adım:
al’ye
30h al=30h 00110000 48 -
değerini
koy
II.adım:
değerleri
1 bit
sağa
kaydır,
en al=18h 00011000 24 0
düşük
seviyeli
biti
CF’na
taşı
III.adım:
değerleri
1 bit
sağa
kaydır,
en al=0Ch 00001100 12 0
düşük
seviyeli
biti
CF’na
taşı

Programda da görüldüğü gibi her bir sağa kaydırma işlemi ile sayıyı ikiye bölmektedir.Her
bir shr işlemi sonrasında carry bayrağı 0 ise çift sayı, 1 ise tek sayıdır.

SHL Komutu ile, soldaki operandın en yüksek seviyeli biti carry bayrağına kopyalanır ve
operandın tüm bitleri sola doğru 1’er bit kayar. En soldaki bitin değeri 0 yapılır. Bu işlem
SHL ile verilen ikinci operandın değeri kadar tekrarlanır.

Örneğimizde 0Ch değerini iki kere 1’er bit sola kaydırarak sonuç üzerindeki değişiklikleri
inceleyeceğiz.

mov al,0Ch ;al=0Ch


shr al,01 ;al=18h
shr al,01 ;al=30h

Şekil 2 - Shl komutu ile bitlerin sola kaydırılması

Adımlar Hexadesimal Binary Desimal CF


I. adım:
al’ye
0Ch al=0Ch 00001100 12 -
değerini
koy
II.adım:
değerleri
1 bit al=18h 00011000 24 0
sola
kaydır,
en
yüksek
seviyeli
biti
CF’na
taşı
III.adım:
değerleri
1 bit
sola
kaydır,
en al=0Ch 00110000 48 0
yüksek
seviyeli
biti
CF’na
taşı

Programda da görüldüğü gibi her bir sola kaydırma işlemi ile sayıyı iki ile çarpmış
oluyoruz.

RCR ve RCL Komutu:

RCR Komutu, işleme tabi tutulan bitlerin herbirini bir pozisyon sağa kaydırır. Düşük
seviyeli bit pozisyonundaki değer carry bayrağına, carry bayrağının değeride yüksek
seviyeli bit pozisyonuna aktarılır. İşlem ikinci operand değeri kadar tekrarlanır.

Şekil 3 - Rcr komutu ile bitlerin sağa kaydırılması

RCL Komutu, işleme tabi tutulan bitlerin herbirini bir pozisyon sola kaydırır. Carry
flağının değeri düşük seviyeli bit pozisyonuna, yüksek seviyeli bit pozisyonundaki değer
ise carry flağına aktarılır. İşlem ikinci operand değeri kadar tekrarlanır.

Şekil 4 - Rcl komutu ile bitlerin sola kaydırılması

Şekil 5 - Programın ekran görüntüsü

Bu bölümde anlattığımız aam,das,shr ve rcl komutlarını kullanarak örnek bir program


hazırladım. aam komutu kullanılan prosedürde shr komutunu işleme katmak istediğimiz
al registerinin içeriğini bir bit sağa kaydırmak için, das ile ilgili prosedürde ise rcl
komutuyla al registerinin içeriğini bir bit sola kaydırdım.
.model small
.stack 64
.data

aamsonuc db ’00$’,10,13
dassonuc db ’00$’
knm db 10,13,’$’

;....................................................
.code
mov ax,@data
mov ds,ax
mov es,ax

call aamornek
call dasornek

;....................................................
aamornek proc

mov al,12h
shr al,01 ;al registerinin içeriğini bir bit sağa kaydır, al=9h

mov bl,9h ;bl=9h


mul bl ;ax=51h
aam ;ax=81h

add ax,3030h ;al ve ah değerlerini ascii karakterlere çevir.

mov byte ptr [aamsonuc],ah


mov byte ptr [aamsonuc+1],al ;ax registerini sonuc dizisi içerisine yerleştir.

mov ah,09h
lea dx,aamsonuc ;sonucu yaz
int 21h

mov ah,09h
lea dx,knm ;alt satıra geç
int 21h

aamornek endp

;....................................................
dasornek proc

mov al,28h
rcr al,01 ;al registerinin içeriğini bir bit sola kaydır, al=14h

mov bl,05h ;bl=5h


sub al,bl ;al=Fh
das ;al=9h

add al,30h ;al değerini ascii karaktere çevir.

mov ah,02h
mov dl,al
int 21h ;sonucu yaz

dasornek endp
;....................................................
mov ah,4ch
int 21h
end ;programdan çıkış
;....................................................

Programımız, anlatılan komutların sağlaması olarak da değerlendirilebilir. Ben kodu


çalıştırdım ve gördüm ki kullandığım komutlar işini başarıyla gerçekleştiriyor.
Makalemizde aritmetik lojik komutların çalışma şekillerini, bayraklar üzerindeki
etkilerini anlatmaya çalıştım. Benzer özelliklere sahip komutların kullanılması sizlerin
tercihine kalmış, gerçekleştireceğiniz uygulamalar ve elde etmek istediğiniz performans
doğrultusunda ihtiyacınıza en uygun olanı belirleyebilirsiniz.

80x86 String Komutları

80x86 String Komutları (Bölüm 4)

Çeşitli bilgilerin birleşerek biraya gelmelerine string denir. �Merhaba� kelimesi bir
string�dir. ASSEMBLY�de stringleri kontrol edebilen komutlar vardır ve bu komutlar
hafıza blokları üzerinde çalışırlar.

Bu makalemizde 80 X 86 ailesinde kullanılan string komutlarını (MOVS, CMPS, LODS,


SCAS, STOS ) incelemeye çalışacağız, komutlarımız byte, word ve double word dizileri
üzerinde işlemler gerçekleştirmek için kullanılmaktadır. Aşağıda ilgili komutlar ve
kullanım şekillerini tablo halinde görebilirsiniz.

Komut Kullanım Şekli


MOVS movsb,movsw,movsd
CMPS cmpsb,cmpsw,cmpsd
SCAS scasb,scasw,scasd
STOS stosb,stosw,stosd
LODS lodsb,kodsw,lodsd

String komutlarını ile işlem yaparken, Indis registerleri olarak adlandırılan SI ve DI


registerlerinden, bellekteki bir bilgiyi bir adresten başka bir adrese taşırken kullanıyoruz.
SI registerine taşınacak bilgi dizisinin başlangıç adresini, DI registerine de diziyi taşımak
istediğimiz belleğin başlangıç adresini yerleştirerek yapmak istediğimiz işlemi
gerçekleştirebiliriz.

Indis registerlerinin program içeirisinde kullanım şeklini gösteren örneğimizi


inceleyebilirsiniz.
Byte1 db �a� ;byte1 için ayrılan alana �a� değerini koy
Byte2 db �*� ;byte2 için ayrılan alana �*� değerini koy

Movs Byte2,Byte1 ;byte2 bilgi dizisini, byte1 bilgi dizisine kopyala

Lea di,Byte2 ;taşımak istediğimiz belleğin başlangıç adresi di registerine koy


Lea si,Byte1 ;taşınacak bilgi dizisinin başlangıç adresini si registerine koy

Movsb ;si registerinde bulunan byte dizisini di registeri ile gösterilen adrese
byte olarak taşı

String işlemlerinde kullanılan komutları daha detaylı incelemeye başlayalım;

MOVS Komutu:

movs komutunun kullandığı veri tipine göre farklı kullanım şekilleri vardır, data
segmentinde ve ofset adresi SI rejisterinde bulunan byte / word / double word dizilerini
ES ve DI registerleri ile gösterilen adrese taşır. CX registerinde tutulan deger adedince
taşıma işlemi tekrarlanır.movsb ile byte dizileri, movsw ile word dizileri ve movsd ile
double word dizileri üzerinde işlemler yapılır.
REP: kendisinden sonra gelen string komutunun cx registerinin değeri 0 olana dek
tekrarlar.

Komut Anlamı İşlem


si�de bulunan
byte dizisini, cx
registerinde
movsb byte dizisi taşı
tutulan değer
adedince di
registerine taşı
si�de bulunan
word dizisini, cx
registerinde
movsw word dizisi taşı
tutulan değer
adedince di
registerine taşı
si�de bulunan
double word
dizisini, cx
movsd double dizisi olarak taşı registerinde
tutulan değer
adedince di
registerine taşı

CMPS Komutu:

REPE, REPZ, REPNE ve REPNZ komutlarının parametresi olarak bellekteki iki stringi
birbirleriyle karşılaştırma işleminde kullanılır. Stringlerden biri Data Segmentte diğeri ise
Extra Segmentte tutulur. Data Segmentindeki stringin ofset adresi SI registerine, Extra
segmentteki stringin ofset adresi ise DI registerine yerleştirilir. Cx registerindeki değer
adadince karşılaştırma işlemi tekrarlanır.
Not:REPE: eşit oduğu zaman tekrarla REPZ: zero flag �0� oldugu zaman tekrarla
REPNE: eşit olmadığı zaman tekrarla REPNZ: zero flag �0� olmadıgı zaman tekrarla

Komut Anlamı İşlem


data
segmentte ve
extra
segmentte
cmpsb byte dizisi karşılaştır tutulan byte
dizisini, cx
registeri
adedince
karşılaştır
data
segmentte ve
extra
segmentte
cmpsw word dizisi karşılaştır tutulan word
dizisini, cx
registeri
adedince
karşılaştır
data
segmentte ve
extra
segmentte
tutulan
cmpsd double word dizisi karşılaştır
double word
dizisini, cx
registeri
adedince
karşılaştır

LODS Komutu:

Lods komutu string komutları içinde tektir. Komut öncesinde herhangi bir komut
kullanmaya gerek yoktur. Ofset adresi DS:SI rejisterinde bulunan byte / word / double
word dizilerini al, ax, eax registerlerine, si registerinin değerini arttırarak veya azaltarak
kopyalar.

Komut Anlamı İşlem


ofset adresi ds:si
registerinde
bulunan byte
dizisini, si
lodsb byte dizisi kopyala
registerinin
değerini
arttırarak, al
registerine taşı
ofset adresi ds:si
lodsw word dizisi kopyala registerinde
bulunan word
dizisini, si
registerinin
değerini
arttırarak, ax
registerine taşı
ofset adresi ds:si
registerinde
bulunan double
double word dizisi word dizisini, si
lodsd
kopyala registerinin
değerini
arttırarak, eax
registerine taşı

SCAS Komutu:

Ofset adresi DS:SI rejisterinde bulunan byte / word / double word dizileri içerisindeki
değerle al, ax, eax registerlerindeki değerleri karşılaştırarak arama yapmak için kullanılır.
Scas komutu AF, Cf, OF, PF, SF ve ZF bayraklarını ayarlarak arama işlemini gerçekleştirir.

Komut Anlamı İşlem


ofset adresi
ds:si
registerinde
bulunan byte
dizisi
scasb byte dizisi içerisinde ara içerisindeki
değerle, al
registerindeki
değeri
karşılaştırarak
arama yap
ofset adresi
ds:si
registerinde
bulunan word
dizisi
scasw word dizisi içerisinde ara içerisindeki
değerle, ax
registerindeki
değeri
karşılaştırarak
arama yap
ofset adresi
ds:si
registerinde
bulunan
double word dizisi içerisinde double word
scasd
ara dizisi
içerisindeki
değerle, eax
registerindeki
değeri
karşılaştırarak
arama yap

STOS Komutu:

Stos komutu es:di ile gösterilen adreste bulunan akümülator içerisinde belirtilen değeri
saklar. Bu işlemden sonra yön bayrağının durumuna göre işlemci di registerini arttırır
veya azaltır. Stos komutu byte, word yada double word üzerinde işlemler yapmak için
uygundur.

Komut Anlamı İşlem


es:di ile
gösterilen
adresteki al
stosb al içerisinde byte dizisi sakla registerinde,
istenilen
byte dizisi
saklanır
es:di ile
gösterilen
adresteki ax
stosw ax içerisinde word dizisi sakla registerinde,
istenilen
word dizisi
saklanır
es:di ile
gösterilen
adresteki
eax
eax içerisinde double word
stosd registerinde,
dizisi sakla
istenilen
double word
dizisi
saklanır

Şekil 1 - Programın Ekran Görüntüsü.

Örneğimizde, kelime içerisindeki küçük harfler büyük harflere dönüştürülecek.


Dönüştürme işleminde kucuk harf olup olmadığını anlamak için �a = ASCII 61h� oldugu
için bu değer ile karşılaştırma yapacağız. Eğer harf bu değerden buyuk ise küçük harf
olduğunu anlayacağız ve harfi buyuğe çevirmek için 20h değerini çıkaracağız. Harf
değerimizden küçükse büyük harf olduğuna karar vereceğiz ve çıkarma işlemi
yapmayacağız. Dönüştürme işleminden sonra programı terk edeceğiz.

Programın ekran çıktısından da gördüğünüz gibi klavyeden ilk harf grubunun tamamı
küçük harf ikinci harf grubunun bir elemanı ise büyük harf olarak girilmiştir. Sonuçta
enter tuşuyla harf girişi tamamlanıp sonuç değeri elde edilmiştir. Umarım string
komutlarının kullanımı konusunda faydalı olur.

.model small
.stack 64
.data

snc db 10,13,� �,�$�,10,13 ; sonuc için ayrılan alan

.code

mov ax,@data
mov ds,ax
mov es,ax

dizi_al:
mov ah,01 ;klavyeden karakter isteniyor
int 21h ;girilen al�de

cmp al,13 ;al�deki değeri enter ile karşılaştır


jz yaz ;enter�e basılmışsa yaz�a atla

inc si ;si ile kelime içinde bir sonraki karaktere ilerle

cmp al,61h ;al içindeki değerle a=61h karşılaştır


jg degis ;girilen değerden buyukse degis�e atla
jl sbt ;girilen değerden kucukse sbt�e atla

degis:
sub al,20h ;al değerinden 20h çıkar

sbt:
sub al,0h ;al değerinden 0h çıkar

mov [snc+si],al ;kelime içerisinde sıradaki harfe son al değerini yukle

jmp dizi_al ;dizi başlangıcına atla

yaz:
mov ah,09h ;ekrana snc�de tutulan değer yazılıyor
lea dx,snc
int 21h

dosa: ;programı terket


mov ah,4ch
int 21h
end

Aslında string komutları ile yapılan işlemler bilgisayar kullanıcılarına o kadar yakın ki,
mesela msword uygulamalarında kullanılan kopyala-yapıştır gibi.
Yorumlarınızı bekliyorum.
Stack Memory (Yığın Hafıza Bölgesi)

Bu makalede stack (yığın) hafıza bölgesini anlatmaya çalışacağım.

Stack memory geçici değerlerin saklandığı bir hafıza alanıdır. Mikroişlemci SS:SP
kaydedicileri ile bu hafıza alanına erişir. SS stack alanının segment adresini tutarken SP
bu alandaki offsetleri işaret eder. Stack hafıza alanını kullanmama diye bir lüksümüz yok,
bu makaleye kadar stack memory ile ilgili hiç bir komut yazmasakta, bu hafıza alanını
yadığımız tüm programlar kullandı.

Bununla beraber stack memory’yi kendi isteklerimiz doğrultusunda da kullanabiliriz.


Bunun için bir çok komut mevcuttur. PUSH, POP, CALL, RET bunlardan bazıları ve bu
makalede anlatılacaklar arasındadır.

LIFO

Mikroişlemci stack memory’yi diğer hafıza alanlarını adreslerken kullandığı yöntemden


biraz daha farklı adresler. Öncelikle stack hafıza alanına yüklenen bilgiler LIFO (Last In
First Out) yani ilk giren son çıkar yada ilk yüklenen bilgi en son alınır düzenine göre
sıralanırlar.

Şekil 1 - Stack (Yığın) hafıza.

Yukarıdaki şekilde kırmızı bloğu stack memory olarak düşünebilirsiniz. BU alanın


büyüklüğünü kod yazarken .stack talimatı ile belirliyoruz, .stack 32, .stack 100h gibi.
Geçici verilerinizi saklamak için ne kadar alana ihtiyacınız varsa o kadar stack tahsis
edebilirsiniz.

Yine yukarıdaki şekilde orijinal SP yığın boşken SP kaydedicisinin aldığı değerdir. Mesela
.stack 32 diye direktif verdiyseniz SP=0020h ı gösterir. Yığına veri itildikçe SP’nin değeri
azalır, bu değerler yığından çekildikçe SP artar. x86 mimarisinde yığın hafızaya 16 bitlik
çalışma modunda 16 bitlik değerler, 32 bitlik çalışma modunda ise 32 bitlik değerler
saklayabilirsiniz.
PUSH ve POP komutları

Stack memory’de verilerinizi saklamak için kullanılan komutlardır. Stack’ta saklanan


veriler veya adres bilgileri için genelde "stack’a itilen" kelimeleri kullanılır. Stack’tan geri
alınan veriler içinse "stack’tan çekilen" kelimesi kullanılır. Özetle PUSH komutu stack’a bir
veriyi itmek için, POP ise verileri çekmek için kullanılır.

Yığın hafıza alanını etkileyen komutların genel kullanım formatları aşağıdaki gibidir;

push reg16
pop reg16
push reg32 (**)
pop reg32 (**)
push segreg
pop segreg (CS hariç)
push mem
pop mem
push imm (*)
pusha (*)
popa (*)
pushad (**)
popad (**)
pushf
popf
pushfd (**)
popfd (**)

(*)- Sadece 80286 ve sonrası işlemcilerde.


(**)- Sadece 80386 ve sonrası işlemcilerde.

En çok kullanılan komutlar PUSH ve POP komutlarıdır. PUSHA ve PUSHAD tüm


kaydedicileri yığında saklamak amacıyla kullanılır, POPA ve POPAD ise saklanan bu
kaydedicileri geri almak için kullanılır. PUSHF, PUSHFD bayrak kaydedicisini saklamak
POPF ve POPFD ise bunları geri almak için kullanılır. Peki neden kaydedicilerin içeriğini
saklamak durumunda kalalım?

Programcıların programlarını düz mantıkla yazmadığını biliyorsunuz, modüler


programlama teknikleri kullanılmazsa 30-40 satırdan sonra kaynak kod anlaşılmaz hale
gelir. Günümüzdeki programlar onbinlerce hatta milyonlarca komut satırından meydana
geliyor. Bunun için programı parçalara ayırmak, modüller hazırlamak şart. Yüksek düzeyli
programlama dillerinde kullanılan, sınıf, fonksiyon gibi kavramlar hep bu amaç için
türetilmiştir. Assembly dilinde CALL ve RET komutlarıda bunu amaçlar. Hatta C dilinde ve
birçok programlama dilinde fonksiyon çağrımlarının assembly dilindeki tam karşılığı CALL
komutudur. İster assembly dilinde call komutunu kullanın isterseniz C dilinde bir
fonksiyonu çağırın, dallanma işlemi gerçekleşmeden önce geçici parametreler stack’ta
saklanır.

CALL ve RET Komutları

Bu komutları anlatmadan önce assembly dilinde prosedür (procedure) mantığını


kavramanız lazım. Bir örnekle prosedürleri anlayalım.
Şekil 2 - Prosedürler kullanılarak yapılmış bir kaynak kod..

Kaynak kodu buradan indirebilirsiniz. Bu program bir önceki makaledeki string


komutlarına örnek olmakla beraber biz burada sadeve CALL ve RET komutlarını
inceleyeceğiz. Program çalıştığında komut isteminden bir yazının girilmesini ister, şayet
yazıyı oluşturan karakterler büyük harfli ise bunları küçük harfe dönüştürür.

Programda üç tane prosedür bulunmakta ve ana prosedüre baktığınızda klasik kodlar olan
segment ayarlama ve dosa dönüşün dışında sadece CALL komutlarını görüyorsunuz. Ana
prosedürden 3 kere alt prosedürlere dallanma gerçekleşiyor. Her dallanma sırasında geri
dönüş adresi yığında saklanır. Alt prosedürlerden dönerkende ret komutu ile bu adres
geri yüklenir. Şayet dallanma bir başka segmente tapılıyorsa CS ve IP, buradaki gibi aynı
segment içindeyse sadece IP yani offset adresi yığında saklanır. Offset ve segment
adreslerinin 2 byte olduğunu düşünecek olursanız bunun stackın yapısınada uygun
olduğunu anlarsınız.

Yığın hafıza yada stackın kullanılmadığı bir program yok gibidir. Tüm kesmeler (bunlarda
bir çeşit alt prosedür gibi düşünüle bilir) çağrılmadan önce, o anki CS ve IP ayrıca tüm
kaydediciler yığında saklanmak zorundadır. Aksi halde geri dönüş yapıldığında alt
prosedürün değiştirdiği kaydediciler ile karşılaşılır, hatta bu bile mümkün olmaz çünkü
mevcut CS ve IP saklanmadan geri dönüş söz konusu değildir.

Satırlar yığın hafızayı anlatmakta biraz yetersiz kalıyor olabilir, bu konuyu anlamamış
olabilirsiniz diye sizlere birde video hazırladım. Yukarıdaki programın örnek alındığı
videoyu buradan download edip izlediğinizde sanırım problem çözülecek :)

Şunları hatırlamakta fayda var;

1- Programlar genelde 3 adet hafıza alanından oluşur, bunlar code, data ve stack tır.
2- CPU bu alanlara adresleme yaparak erişir,
3- CPU CS ve IP kaydedicilerini kodlara erişip onları işlemek için,
4- DS, SI ve DI’yı değişkenlere datalara erişmek için,
5- SS, SP ve BP’yi yığın hafıza alanına erişmek ve biçimlendirmek için kullanılır.

Örnek programımızdaki ana prosedür dışındada CALL komutunu kullanabilirsiniz, alt


prosedürleri istediğiniz kadar çağırabilirsiniz. Fakat bir prosedür içinden kendini çağırırken
dikkatli olmalısınız, yazacağınız kodlarla programın sonsuz döngüye girmesini
engellemezseniz, "memory overflow" hatasından önce "stack overflow" hatasını alırsınız.

Bu makaleyi iyice anlamak için mutlaka videoyu izleyin ve bu örnek programın kaynak
kodlarını kendiniz derleyip çalıştırın ve bu programa başka prosedürler ekleyip onları
başka prosedürlerden yada ana prosedürden çağırarak programı inceleyin. İyi
çalışmalar... :)

C ve Pascal Altinda Assembly

Assembly kodlarımızı farklı programlama dilleri içerisinde de kullanabiliriz. Daha önce


anlatılan makalelerde DOS�ta DEBUG ve TASM�ı kullandık. Bu bölümde ise,
anlatacağımız kodları pascal ve c++ builder içerisinde kullanacağız.

Sadece assembly komutlarını kullanarak programlar geliştirmek kodlar için kullanılan


satır sayısı arttıktan sonra zor olmaya başladığından farklı programlama dilleri içerisinde
assembly kodlarını alt programlar olarak yazmak daha avantajlı bir yol olur. Bu sayede
temelden bize ait olan çalışma mantığını bildiğimiz istediğimiz özelliklere sahip
fonksiyonlar geliştirme fırsatımız oluruz.

Şekil 1�de Turbo Pascal içerisindeki ASM....END; blokları arasında, Şekil 2�de
C++ Builder 6.0 içerisindeki asm{...} blokları arasında tasm�da olduğu şekliyle
kodlarımızı yerleştirebiliyoruz.

Turbo Pascal 7.0 içerisinde assembly kodlarının yerleştirildiği bloğun yapısı;


_______________________________________________________________________________________________
_____________

şekil 1: Pascal içerisindeki assembly bölümü


_______________________________________________________________________________________________
_____________

C++ Builder 6.0 içerisinde assembly kodlarının yerleştirildiği bloğun yapısı;


_______________________________________________________________________________________________
_____________

şekil 2: C ++ Builder içersindeki assembly bölümü


_______________________________________________________________________________________________
_____________

Şekil 1 ve 2�deki assembly blokları içerisinde, iki sayının toplanması işleminin nasıl
gerçekleştiğine bakalım:

Turbo Pascal 7.0�da ASM...END; ve C++ Builder 6.0�da ASM{...} blokları


arasında, komut satırından girilen değerler MOV komutu ile AL registerine aktarıldıktan
sonra ADD komutuyla toplama işlemini gerçekleştirip TOPLAM değişkenine AL
registerinin son değeri olarak aktarıyoruz, assembly bloğu dışında programlama dilinin
fonksiyonları ile sonucu görüntülüyoruz.
şekil 3: Turbo Pascal 7.0 ile assembly kod örneği

şekil 4: Turbo Pascal 7.0 Ekran Görüntüsü

C++ Builder içerisinde kullanacağımız assembly bloğu dışındaki component ve


fonksiyonları inceleyelim.

C ++ Builder Componentleri :

Etiket (TLABEL): Form içerisinde bulunan Metin Kutusu�na ne tür değer girileceğini
belirtmek için kullanılmaktadır.

Metin Kutusu (TEdit): Kullanıcının bilgi girişi yapması ya da elde edilen sonucun
görüntülenmesi için kullanılır.

Komut Düğmesi (TButton): Kullanıcın programda, gerçekleştireceği uygulamayı başlatan


yada bitiren kontroldür.
Assembly Bloğu dışında kullandığımız C ++ Builder Fonksiyonları:

Fonksiyon Anlamı Kullanım Şekli


Metin olarak
girilen değeri
StrToInt StrToInt(Edit1->Text);
sayıya
dönüştürür.
Sayı olarak
girilmiş değeri
IntToStr IntToStr(Edit1->Text);
metine
dönüştürür.

şekil 5: C ++ Builder ile assembly kod örneği


şekil 6: C ++ Builder 6.0 Ekran Görüntüsü

C++ Builder 6.0 ile gerçekleştirdiğimiz örnek kodu buradan , Turbo Pascal 7.0 ile
gerçekleştirdiğimiz örnek kodu buradan indirebilirsiniz.

Makalemizde , assembly�nin diğer diller içerisinde nasıl kullanılacağını en basit haliyle


anlatabilmek için kısa kodlar kullandım, daha gelişmiş kodları gerçekleştirebilmeniz için
sizlere başlangıç olması açısından önemli. Uzak mesafeler katedebilmek için küçük
adımlar atmak gerekiyor. Adımlar küçük yol uzun, önemli olan hedefe ulaşmayı
istemek.Herkese iyi çalışmalar...

Edit: ★BLλCK-
λCK-CΘDΣ
goa@hackermail.com

You might also like