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 5i toplarken 3ün üzerine 5 tane parmak sayardık. Mikroişlemcide her
saykılda (saat darbesinde) ALUda 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 11in 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,tler 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.
-
İş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.
-
İşaretsiz sayılarda işlem yaparken boyut taşmalarını göstermesi amacıyla C
bitini. Örneğin 2 bytelı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.
-
Şayet sonuç negatif bir değerse S=1 değilse S=0 olur.
-
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.
-
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.
-
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 AXteki 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 bytelı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ı Cnin 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 AXe yükleniyor.
SUB AX, Z ; x - y - z işlmi yapılmış oluyor
MOV X, AX ; sonuç xe 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ç xe 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. CMPnin 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 1ler 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. operandtan büyüktür.
b- S=0, O=0 veya S=1, O=1 ise 1. operand 2. operandtan büyüktür.
Şartlı Dallanma Komutları:
İngilizce karşılığı Conditional Jump Instructionsdı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ı
Komut |
Açıklama |
Şart |
Eş Komut |
Karşıt 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 1ler ç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 1ler tek ise) |
Parity = 0 |
JNP |
JPE |
İşaretsiz Sayılarda Jxxx
Komutları
Komut |
Açıklama |
Şart |
Eş Komut |
Karşıt Komut |
JA |
Jump if above (>)
(Yukarısındaysa) |
Carry=0, Zero=0 |
JNBE |
JNA |
JNBE |
Jump if not below or equal
(not <=)
(Aşağısında değil yada eşit değilse) |
Carry=0, Zero=0 |
JA |
JBE |
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 |
JNAE |
Jump if not above or equal
(not >=)
(Yukarısında değil veya eşit değilse) |
Carry = 1 |
JC, JB |
JAE |
JBE |
Jump if below or equal (<=)
(Aşağısında veya eşitse) |
Carry = 1 or Zero = 1 |
JNA |
JNBE |
JNA |
Jump if not above (not >)
(Yukarısında değilse) |
Carry = 1 or Zero = 1 |
JBE |
JA |
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ı
Komut |
Açıklama |
Şart |
Eş Komut |
Karşıt Komut |
JG |
Jump if greater (>)
(Büyükse) |
Sign = Ovrflw or Zero=0 |
JNLE |
JNG |
JNLE |
Jump if not less than or equal
(not <=)
(Düşük değilse yada eşit değilse) |
Sign = Ovrflw or Zero=0 |
JG |
JLE |
JGE |
Jump if greater than or equal
(>=)
(Büyükse veya eşitse) |
Sign = Ovrflw |
JNL |
JGE |
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 |
JNGE |
Jump if not greater or equal
(not >=)
(Büyük değilse veya eşit değilse) |
Sign Ovrflw |
JL |
JGE |
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 JNBEyi
ö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, bden 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 assemblyde 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 anı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, JLEnin anlamı less or equal yani düşük yada eşitse.
Yani anın değeri olan 5 bnin değeri olan 4ten düşük yada eşitse, program
00411A3E adresinden devam edip, önce eaxe k karakterini yükleyip (daha doğrusu
knın ASCII kod karşılığını yükleyecek), bunu hafızadaki "sonuc" adlı yere (sonuc
da aslında bir adres :)) kaydedecektir. Tabi 5 4ten 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 eaxe "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.NETin 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.
Şimdi bu makaleyi okuyan sizlerin akıllarına başka sorularda takılabilir. Şayet
böyle bir durum varsa hemen aşağıdaki "yorum yazmak istiyorum" linkine tıklayıp
sorularınızı sorabilirsiniz. Tüm sorularınızı memnuniyetle cevaplayacağım.
Özellikle şekil 2ye ait sorularınızı bekliyorum.
Bir sonraki makalemizde 80x86 komutlarını öğrenmeye devam edeceğiz. O zamana
kadar her şey gönlünüzce olsun...
Makale:
80x86 KOMUT SETİ (Bölüm 2) Assembly ve X86 Programlama Eren Erener
|