Mikroişlemciler · Ders Notu

Bölüm 11: Komut Kodlama ve Dizgi Komutları

Assembly dilinde yazılan her satır, sonunda birkaç bayta dönüşür. İşlemci yalnız bu baytları görür: bellekten okur, önden getirme kuyruğuna koyar, çözer ve yürütür. Bu not önce 8086'nın komut kümesini işlevsel sınıflara ayırır. Ardından bir komutun makine koduna nasıl dönüştüğünü bit bit gösterir: önek, işlem kodu, D ve W bitleri, ModR/M baytı, yer değiştirme ve anlık değer. Aynı kuralların tersi, yani bellekteki baytlardan komutu geri çıkarmak da adım adım işlenir. Yanlış bir adresten başlayan çözmenin aynı baytları bambaşka komutlara çevirdiği de bir örnekle gösterilir. Sonra veri taşıma komutlarından XLAT, LEA, LDS ve LES ele alınır. Notun ikinci yarısı dizgi komutlarına ayrılmıştır. MOVS, CMPS, SCAS, LODS ve STOS'un SI, DI, CX ve yön bayrağıyla birlikte çalışması anlatılır. Ardından REP, REPE ve REPNE önekleri, örtüşen bloklar ve süreler gelir. Son kesimler JMP'nin kodlamasını, işlemci kontrolü komutlarını ve hangi komutun hangi bayrağı değiştirdiğini gösteren başvuru tablolarını içerir. Her kod parçası yazmaçlar, bayraklar ve bellekle birlikte izlenir.

Öğrenme çıktıları

Komut sınıflarını tanımak
8086'nın yaklaşık yüz komutunu veri taşıma, aritmetik, mantık-kaydırma, dizgi, kontrol aktarma ve işlemci kontrolü sınıflarına ayırmak; her sınıfın tipik kullanımını açıklamak.
Komut kodlamak
Bir assembly komutunun işlem kodunu, D ve W bitlerini, ModR/M baytını, yer değiştirmesini ve anlık değerini bulup makine kodunu bayt bayt yazmak; kısa özel biçimleri ve önekleri tanımak.
Makine kodunu çözmek
Bellekteki bir bayt dizisini doğru başlangıç adresinden komutlara ayırmak; yanlış adresten başlayan çözmenin neden başka komutlar ürettiğini göstermek.
Adres yükleme komutlarını kullanmak
XLAT ile tablo çevirisi yapmak; LEA ile adresi, MOV ile içeriği almak; LDS ve LES ile bellekteki uzak işaretçiyi kesim ve bağıl konum yazmaçlarına yüklemek.
Dizgi komutlarını izlemek
MOVS, CMPS, SCAS, LODS ve STOS komutlarını REP, REPE ve REPNE önekleriyle kullanmak; her adımda SI, DI, CX, ZF ve DF'nin değişimini göstermek; örtüşen bloklarda doğru yönü seçmek.
Başvuru tablolarını okumak
JMP biçimlerinin kodlamasını ve göreli uzaklığı hesaplamak; işlemci kontrolü komutlarının işlemci uçlarıyla ilişkisini açıklamak; bayrak etkileri tablosundan bir komutun hangi bayrağı değiştirdiğini okumak.

11.1Komut kümesine genel bakış

11.1.1Komut kümesi nedir?

Bir işlemcinin yürütebildiği bütün komutların listesine komut kümesi (instruction set) denir. Komut kümesi, işlemci ile onu programlayan kişi arasındaki sözleşmedir. Programcı hangi komutların bulunduğunu, her komutun hangi işlenenleri kabul ettiğini ve yazmaçlara, belleğe ve bayraklara ne yaptığını bilir. İşlemcinin içindeki devrelerin nasıl kurulduğunu bilmesi gerekmez. Aynı komut kümesini yürüten iki farklı işlemci aynı programı çalıştırır; 8086, 8088 ve sonraki x86 işlemciler bu yüzden 8086 için yazılmış programları çalıştırabilir.

8086'nın komut kümesinde, sayılış biçimine göre, yaklaşık yüz komut vardır. Programcı bu komutları MOV, ADD, JMP gibi anımsatıcılarla (mnemonic) yazar. Anımsatıcı, insan için kolay hatırlanan bir addır; işlemci onu tanımaz. Assembler her satırı işlemcinin anladığı sayısal biçime, yani makine koduna çevirir. Aynı anımsatıcı, işlenenlerine göre birçok farklı makine koduna dönüşebilir. Örneğin MOV yazmaçtan yazmaca, bellekten yazmaca, anlık değerden belleğe ve kesim yazmacına taşıma için ayrı kodlar kullanır. Bu yüzden 8086'nın anımsatıcı sayısı yüz kadarken farklı işlem kodu sayısı birkaç yüzü bulur.

8086'nın komutları değişken uzunluktadır. En kısa komut tek bayttır (NOP = 90h, INC AX = 40h). En uzun komut, öneksiz olarak altı bayta ulaşır (MOV WORD PTR [1234h], 5678h = C7 06 34 12 78 56). Başa bir ya da daha çok önek baytı da eklenebilir. Değişken uzunluk belleği verimli kullanır: sık kullanılan komutlar kısa, seyrek kullanılanlar uzun tutulur. Bedeli, işlemcinin bir komutun nerede bittiğini ancak baytlarını sırayla çözerek anlayabilmesidir.

11.1.2Altı işlevsel sınıf

Komutları ezberlemenin en kolay yolu, onları yaptıkları işe göre gruplamaktır. Tablo 11.1 8086 komutlarını altı sınıfa ayırır. Sınıflar yalnız bir ezber kolaylığı değildir: aynı sınıftaki komutlar bayrakları benzer biçimde etkiler ve çoğu zaman benzer biçimde kodlanır. Veri taşıma komutları bayraklara dokunmaz; aritmetik ve mantık komutları sonucu anlatan bayrakları yazar; kontrol aktarma komutları bayrakları okur ama değiştirmez.

Tablo 11.1: 8086 komut kümesinin altı işlevsel sınıfı ve her sınıftaki komutlar.
SınıfYaptığı işKomutlar
1. Veri taşımayazmaç, bellek, yığın ve G/Ç arasında veri kopyalamaMOV PUSH POP XCHG IN OUT XLAT LEA LDS LES LAHF SAHF PUSHF POPF
2. Aritmetikdört işlem, BCD düzeltme, işaret genişletmeADD ADC INC AAA DAA SUB SBB DEC NEG CMP AAS DAS MUL IMUL AAM DIV IDIV AAD CBW CWD
3. Mantık, kaydırma, döndürmebit düzeyinde işlemlerNOT AND OR XOR TEST SHL SAL SHR SAR ROL ROR RCL RCR
4. Dizgi işlemebellek bloklarını SI ve DI ile tarama ve kopyalamaMOVSB/W CMPSB/W SCASB/W LODSB/W STOSB/W; önekler REP REPE REPZ REPNE REPNZ
5. Kontrol aktarmadallanma, döngü, prosedür ve kesmeJMP, Jcc (JE JNE JA JB JG JL …), LOOP LOOPE LOOPNE JCXZ CALL RET RETF INT INTO IRET
6. İşlemci kontrolübayrakları kurma ve temizleme, işlemcinin durumunu yönetmeCLC STC CMC CLD STD CLI STI HLT WAIT ESC LOCK NOP

Bu not, birinci sınıftan adres yükleme komutlarını (XLAT, LEA, LDS, LES), dördüncü sınıfın tamamını, beşinci sınıftan JMP'nin kodlamasını ve altıncı sınıfın tamamını ayrıntılı işler. Aritmetik ve mantık komutlarının bayrak etkileri, notun sonundaki başvuru tablolarında bir arada verilir. Kodlama kuralları ise bütün sınıflar için ortaktır.

11.1.3Komut baytları işlemciye nasıl ulaşır?

Kodlama kurallarının neden böyle olduğunu anlamak için komutun işlemcinin içinde izlediği yola bakmak gerekir. 8086'nın içi iki birime ayrılır. Yol arayüz birimi (bus interface unit, BIU) dış yolları yönetir. CS:IP'nin gösterdiği adresten komut baytlarını okur ve altı baytlık önden getirme kuyruğuna (prefetch queue) koyar. Kuyrukta iki bayt boşalır boşalmaz yeni bir okuma başlatır. 8086'nın veri yolu 16 bit olduğundan, çift adresten başlayan bir okuma tek yol döngüsünde iki bayt getirir.

Yürütme birimi (execution unit, EU) baytları kuyruğun başından tek tek alır. İlk bayt bir önekse onu bir mandala kaydeder ve sonraki bayta geçer. İlk gerçek işlem kodu baytı, komutun türünü ve ardından kaç bayt geleceğini belirler. İşlem kodu bir ModR/M baytı gerektiriyorsa EU kuyruktan ikinci baytı alır. ModR/M'nin MOD alanı yer değiştirmenin 0, 1 ya da 2 bayt olduğunu söyler. İşlem kodundaki W biti ve komutun türü de anlık değerin uzunluğunu belirler. EU bu baytları aldıktan sonra komutun nerede bittiğini bilir; bir sonraki bayt yeni komutun ilk baytıdır.

Bu sıralı çözme, makine kodunun iki önemli özelliğini açıklar. Birincisi, komutun uzunluğu ilk bir-iki bayttan çıkar; bu yüzden önemli bilgiler (işlem kodu, D, W, MOD) hep başta durur. İkincisi, işlemci kodla veriyi ayırt etmez. CS:IP nereyi gösteriyorsa oradaki bayt işlem kodu sayılır. Yanlış bir adrese atlanırsa aynı baytlar bambaşka komutlar olarak yürütülür. Bu konu makine kodunu çözme kesiminde bir örnekle ayrıntılı işlenir.

Tanım 11.1

Makine kodu (machine code), bir komutun bellekte saklanan ve işlemcinin doğrudan yürüttüğü bayt dizisidir. Komut biçimi (instruction format) bu dizinin alanlarını ve sırasını tanımlar. Kodlama, assembly komutundan makine kodunu üretmek; çözme, makine kodundan assembly komutunu geri bulmaktır.

11.2Komut biçimi

11.2.1Alanlar

8086'nın genel komut biçimi Şekil 11.1 ile gösterilmiştir. Bir komut en çok altı alandan oluşur ve bu alanlar her zaman aynı sırayla dizilir:

  1. Önekler (isteğe bağlı): kesim öneki (26h, 2Eh, 36h, 3Eh), yineleme öneki (F3h, F2h) ya da LOCK (F0h). Komutun davranışını değiştirir.
  2. İşlem kodu baytı (her zaman var): komutun ne yaptığını söyler. Çoğu iki işlenenli komutta bu baytın 6 biti işlemi, son iki biti D ve W bitlerini taşır.
  3. ModR/M baytı (işlenenlerden biri yazmaç ya da bellek olabiliyorsa): MOD, REG ve R/M alanlarıyla iki işleneni seçer.
  4. Yer değiştirme (0, 1 ya da 2 bayt): bellek adresine eklenen sabit; düşük bayt önce gelir.
  5. Anlık değer (0, 1 ya da 2 bayt): komutun içinde verilen sabit işlenen; düşük bayt önce gelir.
opcode6 bitDWMOD2REG3R/M3disp düşükdisp yüksekveri düşükveri yüksek1. bayt2. bayt (ModR/M)0 / 1 / 2 bayt yer değiştirme0 / 1 / 2 bayt anlık veriKomut uzunluğu 1–6 bayt; önekler (segment, REP, LOCK) en başa eklenebilir.
Şekil 11.1: 8086 komut biçimi. Birinci bayt 6 bitlik işlem kodu ile D ve W bitlerini, ikinci bayt (ModR/M) MOD, REG ve R/M alanlarını taşır. Ardından 0–2 bayt yer değiştirme ve 0–2 bayt anlık değer gelir.

Yer değiştirme ve anlık değer baytları her zaman little-endian sırayla yazılır: 16 bitlik bir sayının düşük baytı düşük adrese, yüksek baytı bir sonraki adrese konur. MOV AX, 1234h bu yüzden B8 34 12 olarak kodlanır. Assembly satırında sayılar insanların okuduğu sırayla yazılır; bellekte ve makine kodu listelerinde ise ters dizilmiş görünür. Bu ters diziliş 8086'nın 16 bitlik veri yolunun düzeninden gelir. Çift adresteki bayt veri yolunun düşük yarısında (D0–D7), tek adresteki bayt yüksek yarısında (D8–D15) taşınır. Sözcüğün düşük baytı çift adreste durunca yazmacın düşük yarısına doğrudan bağlanır.

11.2.2D ve W bitleri

İki işlenenli komutların çoğunda işlenenlerden biri yazmaç, öteki yazmaç ya da bellektir. 8086 bu iki işleneni tek bir ModR/M baytıyla tanımlar: REG alanı yazmacı, R/M alanı ise yazmacı ya da bellek adresini seçer. Geriye iki soru kalır: işlem bayt mı, sözcük mü; sonuç REG'e mi, R/M'ye mi yazılacak? Bu iki soruyu işlem kodu baytının son iki biti cevaplar.

Tanım 11.2

W biti (word, işlem kodunun bit 0'ı) işlenen boyutunu seçer: W = 0 bayt (8 bit), W = 1 sözcük (16 bit) işlemdir. REG alanındaki aynı üç bit, W = 0 iken AL…BH, W = 1 iken AX…DI yazmaçlarından birini gösterir.

D biti (direction, işlem kodunun bit 1'i) yönü seçer: D = 1 ise REG alanı hedef, R/M alanı kaynaktır; D = 0 ise REG alanı kaynak, R/M alanı hedeftir.

D biti, iki yazmaçlı komutlarda ilginç bir sonuç doğurur. MOV AX, BX komutunda iki işlenen de yazmaçtır; hangisinin REG, hangisinin R/M alanına yazılacağı serbesttir. D = 1 seçilirse hedef AX REG alanına, kaynak BX R/M alanına girer: 100010 1 1 = 8Bh, ModR/M = 11 000 011 = C3h. D = 0 seçilirse kaynak BX REG alanına, hedef AX R/M alanına girer: 100010 0 1 = 89h, ModR/M = 11 011 000 = D8h. Tablo 11.2 iki kodlamayı ve aynı durumdaki ADD AX, BX komutunu yan yana koyar.

Tablo 11.2: İki yazmaçlı komutların iki geçerli kodlaması. İkisi de aynı işi yapar; fark yalnız D bitinde ve REG ile R/M alanlarının yer değiştirmesindedir.
KomutDİşlem koduModR/M (MOD REG R/M)Makine kodu
MOV AX, BX1100010 1 1 = 8Bh11 000 (AX) 011 (BX) = C3h8B C3
MOV AX, BX0100010 0 1 = 89h11 011 (BX) 000 (AX) = D8h89 D8
ADD AX, BX1000000 1 1 = 03h11 000 (AX) 011 (BX) = C3h03 C3
ADD AX, BX0000000 0 1 = 01h11 011 (BX) 000 (AX) = D8h01 D8

İki kodlama da geçerlidir ve işlemci ikisini de aynı biçimde yürütür: sonuç AX'e yazılır, BX değişmez. Hangisinin üretileceğine assembler karar verir. Hangi biçimin seçileceği assembler'a bağlıdır: MASM genellikle D = 1 biçimini (8B C3, 03 C3), NASM ise D = 0 biçimini (89 D8, 01 D8) üretir. İkisi de aynı işi yapar ve aynı uzunluktadır. Bu yüzden ders malzemesinde aynı komut iki farklı kodla görülebilir: 8086 mimarisi anlatılırken ADD AX, BX için 01 D8, assembler çıktısında 03 C3 yazılması bir çelişki değildir. İşlenenlerden biri bellek olduğunda ise seçim kalmaz. MOV [BX], AL komutunda bellek yalnız R/M alanında gösterilebildiği için AL REG alanına girer ve D = 0 olmak zorundadır (88 07). MOV AL, [BX] komutunda ise D = 1'dir (8A 07).

Sık yapılan hata

D bitini “ilk işlenen” ya da “soldaki işlenen” diye okumak. D bitinin anlamı REG alanına bağlıdır: D = 1, REG'deki yazmacın hedef olduğunu söyler. Komut satırında hedef her zaman soldadır; ama makine kodunda hedefin REG mi, R/M mi olduğu D'ye göre değişir.

11.2.3REG alanı

ModR/M baytının ortadaki üç biti REG alanıdır. Üç bit sekiz yazmacı seçmeye yeter. Hangi sekiz yazmacın kastedildiği W bitine bağlıdır. Tablo 11.3 bu kodları verir. Aynı tablo, kesim yazmaçlarının iki bitlik kodlarını da içerir. Kesim yazmaçları yalnız iki komut ailesinde (8Ch ve 8Eh ile başlayan MOV, ayrıca PUSH ve POP) yer alır ve REG alanına başında 0 ile yazılır.

Tablo 11.3: REG alanının (ve MOD = 11 iken R/M alanının) yazmaç kodları. Kesim yazmaçlarının kodu iki bittir.
KodW = 0 (bayt)W = 1 (sözcük)Kesim yazmacı
000ALAXES (00)
001CLCXCS (01)
010DLDXSS (10)
011BLBXDS (11)
100AHSP–
101CHBP–
110DHSI–
111BHDI–

Sıranın bir mantığı vardır. İlk dört kod AX, CX, DX, BX'i ve onların düşük yarılarını verir. Son dört kod, sözcük işlemde SP, BP, SI, DI'yi, bayt işlemde ilk dört yazmacın yüksek yarılarını (AH, CH, DH, BH) seçer. Yani 100 kodu W = 0 iken AH, W = 1 iken SP demektir. Bir çözmede W bitini gözden kaçırmak, AH'yi SP sanmaya yol açar. Bu kodlar yalnız ModR/M içinde kullanılmaz. Tek baytlık kısa biçimlerde de işlem kodunun son üç biti aynı yazmaç kodunu taşır: INC 40h + r, PUSH 50h + r, MOV r8, imm8 B0h + r, MOV r16, imm16 B8h + r. PUSH BX bu yüzden 50h + 3 = 53h, MOV SI, 1000h ise B8h + 6 = BEh, ardından 00 10'dur.

11.2.4MOD alanı

ModR/M baytının en soldaki iki biti MOD alanıdır. MOD, R/M alanının nasıl yorumlanacağını ve komutta kaç baytlık yer değiştirme bulunduğunu söyler. Tablo 11.4 dört durumu verir.

Tablo 11.4: MOD alanının dört değeri.
MODAnlamıYer değiştirme
00bellek işleneni, yer değiştirme yok; tek istisna R/M = 110: 16 bitlik doğrudan adres0 bayt (R/M = 110 ise 2 bayt)
01bellek işleneni + 8 bitlik yer değiştirme; d8 işaretle 16 bite genişletilir1 bayt
10bellek işleneni + 16 bitlik yer değiştirme2 bayt
11yazmaç işleneni: R/M alanı da bir yazmaç koduduryok

MOD = 01 durumundaki işaret genişletmenin pratik bir yararı vardır. Yer değiştirme −128 ile +127 arasındaysa assembler tek bayt kullanır. MOV AX, [BX+DI−2] komutunda −2 = FEh tek bayta sığar ve komut 8B 41 FE olarak üç bayt tutar. İşlemci FEh'yi FFFEh'ye genişletip BX + DI'ye ekler; 16 bitlik toplamada FFFEh eklemek 2 çıkarmakla aynı sonucu verir. Yer değiştirme bu aralığın dışındaysa MOD = 10 seçilir ve iki bayt gerekir: MOV CL, [BX+SI+1234h] = 8A 88 34 12.

11.2.5R/M alanı ve etkin adres

MOD = 11 değilse R/M alanı bir adresleme biçimini seçer. İşlemci bu biçime göre etkin adresi (effective address, EA) hesaplar. Etkin adres, işlenenin kesim içindeki bağıl konumudur. Fiziksel adres, kesim yazmacının 16 ile çarpımına EA eklenerek bulunur. Tablo 11.5 sekiz R/M kodunu, üç MOD değeriyle ve varsayılan kesimle birlikte verir.

Tablo 11.5: R/M alanı ve etkin adres. BP içeren bütün biçimlerde varsayılan kesim SS'tir; MOD = 00, R/M = 110 ise doğrudan adrestir ve kesim DS'dir.
R/MMOD = 00MOD = 01MOD = 10Varsayılan kesim
000[BX+SI][BX+SI+d8][BX+SI+d16]DS
001[BX+DI][BX+DI+d8][BX+DI+d16]DS
010[BP+SI][BP+SI+d8][BP+SI+d16]SS
011[BP+DI][BP+DI+d8][BP+DI+d16]SS
100[SI][SI+d8][SI+d16]DS
101[DI][DI+d8][DI+d16]DS
110[d16] doğrudan[BP+d8][BP+d16]DS / SS
111[BX][BX+d8][BX+d16]DS

Tablodan üç kural çıkar. Birincisi, yalnız BX, BP, SI ve DI adres yazmacı olabilir; bir taban yazmacı (BX ya da BP) ile bir indis yazmacı (SI ya da DI) birlikte kullanılabilir. [AX], [CX], [SI+DI] ya da [BX+BP] için R/M kodu yoktur ve assembler bunları hata sayar. İkincisi, BP içeren her biçim yığın kesimini (SS) kullanır. BP, yığın çerçevesindeki parametrelere ve yerel değişkenlere erişmek için tasarlanmıştır. Üçüncüsü, MOD = 00 ve R/M = 110 birleşimi [BP]'ye değil 16 bitlik doğrudan adrese ayrılmıştır. Yalnız [BP] yazıldığında assembler bu yüzden MOD = 01 ve d8 = 0 kullanır: MOV AL, [BP] üç bayttır (8A 46 00), oysa MOV AL, [BX] iki bayttır (8A 07).

Etkin adresin hesaplanması zaman alır ve bu süre komutun süresine eklenir. 8086'da EA'yı yürütme biriminin mikrokodu ALU ile adım adım hesaplar; bu yüzden süre adresleme biçimine göre değişir. Tablo 11.6 Intel 8086 kılavuzundaki EA sürelerini verir. Bir bellek işlenenli komutun toplam süresi “temel süre + EA” biçiminde yazılır. Örneğin MOV reg, mem 8 + EA saat turu sürer; MOV CL, [BX+SI+12h] için bu 8 + 11 = 19 turdur. 5 MHz'lik bir 8086'da bir saat turu 200 ns olduğundan komut yaklaşık 3,8 µs sürer.

Tablo 11.6: 8086'da etkin adres hesaplama süreleri (saat turu). Kesim öneki varsa 2 tur eklenir.
Adresleme biçimiÖrnekEA süresi
yalnız taban ya da indis[BX], [SI], [DI]5
yalnız yer değiştirme (doğrudan)[1234h], degisken6
taban + indis[BP+DI], [BX+SI] / [BP+SI], [BX+DI]7 / 8
yer değiştirme + taban ya da indis[BX+4], [BP+2], dizi[SI]9
yer değiştirme + taban + indis[BP+DI+d], [BX+SI+d] / [BP+SI+d], [BX+DI+d]11 / 12

Bellekten okunan ya da belleğe yazılan her sözcük için bir sayı daha önemlidir. Sözcük çift adresteyse BIU onu tek yol döngüsünde (4 saat turu) taşır. Tek adresteyse iki ayrı bayt döngüsü gerekir ve süreye 4 tur eklenir. 8086, çift adres için A0 = 0 ile düşük bayt şeridini, tek adres için BHE̅ = 0 ile yüksek bayt şeridini seçer. Tek adresteki bir sözcüğün iki baytı farklı şeritlere ve farklı sözcük adreslerine düştüğünden iki döngü kaçınılmazdır. Assembler'daki EVEN yönergesi sözcük dizilerini çift adrese hizalamak için bu yüzden kullanılır.

11.2.6Kısa özel biçimler

Genel biçim her komutu kodlayabilir ama her zaman en kısa kodu vermez. 8086'nın tasarımcıları, en sık kullanılan işlemler için ModR/M baytı gerektirmeyen ayrı işlem kodları ayırmıştır. Bu kısa biçimler bir ya da iki bayt kazandırır ve bazen daha hızlıdır. Tablo 11.7 en önemlilerini toplar.

Tablo 11.7: ModR/M baytı kullanmayan kısa biçimler. “r” işlem kodunun son üç bitine yazılan yazmaç kodudur.
Biçimİşlem koduÖrnekKodGenel biçim
INC / DEC r1640h+r / 48h+rINC AX40FF C0 (2 bayt)
PUSH / POP r1650h+r / 58h+rPUSH BX53FF F3 (2 bayt)
XCHG AX, r1690h+rXCHG AX, BX9387 C3 (2 bayt)
MOV r8, imm8B0h+rMOV AL, 5B0 05C6 C0 05 (3 bayt)
MOV r16, imm16B8h+rMOV AX, 1234hB8 34 12C7 C0 34 12 (4 bayt)
MOV AL/AX ↔ bellekA0h–A3hMOV AX, [2000h]A1 00 208B 06 00 20 (4 bayt)
ADD AL/AX, imm04h / 05hADD AL, 704 0780 C0 07 (3 bayt)
ADD r/m16, imm8 (işaret genişletmeli)83h /0ADD BX, 583 C3 0581 C3 05 00 (4 bayt)

Son satırdaki 83h biçimi bir başka fikri de gösterir. 80h, 81h ve 83h işlem kodları sekiz farklı aritmetik ve mantık komutunu (ADD, OR, ADC, SBB, AND, SUB, XOR, CMP) anlık değerle yapar. Hangi işlemin yapılacağını ModR/M'nin REG alanı söyler, çünkü bu komutlarda ikinci bir yazmaç işleneni yoktur. Kılavuzlarda bu durum 83 /0 biçiminde yazılır: “/0”, REG alanına 000 konacağını, yani işlemin ADD olduğunu belirtir. ADD BX, 5 için ModR/M = 11 000 011 = C3h olur ve ardından tek bayt 05 gelir. İşlemci 05h'yi işaretle 0005h'ye genişletip BX'e ekler. Aynı “işlem seçici” düzen FEh ve FFh ailesinde de vardır: FF /4 dolaylı yakın JMP, FF /6 PUSH bellek, FE /0 bayt INC'dir. Bu düzen JMP'nin kodlaması anlatılırken yeniden karşımıza çıkar.

Sık yapılan hata

Bir komutun makine kodunun tek olduğunu sanmak. MOV AL, [1000h] hem A0 00 10 (3 bayt) hem 8A 06 00 10 (4 bayt) olarak kodlanabilir; ikisi de doğrudur. Assembler kısa biçimi seçer. Elle kodlama yaparken önce kısa bir biçim olup olmadığına bakmak, sınavda ve listing çıktısıyla karşılaştırmada karışıklığı önler.

11.3Komut kodlama adım adım

11.3.1Kodlama yöntemi

Bir assembly komutunu elle kodlamak, assembler'ın yaptığı işi kâğıt üzerinde tekrarlamaktır. İş her seferinde aynı sırayla yürür:

  1. Biçimi seç. İşlenenlerin türüne bakılır: yazmaç–yazmaç, yazmaç–bellek, anlık–yazmaç, anlık–bellek. Kısa özel bir biçim varsa (AL/AX ile bellek, anlık değerle yazmaç, INC r16 …) o kullanılır; yoksa genel biçime geçilir.
  2. İşlem kodunu yaz. Komut ailesinin 6 bitlik kodu yazılır (MOV için 100010, ADD için 000000). Ardından D ve W bitleri eklenir. D, REG alanındaki yazmacın hedef olup olmadığına göre; W, işlenen boyutuna göre seçilir.
  3. ModR/M baytını kur. REG alanına yazmaç işleneninin kodu yazılır. Öteki işlenen yazmaçsa MOD = 11 olur ve R/M'ye onun kodu girer. Bellekse adresleme biçimine göre R/M seçilir ve yer değiştirmenin boyutuna göre MOD = 00, 01 ya da 10 olur.
  4. Yer değiştirmeyi ve anlık değeri ekle. Önce yer değiştirme, sonra anlık değer gelir; ikisi de düşük bayt önce yazılır.
  5. Önekleri başa koy. Varsayılan kesim dışında bir kesim kullanılıyorsa kesim öneki, dizgi komutu yineleniyorsa REP öneki, yol kilitleniyorsa LOCK en başa eklenir.

11.3.2Örnek: MOV CL, [BX+SI+12h]

Bu komut DS:(BX + SI + 12h) adresindeki bir baytı CL'ye kopyalar. İşlenenlerden biri yazmaç (CL), öteki bellektir. Kısa bir biçim yoktur, çünkü kısa bellek biçimi yalnız AL ve AX için ve yalnız doğrudan adres için vardır. Genel biçim kullanılır: MOV reg, r/m. Şekil 11.2 kodlamanın üç aşamasını gösterir.

100010OPCODE1D0WMODREGR/Mdisp81. bayt2. bayt8AhMOV CL, [BX+SI+12h]100010OPCODE1D0W01MOD001REG000R/Mdisp81. bayt2. bayt8Ah48hMOV CL, [BX+SI+12h]100010OPCODE1D0W01MOD001REG000R/M12hdisp81. bayt2. bayt8Ah48h12hMakine kodu: 8A 48 12 (3 bayt)MOV CL, [BX+SI+12h]
Şekil 11.2: MOV CL, [BX+SI+12h] komutunun kodlanması. Üstte birinci bayt (işlem kodu 100010, D = 1, W = 0 → 8Ah), ortada ModR/M baytı (MOD = 01, REG = 001, R/M = 000 → 48h), altta 8 bitlik yer değiştirme (12h) ve sonuç.
  1. İşlem kodu. MOV'un yazmaç ile r/m arasındaki ailesi 100010'dur.
  2. D ve W. Hedef CL bir yazmaçtır ve REG alanına girecektir; REG hedef olduğu için D = 1. CL bayt yazmacıdır; W = 0. Birinci bayt 1000 1010b = 8Ah.
  3. REG. CL'nin kodu, W = 0 sütununda 001'dir.
  4. R/M ve MOD. [BX+SI+d] biçiminin R/M kodu 000'dır. Yer değiştirme 12h, −128…+127 aralığına sığar; MOD = 01 (8 bitlik yer değiştirme). ModR/M = 01 001 000b = 48h.
  5. Yer değiştirme. 12h tek bayt olarak eklenir.

Sonuç 8A 48 12'dir ve komut üç bayttır. Yer değiştirme 1234h gibi bir sözcük olsaydı MOD = 10 seçilecek, ModR/M 10 001 000b = 88h olacak ve komut 8A 88 34 12 biçiminde dört bayt tutacaktı. Komutun süresi 8 + EA'dır; [BX+SI+d] biçiminin EA süresi 11 tur olduğundan toplam 19 saat turu eder. Bu sürenin büyük kısmı adres hesabına ve BIU'nun bellekten baytı getirmesine gider.

Örnek 11.1

MOV [SI+20h], CX komutunu kodlayalım. Kaynak CX REG alanına girer, hedef bellek R/M'dedir: REG kaynak olduğundan D = 0, sözcük işlem olduğundan W = 1. Birinci bayt 100010 0 1 = 89h. REG = 001 (CX), R/M = 100 ([SI+d]), 20h tek bayta sığdığından MOD = 01. ModR/M = 01 001 100b = 4Ch. Sonuç 89 4C 20.

Aynı yöntemle ADD BX, [1234h]: ADD ailesi 000000, BX hedef ve REG'de olduğundan D = 1, W = 1 → 03h. REG = 011 (BX). Doğrudan adres için MOD = 00, R/M = 110 → ModR/M = 00 011 110b = 1Eh. Ardından adres 34 12 gelir: 03 1E 34 12.

11.3.3Örnek komutlar ve uzunlukları

Tablo 11.8 farklı uzunluktaki on komutu ve makine kodlarını bir arada verir. Tablo, komut biçiminin bütün alanlarının ne zaman ortaya çıktığını gösterir: yalnız işlem kodu (1 bayt), işlem kodu ve anlık değer, işlem kodu ve ModR/M, önek ve ModR/M, ModR/M ile yer değiştirme ve anlık değer birlikte.

Tablo 11.8: Örnek komutlar, makine kodları ve uzunlukları.
KomutMakine koduBaytAçıklama
NOP901aslında XCHG AX, AX (90h + 0)
INC AX / PUSH BX40 / 531yazmaç kodu işlem kodunun içinde (40h + r, 50h + r)
MOV AL, 5B0 052B0h + r, anlık bayt
INT 21hCD 212kesme numarası komutun içinde
MOV AX, 1234hB8 34 123düşük bayt önce (little-endian)
MOV CL, [BX+SI+12h]8A 48 123ModR/M + d8
MOV ES:[BX], AL26 88 07326h = ES öneki
ADD [BX+DI+1000h], DX01 91 00 104MOD = 10, d16
MOV WORD PTR [1234h], 5678hC7 06 34 12 78 566en uzun öneksiz biçim: d16 + imm16
REP MOVSBF3 A42F3h = REP öneki

Birkaç satırı ayrıca açmak yararlıdır. ADD [BX+DI+1000h], DX komutunda kaynak DX REG alanındadır (D = 0, W = 1 → 01h). ModR/M = 10 010 001b = 91h: MOD = 10 (16 bitlik yer değiştirme), REG = 010 (DX), R/M = 001 ([BX+DI+d]). Yer değiştirme 1000h, little-endian olarak 00 10 diye eklenir. Bu komut belleği hem okur hem yazar: BIU önce sözcüğü okur, ALU toplamayı yapar, BIU sonucu aynı adrese geri yazar. 8086'da bu biçimin süresi 16 + EA, yani 16 + 12 = 28 saat turudur.

MOV WORD PTR [1234h], 5678h komutunda bütün alanlar bir aradadır. C7h, “anlık değeri r/m'ye taşı” ailesinin sözcük biçimidir. ModR/M = 00 000 110b = 06h: REG alanı burada yazmaç değil işlem seçicidir ve MOV için 000'dır; MOD = 00, R/M = 110 doğrudan adres demektir. Ardından adres (34 12) ve en sonda anlık değer (78 56) gelir. Bu komut, öneksiz bir 8086 komutunun ulaşabildiği en büyük uzunluk olan altı bayttır. Assembly satırındaki WORD PTR gereklidir; o olmadan assembler 5678h'nin bayt mı sözcük mü yazılacağını bilemez. Bu örnekte 5678h bayta sığmadığı için sözcük tahmin edilebilir, ama MOV [1234h], 5 gibi bir satırda boyut gerçekten belirsizdir ve hata verilir.

11.3.4MOV'un kodlama biçimleri

MOV, 8086'nın en çok kullanılan komutudur ve tek bir anımsatıcının arkasında sekiz ayrı kodlama biçimi barındırır. Tablo 11.9 bu biçimleri, ardından gelen alanları, birer örneği ve Intel 8086 kılavuzundaki süreleri verir.

Tablo 11.9: MOV'un kodlama biçimleri. Süreler saat turudur; EA etkin adres süresidir.
Biçimİşlem kodu (8 / 16 bit)ArdındanÖrnekKodSüre
yazmaç → r/m88 / 89ModR/M (+ disp)MOV [BX], AL88 072 (yazmaç), 9 + EA
r/m → yazmaç8A / 8BModR/M (+ disp)MOV CX, [SI]8B 0C2 (yazmaç), 8 + EA
anlık → r/mC6 / C7ModR/M (REG = 000) + disp + immMOV BYTE PTR [BX], 5C6 07 0510 + EA
anlık → yazmaçB0+r / B8+rimm8 / imm16MOV SI, 1000hBE 00 104
bellek → AL / AXA0 / A116 bit adresMOV AX, [2000h]A1 00 2010
AL / AX → bellekA2 / A316 bit adresMOV [1000h], ALA2 00 1010
kesim yazmacı → r/m168CModR/M (REG = sreg)MOV AX, ES8C C02, 9 + EA
r/m16 → kesim yazmacı8EModR/M (REG = sreg)MOV DS, AX8E D82, 8 + EA

Tablodaki ilk iki satır aynı ailenin D = 0 ve D = 1 hâlleridir: 88h/89h ile 8Ah/8Bh arasındaki tek fark bit 1'dir. MOV CX, [SI] komutunda CX hedef ve REG'dedir: 8Bh; ModR/M = 00 001 100b = 0Ch. MOV [BX], AL komutunda AL kaynaktır: 88h; ModR/M = 00 000 111b = 07h. Üçüncü satırda REG alanı yazmaç seçmez, “000 = MOV” anlamına gelir; ikinci işlenen anlık değer olduğundan REG boştur. AL/AX ile doğrudan adres arasındaki taşımanın A0h–A3h kısa biçimi, ModR/M baytını atlar ve bir bayt kazandırır. Kısa biçim 10 tur sürer; genel biçim okumada 8 + 6 = 14, yazmada 9 + 6 = 15 tur tutar.

11.3.5Kesim yazmaçlı MOV

Kesim yazmaçları genel amaçlı yazmaçlardan ayrı tutulur ve yalnız iki MOV biçimiyle taşınır. 8Ch kesim yazmacından r/m16'ya, 8Eh r/m16'dan kesim yazmacına kopyalar. Her iki biçimde de REG alanının ilk biti 0, son iki biti kesim yazmacının kodudur: ES = 00, CS = 01, SS = 10, DS = 11. Şekil 11.3 MOV DS, AX komutunun ikinci baytını açar: MOD = 11 (yazmaç), REG = 011 (DS), R/M = 000 (AX), yani 1101 1000b = D8h. Komut 8E D8'dir.

mov ds, ax → 8E D811011000MOD = 11REG = 011 → DSR/M = 000 → AXES 00 · CS 01 · SS 10 · DS 11 (REG alanına 0 + sreg)
Şekil 11.3: MOV DS, AX = 8E D8. ModR/M baytının REG alanında 0 + kesim yazmacı kodu (DS = 11), R/M alanında kaynak yazmaç (AX = 000) bulunur.

Bazı biçimler hiç yoktur. Kesim yazmacına anlık değer yazan bir işlem kodu bulunmaz; MOV DS, 1234h geçersizdir ve değer önce AX gibi bir genel yazmaca yüklenir. Bellekten belleğe taşıyan bir MOV da yoktur, çünkü ModR/M baytı yalnız bir bellek işleneni tanımlayabilir. İki bellek işleneni gerektiren iş, dizgi komutlarının (MOVS, CMPS) özel görevidir. MOV CS, AX ise kodlanabilir görünse de kullanılmaz: CS'yi IP ile birlikte değiştirmeyen bir komut, programın bir sonraki komutu bambaşka bir kesimden getirmesine yol açar. CS yalnız uzak JMP, uzak CALL, RETF, INT ve IRET ile, IP ile birlikte değişir. 8086'nın bir özelliği daha vardır: SS'ye yazan bir MOV ya da POP'tan sonra, bir sonraki komut bitene kadar kesme kabul edilmez. Böylece MOV SS, AX ile hemen ardından gelen MOV SP, … arasına bir kesmenin girip yanlış yığını kullanması önlenir.

Örnek 11.2

MOV ES, DX: 8Eh ailesi, REG = 0 + 00 (ES) = 000, MOD = 11, R/M = 010 (DX). ModR/M = 11 000 010b = C2h. Komut 8E C2. Ters yöndeki MOV AX, ES 8Ch ile kodlanır ve ModR/M = 11 000 000b = C0h olur: 8C C0.

11.3.6Doğrudan adres: MOD = 00, R/M = 110

R/M tablosundaki tek düzensizlik, MOD = 00 ve R/M = 110 birleşimidir. Düzenli olsaydı bu birleşim [BP] anlamına gelecekti. 8086 tasarımcıları onu 16 bitlik doğrudan adrese ayırmıştır, çünkü değişkenlere adıyla erişmek [BP]'den çok daha sıktır. MOV [1000h], BL komutu bu yüzden şöyle kodlanır:

Tablo 11.10: MOV [1000h], BL = 88 1E 00 10 komutunun bitleri.
BaytBitlerAnlamı
88h100010 0 0MOV r/m ↔ reg; D = 0 (REG kaynak), W = 0 (bayt)
1Eh00 011 110MOD = 00, REG = 011 (BL), R/M = 110 → doğrudan adres
00 100001 0000 0000 000016 bitlik adres 1000h (düşük bayt önce)

Komut dört bayttır ve kesim DS'dir. BL için A2h benzeri bir kısa biçim yoktur; kısa biçim yalnız AL ve AX içindir. Aynı adrese AL yazılsaydı assembler A2 00 10 (3 bayt) üretirdi. Genel biçimle yazılmış 88 06 00 10 de aynı işi yapar: ModR/M 06h = 00 000 110b, REG = 000 (AL). Tablo 11.11 bu biçimleri [BP] ile karşılaştırır.

Tablo 11.11: Doğrudan adres ve [BP] kodlamaları.
KomutKodAçıklama
MOV [1000h], ALA2 00 10AL için kısa biçim, 3 bayt
MOV [1000h], AL88 06 00 10genel biçim, 4 bayt (aynı iş): ModR/M 06h = 00 000 110
MOV [1000h], BL88 1E 00 10BL için kısa biçim yok
MOV [BP], BL88 5E 005Eh = 01 011 110: [BP+d8], d8 = 0; kesim SS

Bu kural yalnız assembler'ı değil çözücüyü de ilgilendirir. 1Eh baytını gören bir çözücü, MOD = 00 ve R/M = 110 olduğunu fark edip arkasından iki adres baytı okumak zorundadır. Kuralı unutan bir çözücü 88 1E'yi iki baytlık MOV [BP], BL sanır. O zaman adres baytları yeni bir komutun başı olur: 00 10, ADD [BX+SI], DL diye okunur (00h = ADD r/m8, r8; 10h = 00 010 000b). Bundan sonraki bütün komut sınırları kayar. Bu, bir sonraki kesimde işlenen genel sorunun küçük bir örneğidir.

11.3.7Önek baytları

Önek, kendi başına bir komut değildir; arkasından gelen komutun davranışını değiştirir. 8086'da üç tür önek vardır ve Tablo 11.12 hepsini listeler.

Tablo 11.12: 8086 önek baytları.
ÖnekBaytEtkisi
ES: / CS: / SS: / DS:26 / 2E / 36 / 3Ebellek işleneninin varsayılan kesimini değiştirir (001ss110 düzeni)
REP / REPE / REPZF3dizgi komutunu CX ≠ 0 (ve CMPS/SCAS için ZF = 1) iken yineler
REPNE / REPNZF2dizgi komutunu CX ≠ 0 ve ZF = 0 iken yineler
LOCKF0komut süresince LOCK̅ ucunu etkin tutar; yolu başka işlemcilere kapatır

Kesim öneklerinin bit düzeni ilginçtir: 26h = 001 00 110, 2Eh = 001 01 110, 36h = 001 10 110, 3Eh = 001 11 110. Ortadaki iki bit, MOV biçimlerinde kullanılan kesim yazmacı kodunun aynısıdır. MOV AL, ES:[BX] bu yüzden 26 8A 07 olarak kodlanır: önek, ardından MOV AL, [BX]'in kendi kodu. Assembler gereksiz öneki üretmez. MOV AL, DS:[BX] yazılırsa DS zaten varsayılan kesim olduğu için önek eklenmez ve kod 8A 07 kalır. MOV AL, DS:[BP] ise öneki gerektirir, çünkü BP'nin varsayılanı SS'tir.

Önek, komutun süresine 2 saat turu ekler ve bir bayt yer kaplar. Bir başka bedeli kesmelerle ortaya çıkar. 8086, REP önekli bir dizgi komutu yürütülürken kesme gelirse işi yarıda bırakıp kesmeye gidebilir. Dönüşte komutu yeniden başlatmak için yığına önekin adresini iter. Ancak 8086 yalnız son öneki hatırlar: REP ile kesim öneki birlikte kullanılmışsa (REP ES: MOVSB gibi) dönüşte önceki önek kaybolur. Bu yüzden 8086 programlarında REP'li bir dizgi komutuna kesim öneki eklenmez; gerekiyorsa kesmeler CLI ile kapatılır.

Örnek 11.3

2E FF 27 baytlarını çözelim. 2Eh, CS önekidir. FFh, işlemi ModR/M'nin REG alanının seçtiği bir ailedir. 27h = 00 100 111b: MOD = 00, REG = 100 (/4: dolaylı yakın JMP), R/M = 111 ([BX]). Komut JMP WORD PTR CS:[BX]'tir: IP, CS:[BX] adresindeki sözcükle değişir. Atlama tablosu kod kesiminde tutulduğunda bu önek gerekir.

11.4Makine kodunu çözme

11.4.1Çözme yöntemi

Çözme (disassembly), kodlamanın tersidir: bellekteki baytlardan assembly komutlarını geri bulmak. Hata ayıklayıcılar, emu8086'nın bellek penceresi ve listing çıktısı bu işi yapar. Elle çözme, bir programın bellekte nasıl durduğunu anlamanın ve sınavlarda sık sorulan “bu baytlar hangi komutlar?” sorusunu cevaplamanın yoludur. Yöntem, işlemcinin EU'sunun yaptığını taklit eder:

  1. Başlangıç noktasını bil. Çözme, bir komutun ilk baytından başlamalıdır. Program başlangıcı (.COM için 0100h) ya da bir atlama hedefi güvenilir başlangıçtır.
  2. İlk baytı sınıflandır. Bayt bir önekse (26h, 2Eh, 36h, 3Eh, F0h, F2h, F3h) not edilir ve sonraki bayta geçilir. Değilse işlem kodu tablosundan komut ailesi bulunur.
  3. Kısa biçim mi? 40h–5Fh, 90h–97h, B0h–BFh gibi kodlarda yazmaç işlem kodunun içindedir; ModR/M yoktur. Ardından gelecek anlık değerin uzunluğu ailenin kuralından bilinir.
  4. ModR/M'yi aç. Aile ModR/M gerektiriyorsa sonraki bayt MOD, REG, R/M alanlarına ayrılır. MOD ve R/M'den yer değiştirmenin uzunluğu çıkar (MOD = 00 ve R/M = 110 özel durumu unutulmaz).
  5. Kalan baytları al. Yer değiştirme ve anlık değer little-endian olarak okunur. Komut burada biter; bir sonraki bayt yeni komutun ilk baytıdır.

11.4.2Örnek: on iki baytlık bir parça

Bellekte 0100h'den başlayan şu on iki bayt bir programın parçası olsun: 8B 4E 04 03 C3 A3 00 02 F3 AB EB FE. Çözme 0100h'den başlar. Tablo 11.13 sonucu verir; adımlar aşağıda açıklanmıştır.

Tablo 11.13: 8B 4E 04 03 C3 A3 00 02 F3 AB EB FE baytlarının çözümü.
AdresBaytlarÇözümKomut
0100h8B 4E 048Bh = 100010 1 1; 4Eh = 01 001 110 → MOD = 01, REG = CX, R/M = [BP+d8]; d8 = 04hMOV CX, [BP+04h]
0103h03 C303h = 000000 1 1 (ADD, D = 1, W = 1); C3h = 11 000 011 → AX ← AX + BXADD AX, BX
0105hA3 00 02A3h = AX → bellek kısa biçimi; adres 0200hMOV [0200h], AX
0108hF3 ABF3h = REP öneki; ABh = STOSWREP STOSW
010AhEB FEEBh = kısa JMP; FEh = −2 → hedef 010Ch − 2 = 010AhJMP 010Ah (kendine)

İlk bayt 8Bh, MOV r/m ↔ reg ailesindendir ve D = 1, W = 1 taşır: REG alanı 16 bitlik hedef yazmaçtır. İkinci bayt 4Eh = 0100 1110b, alanlarına ayrılınca 01 | 001 | 110 olur. MOD = 01 olduğundan bir bayt yer değiştirme gelecektir. REG = 001, W = 1 ile CX'tir. R/M = 110, MOD = 01 iken [BP+d8] demektir; MOD = 00 olsaydı doğrudan adres olurdu. Üçüncü bayt 04h yer değiştirmedir. Komut MOV CX, [BP+04h], kesim SS'tir. Bu biçim, yığından bir prosedür parametresi okumanın tipik kalıbıdır.

Dördüncü bayt 03h, ADD ailesinin D = 1, W = 1 biçimidir. Beşinci bayt C3h = 11 000 011b'de MOD = 11'dir, yani iki işlenen de yazmaçtır: REG = AX (hedef), R/M = BX. Komut ADD AX, BX'tir. Aynı komutun 01 D8 biçiminde de kodlanabileceği yukarıda gösterilmişti; burada D = 1 biçimiyle karşılaşıyoruz. Altıncı bayt A3h, ModR/M kullanmayan kısa biçimdir: AX'i doğrudan adrese yazar. Ardından gelen iki bayt adresi verir: 00 02 → 0200h. Dokuzuncu bayt F3h bir önektir; komut bir sonraki baytla tamamlanır. ABh = 1010 101 1b, STOS ailesinin W = 1 biçimidir. Komut REP STOSW'dir ve yalnız iki bayt tutar. Son iki bayt EB FE'dir. EBh kısa JMP'dir ve FEh işaretli olarak −2'dir. Hedef, bir sonraki komutun adresi olan 010Ch'ye −2 eklenerek bulunur: 010Ah, yani komutun kendisi. Bu iki bayt, bir programı olduğu yerde durduran sonsuz döngüdür.

Sonuç: on iki bayt beş komuttur. Komutların uzunlukları 3, 2, 3, 2 ve 2 bayttır ve her uzunluk yalnız ilk bir iki bayta bakılarak çıkarılmıştır. Çözmenin sonunda bayt sayısının tam tutması (0100h + 12 = 010Ch), sınırların doğru bulunduğunun bir denetimidir.

11.4.3Yanlış bağıl konumdan çözme

Çözme yönteminin ilk adımı, başlangıç noktasını bilmekti. Bu adımın neden vazgeçilmez olduğunu bir örnek gösterir. Bellekte bağıl konum 0000h'den başlayan şu on bir bayt bulunsun: B4 02 B2 41 CD 21 B8 00 4C CD 21. Konum 0'dan çözüldüğünde bu baytlar, ekrana 'A' yazıp programı bitiren tanıdık bir programdır:

MOV AH, 02h     ; B4 02
MOV DL, 41h     ; B2 41  ('A')
INT 21h        ; CD 21  karakter yaz
MOV AX, 4C00h   ; B8 00 4C
INT 21h        ; CD 21  programı bitir

B4h = B0h + 4, MOV AH, imm8'dir ve bir anlık bayt alır: 02h. B2h = B0h + 2, MOV DL, imm8'dir: 41h = 'A'. CDh, INT komutudur ve kesme numarasını ardından gelen bayttan alır. B8h, MOV AX, imm16'dır ve iki bayt alır: 00 4C → 4C00h. Son CD 21, AH = 4Ch ile DOS'un program bitirme hizmetini çağırır. Çözüm, programcının yazdığı kodun ta kendisidir.

Şimdi IP'nin bir bayt kaydığını düşünelim; örneğin hatalı bir atlama 0001h'ye gitsin. İşlemci 02h'yi işlem kodu sayar. Şekil 11.4 iki çözümü üst üste gösterir ve Tablo 11.14 ikinci çözümün bayt bayt açıklamasını verir.

konum 0'dankonum 1'den0000B40001020002B20003410004CD0005210006B800070000084C0009CD000A21MOV AH, 02hMOV DL, 41hINT 21hMOV AX, 4C00hINT 21hADD DH, [BP+SI+CD41h]AND [BX+SI+4C00h], DIINT 21h
Şekil 11.4: Aynı on bir baytın bağıl konum 0'dan (üstte) ve bağıl konum 1'den (altta) çözülmesi. Konum 1'den çözümde 'A' ve INT'in işlem kodu bir yer değiştirmenin içinde kaybolur; son CD 21'de iki çözüm yeniden aynı sınıra düşer.
Tablo 11.14: Bağıl konum 1'den başlayan çözüm.
KonumBaytlarÇözümKomut
0001h02 B2 41 CD02h = ADD r8, r/m8 (D = 1, W = 0); B2h = 10 110 010 → MOD = 10 (d16), REG = DH, R/M = [BP+SI]; d16 = CD41hADD DH, [BP+SI+CD41h]
0005h21 B8 00 4C21h = AND r/m16, r16 (D = 0, W = 1); B8h = 10 111 000 → MOD = 10, REG = DI, R/M = [BX+SI]; d16 = 4C00hAND [BX+SI+4C00h], DI
0009hCD 21INT 21hINT 21h

Konum 1'den çözüm üç komut verir. İlki B2h'yi ModR/M sayar ve MOD = 10 yüzünden iki bayt yer değiştirme okur. Böylece 'A' karakteri (41h) ile INT'in işlem kodu (CDh), CD41h diye bir sayının içinde kaybolur. Bu komut SS:(BP + SI + CD41h) adresindeki baytı DH'ye ekler; yalnız okur. İkinci komut daha tehlikelidir: 21h, D = 0 olan AND'dir ve sonucu bellekteki r/m işlenene yazar. DS:(BX + SI + 4C00h) adresindeki sözcük, DI ile VE işlemine girip bozulur. Son CD 21'de iki çözüm yeniden aynı sınıra düşer; çözme kendiliğinden yeniden eşlenmiştir. Ama bu kez AH = 02h hiç yüklenmemiştir. INT 21h, AH'de o an ne varsa o hizmetle çağrılır ve sonucu kestirilemez.

Örnek iki şey öğretir. Birincisi, baytların anlamını baytlar değil, IP'nin başladığı yer belirler. Aynı on bir bayt bir başlangıçta zararsız bir “A yaz ve çık” programıdır, bir bayt ötede belleği bozan bir komut dizisidir. İkincisi, x86 kodunda çözme genellikle birkaç komut sonra kendiliğinden doğru sınıra döner. Bu, yanlış çözümün fark edilmesini zorlaştırır: birkaç anlamsız komuttan sonra her şey yeniden düzgün görünür.

Örnek 11.4

8A 1E 34 12 baytlarını konum 0'dan çözelim. 8Ah = 100010 1 0 (D = 1, W = 0), 1Eh = 00 011 110 (MOD = 00, REG = BL, R/M = 110 → doğrudan adres) olur. Komut MOV BL, [1234h]'tür. Konum 1'den başlanırsa 1Eh tek baytlık PUSH DS'dir (06h + 8 × 3 düzenindeki kesim PUSH'u), ardından 34 12 iki baytlık XOR AL, 12h olur. Dört bayt bu kez iki ayrı komut verir ve yığına beklenmedik bir sözcük itilir.

11.4.4Kod ve veri: işlemcinin bakışı

8086'da kodla veriyi ayıran bir donanım yoktur. Kod kesimi ile veri kesimi, kesim yazmaçlarındaki birer sayıdan ibarettir ve .COM programlarında dördü de aynı kesimi gösterir. Program, verilerini kodun hemen arkasına koyar. Bir JMP yanlışlıkla veri alanına atlarsa ya da yığındaki dönüş adresi bozulup RET oraya dönerse, işlemci veri baytlarını komut diye yürütmeye başlar. sayi db 3Ch gibi bir değişken, işlem kodu olarak okunursa 3Ch = CMP AL, imm8 olur ve ardından gelen baytı anlık değer diye yutar.

Çözücü programlar da aynı sorunla karşılaşır. Bir çözücü, bellekteki baytların hangisinin kod, hangisinin veri olduğunu ancak programın akışını izleyerek tahmin edebilir. Listing dosyası bu sorunu yaşamaz, çünkü assembler her satırın ne olduğunu kaynak koddan bilir. Bu nedenle bir programın makine kodunu incelemenin en güvenilir yolu, assembler'ın ürettiği listing çıktısına bakmaktır. emu8086'da derleme sonrasında oluşan .lst dosyası, her satırın adresini ve baytlarını yan yana verir.

Sık yapılan hata

Programın sonundaki RET ya da INT 21h/4Ch'yi unutmak. Kod bittiğinde işlemci durmaz; IP ilerlemeye devam eder ve hemen arkadan gelen değişkenleri komut olarak yürütür. Program o zaman anlamsız davranır ya da kilitlenir. Değişkenleri koddan önce tanımlayıp üzerinden bir JMP ile atlamak ya da her çıkış yolunun açıkça programı bitirdiğinden emin olmak bu hatayı önler.

11.5Veri taşıma komutları

11.5.1Genel bakış

Veri taşıma komutları (data transfer instructions) bir değeri bir yerden ötekine kopyalar: yazmaçtan yazmaca, bellekten yazmaca, yığına ya da bir G/Ç portuna. Bu komutların ortak özelliği ALU'dan bir hesap sonucu geçirmemeleridir. Bu yüzden bayrakları değiştirmezler. İki istisna, bayrakları doğrudan yazmak için tasarlanmış SAHF ve POPF'tur. Tablo 11.15 bu sınıfın komutlarını özetler.

Tablo 11.15: Veri taşıma komutları.
KomutİşlemÖrnekKod
MOV h, kh ← k (kopyalama)MOV BX, [SI]8B 1C
PUSH k / POP hSP ← SP − 2, [SS:SP] ← k / h ← [SS:SP], SP ← SP + 2PUSH AX50
XCHG a, ba ↔ b (biri yazmaç olmalı)XCHG AL, [BX]86 07
IN / OUTAL/AX ↔ G/Ç portuIN AL, 60hE4 60
XLATAL ← [DS:BX + AL]tablo çevirisiD7
LEA r, mr ← m'nin adresi (EA)LEA SI, [BX+DI+4]8D 71 04
LDS / LES r, mr ← [m], DS/ES ← [m + 2] (uzak işaretçi)LES DI, uzakC4 3E …
LAHF / SAHFAH ← FLAGS'ın düşük baytı / tersi9F / 9E
PUSHF / POPFFLAGS'ı yığına at / yığından al9C / 9D

MOV'un kodlama biçimleri yukarıda ayrıntılı işlendi. PUSH ve POP'un yazmaçlı biçimleri tek bayttır (50h + r, 58h + r); kesim yazmaçlarınınki de tek bayttır (PUSH ES = 06h, PUSH CS = 0Eh, PUSH SS = 16h, PUSH DS = 1Eh). XCHG iki işlenenin yerini değiştirir ve ara yazmaca gerek bırakmaz; AX ile bir yazmacın takası 90h + r biçiminde tek bayttır. IN ve OUT, G/Ç adres alanına erişen tek komutlardır. Port numarası 0–255 ise komutun içinde sabit olarak verilir (E4h, E5h, E6h, E7h), daha büyük port numaraları DX ile verilir (ECh, EDh, EEh, EFh). Bu komutlar yürütülürken 8086'nın M/IO̅ ucu 0'a çekilir ve adres yolu bellek yerine bir G/Ç portunu seçer.

LAHF (load AH from flags) bayrak yazmacının düşük baytını AH'ye kopyalar. Bu baytta SF (bit 7), ZF (bit 6), AF (bit 4), PF (bit 2) ve CF (bit 0) bulunur. SAHF bunun tersini yapar ve AH'deki bu beş biti bayraklara yazar. İki komut, 8080 işlemcisinden aktarılan programların bayrak işlemlerini kolaylaştırmak için konmuştur. OF, DF, IF ve TF'ye ise yalnız PUSHF/POPF ile toplu olarak erişilir.

11.5.2XLAT: tablo çevirisi

Birçok program bir değeri tablodan başka bir değere çevirir: bir onaltılık basamağı ASCII karakterine, bir tuş kodunu bir harfe, bir sayıyı yedi bölümlü göstergenin bit desenine. Bu işin genel yolu, değeri bir indis yazmacına koyup tablodan okumaktır. 8086 bu sık işe tek baytlık özel bir komut ayırmıştır.

Tanım 11.3

XLAT (translate), AL'deki değeri bir çeviri tablosunda indis olarak kullanır: AL ← [DS:BX + AL]. BX tablonun başlangıç adresini tutar; AL işaretsiz bir sayı (0–255) olarak eklenir. Tablo en çok 256 bayt olabilir. Komut tek bayttır (D7h), bayrakları değiştirmez ve 11 saat turu sürer.

XLAT'ın etkin adres hesabı sıradan adresleme biçimlerinin hiçbirine uymaz: 8 bitlik AL, 16 bitlik BX'e eklenir. Bu yüzden XLAT'ın ModR/M baytı yoktur ve işlenenleri örtüktür. Bazı assembler'lar XLAT tablo ya da XLATB yazımını da kabul eder; işlenen yalnız belgeleme içindir ve kodu değiştirmez. Tek fark, işlenen başka bir kesimdeyse (XLAT ES:tablo) bir kesim önekinin eklenmesidir. BX'e tablonun adresini koymak programcının işidir.

11.5.3Kod izleme: XLAT ile baytı onaltılık yazdırma

Aşağıdaki program, bellekteki bir baytı iki onaltılık basamak olarak ekrana yazar. Bayt 3Ch'dir; ekranda “3C” görülmelidir. Bir bayt iki yarıma (dörtlüye) ayrılır; her yarım 0–15 arasında bir sayıdır ve XLAT ile '0'…'F' karakterlerinden birine çevrilir.

org 100h
mov bx, offset tablo
mov al, sayi       ; AL = 3Ch
mov cl, 4
shr al, cl         ; üst dörtlü
xlat               ; AL = [BX+AL]
mov dl, al
mov ah, 2
int 21h            ; DL'yi yaz
mov al, sayi
and al, 0Fh        ; alt dörtlü
xlat
mov dl, al
int 21h
ret
sayi  db 3Ch
tablo db '0123456789ABCDEF'

Program 0100h'den başlar. Komutların toplam uzunluğu 28 bayttır; bu yüzden sayi 011Ch'ye, tablo 011Dh'ye yerleşir. Tablo 11.16 her komuttan sonraki durumu verir.

Tablo 11.16: XLAT programının izlemesi. Her satır komut yürütüldükten sonraki durumdur; başlangıçta bütün yazmaçlar 0'dır.
AdresKodKomutALAHBXCLDLÇıktı
0100hBB 1D 01MOV BX, OFFSET tablo00h00h011Dh00h00h
0103hA0 1C 01MOV AL, sayi3Ch00h011Dh00h00h
0106hB1 04MOV CL, 43Ch00h011Dh04h00h
0108hD2 E8SHR AL, CL03h00h011Dh04h00h
010AhD7XLAT33h ('3')00h011Dh04h00h
010Bh8A D0MOV DL, AL33h00h011Dh04h33h
010DhB4 02MOV AH, 233h02h011Dh04h33h
010FhCD 21INT 21h33h02h011Dh04h33h3
0111hA0 1C 01MOV AL, sayi3Ch02h011Dh04h33h3
0114h24 0FAND AL, 0Fh0Ch02h011Dh04h33h3
0116hD7XLAT43h ('C')02h011Dh04h33h3
0117h8A D0MOV DL, AL43h02h011Dh04h43h3
0119hCD 21INT 21h43h02h011Dh04h43h3C
011BhC3RET43h02h011Dh04h43h3C

İlk satır BX'e tablonun bağıl konumunu yükler: 011Dh. MOV AL, sayi AL için kısa biçimi (A0h) kullanır ve doğrudan adres 011Ch'yi okur. 8086'da 1'den büyük kaydırma sayısı yalnız CL ile verilebildiğinden CL'ye 4 konur. SHR AL, CL, 0011 1100b'yi dört bit sağa kaydırır: 0000 0011b = 03h. Dışarı çıkan son bit 1 olduğundan CF = 1 olur; kaydırma sayısı 1'den büyük olduğu için OF tanımsızdır. Bu bayraklar programın akışını etkilemez.

Birinci XLAT, AL = 03h'yi indis olarak kullanır. Okunan adres DS:(011Dh + 03h) = DS:0120h'dir; orada tablonun dördüncü karakteri '3' (33h) durur. AL artık 33h'dir. DL'ye kopyalanır, AH'ye 02h konur ve INT 21h çağrılır. DOS'un 02h hizmeti DL'deki karakteri yazar ve AL'ye de aynı değeri koyar; ekranda “3” görülür.

İkinci yarıda bayt yeniden okunur ve AND AL, 0Fh üst dörtlüyü siler: 3Ch AND 0Fh = 0Ch. AND, CF ile OF'yi 0 yapar. İkinci XLAT DS:(011Dh + 0Ch) = DS:0129h'yi okur; orada 'C' (43h) vardır. AH hâlâ 02h olduğundan yeniden yüklenmez; DL = 43h ile INT 21h çağrılır ve ekranda “3C” görülür. RET, programı PSP'deki INT 20h üzerinden bitirir.

Bu programda XLAT'ın kazancı açıktır. Aynı çeviri MOV SI, AX benzeri hazırlık ve MOV AL, tablo[SI] gibi bir komutla da yapılabilirdi. Ama AL'yi 16 bitlik bir indis yazmacına genişletmek ek komut ister. XLAT bu işi tek bayt ve 11 turda yapar. Tablo yönteminin bir başka yararı, karşılaştırma ve dallanma gerektirmemesidir: “basamak 10'dan küçükse '0' ekle, değilse 'A' − 10 ekle” mantığı yerine tek bir tablo okuması yeter.

Örnek 11.5

DS = 1000h, BX = 0200h ve AL = 05h iken XLAT hangi fiziksel adresi okur? Etkin adres BX + AL = 0200h + 05h = 0205h'dir. Fiziksel adres 1000h × 10h + 0205h = 10000h + 0205h = 10205h'dir. O adresteki bayt AL'ye yüklenir; BX değişmez.

Sık yapılan hata

XLAT'tan önce AH'nin ya da AL'nin üst bitlerinin temizlenmesi gerektiğini sanmak ya da tam tersine, AL'deki fazla bitleri temizlemeyi unutmak. XLAT yalnız AL'yi kullanır; AH'ye bakmaz. Ama AL'nin bütün sekiz biti indise katılır. Yukarıdaki programda AND AL, 0Fh unutulsaydı indis 3Ch = 60 olur ve tablonun çok ötesindeki bir bayt okunurdu.

11.5.4LEA: adres mi, içerik mi?

Bir bellek işleneninin iki yönü vardır: adresi ve o adreste duran içerik. MOV içeriği okur. Bazen ise yalnız adres gerekir: bir dizginin başlangıcını SI'ya koymak, bir dizinin elemanının adresini bir prosedüre vermek. 8086 bunun için ayrı bir komut sunar.

Tanım 11.4

LEA (load effective address), bellek işleneninin etkin adresini hesaplar ve hedef yazmaca yazar: r16 ← EA. Belleğe erişmez ve bayrakları değiştirmez. Kodu 8Dh + ModR/M'dir; süresi 2 + EA saat turudur.

Adresi derleme anında belli olan bir değişken için LEA SI, metin ile MOV SI, OFFSET metin aynı sonucu verir. emu8086 doğrudan adresli LEA'yı bu yüzden MOV r16, imm16 ile aynı üç baytlık koda derler: LEA BX, veri = BB 14 01. Gerçek MASM aynı satırı 8D 1E ile başlayan dört baytlık bir kodla derler; aradaki bir bayt, arkadan gelen değişkenlerin adreslerini de bir kaydırır. LEA'nın asıl gücü, adresi çalışma anında yazmaçlardan hesaplanan işlenenlerde ortaya çıkar. LEA SI, [BX+DI+4] komutu, BX ve DI'nın o anki değerleriyle SI = BX + DI + 4 yapar (8D 71 04). OFFSET işleci bunu yapamaz, çünkü değeri derleme anında hesaplanan bir sabittir.

LEA'nın belleğe erişmemesi ve bayrak değiştirmemesi onu bir toplama aracı da yapar. LEA AX, [BX+SI], AX = BX + SI işlemini tek komutta yapar; BX ve SI değişmez, bayraklar korunur. ADD ile aynı iş iki komut ister (MOV AX, BX / ADD AX, SI) ve bayrakları bozar. Bu özellik, bir CMP ile koşullu atlama arasında bir adresi artırmak gerektiğinde işe yarar.

mov bx, 0010h
mov di, 0004h
lea ax, [bx+di+2]  ; AX = 0016h (adres)
mov ax, [bx+di+2]  ; AX = [0016h] (içerik)

Bu iki satır aynı işleneni kullanır ve aynı etkin adresi (0010h + 0004h + 2 = 0016h) hesaplar. LEA bu sayıyı AX'e yazar ve durur. MOV ise BIU'ya DS:0016h'den bir sözcük okutur ve okunan değeri AX'e yazar. Makine kodları da yalnız işlem kodunda ayrılır: 8D 41 02 ve 8B 41 02.

11.5.5LDS ve LES: uzak işaretçi

Bağıl konum tek başına bir bellek yerini belirtmeye yetmez; kesim de gerekir. Hem kesimi hem bağıl konumu tutan dört baytlık değere uzak işaretçi (far pointer) denir. Uzak işaretçi bellekte belirli bir sırayla saklanır: önce bağıl konum (düşük sözcük), sonra kesim (yüksek sözcük). Her sözcük de kendi içinde little-endian'dır. B800:0000 adresini gösteren bir uzak işaretçi bellekte 00 00 00 B8 baytları olarak durur.

Tanım 11.5

LDS r16, m32 bellekteki uzak işaretçiyi yükler: r16 ← [m], DS ← [m + 2]. LES r16, m32 aynı işi ES ile yapar. İşlem kodları C5h (LDS) ve C4h (LES), ardından ModR/M gelir; ikinci işlenen bellek olmak zorundadır. Bayraklar değişmez; süre 16 + EA saat turudur.

LDS ve LES, iki sözcüğü tek komutta okur. Aynı işi MOV ile yapmak iki komut ister: biri bağıl konumu genel yazmaca, öteki kesimi kesim yazmacına yükler. Dizgi komutlarıyla birlikte kullanımları özellikle yaygındır: LDS SI kaynağın, LES DI hedefin tam adresini tek komutta hazırlar. Ekran belleği gibi programın kendi kesimi dışındaki bir alana yazmak için tipik kalıp LES DI, ekran ve ardından STOSB ya da STOSW'dir.

Örnek 11.6

Bellekteki uzak dd değişkeninin baytları 34 12 00 B8 olsun. LES DI, uzak sonrası ilk sözcük (34 12 → 1234h) DI'ya, ikinci sözcük (00 B8 → B800h) ES'ye yüklenir: DI = 1234h, ES = B800h. ES:DI artık fiziksel B8000h + 1234h = B9234h adresini gösterir. Baytlar 00 20 00 10 olsaydı LDS SI, … ile SI = 2000h, DS = 1000h olur ve DS:SI 12000h'yi gösterirdi.

11.5.6Kod izleme: LEA, MOV ve LES

Aşağıdaki program LEA ile MOV'un farkını ve LES ile uzak işaretçinin yüklenmesini bir arada gösterir. Program sonunda ekranın sol üst köşesine 'A' yazar.

org 100h
lea bx, veri        ; BX ← adres
mov di, 2
lea ax, [bx+di+2]   ; AX ← EA
mov ax, [bx+di+2]   ; AX ← [EA]
les di, ekran       ; uzak işaretçi
mov al, 'A'
stosb               ; ES:[DI] ← AL
ret
veri  dw 1111h, 2222h, 3333h
ekran dw 0000h, 0B800h

Komutların uzunlukları 3 + 3 + 3 + 3 + 4 + 2 + 1 + 1 = 20 bayttır. Program 0100h'den başladığından veri 0114h'ye, ekran 0114h + 6 = 011Ah'ye yerleşir. Tablo 11.17 izlemeyi, Şekil 11.5 ise LEA ile MOV'un aynı EA'dan farklı şeyler aldığını gösterir.

Tablo 11.17: LEA, MOV ve LES programının izlemesi. Başlangıçta CS = DS = ES = SS = 0700h.
AdresKodKomutAXBXDIESBellek
0100hBB 14 01LEA BX, veri0000h0114h0000h0700h
0103hBF 02 00MOV DI, 20000h0114h0002h0700h
0106h8D 41 02LEA AX, [BX+DI+2]0118h0114h0002h0700hokunmaz
0109h8B 41 02MOV AX, [BX+DI+2]3333h0114h0002h0700hDS:0118h okunur
010ChC4 3E 1A 01LES DI, ekran3333h0114h0000hB800hDS:011Ah–011Dh okunur
0110hB0 41MOV AL, 'A'3341h0114h0000hB800h
0112hAASTOSB3341h0114h0001hB800h[B8000h] ← 41h
0113hC3RET3341h0114h0001hB800h
BX0114hDI0002hAX0118hES0700hveri (DS:0114h)1111h0114h2222h0116h3333h0118hekran (uzak işaretçi)0000hbağıl konumB800hkesimEA = BX + DI + 2 = 0118h (adres)BX0114hDI0002hAX3333hES0700hveri (DS:0114h)1111h0114h2222h0116h3333h0118hekran (uzak işaretçi)0000hbağıl konumB800hkesim[EA] = 3333h (içerik)
BX0114hDI0000hAX3333hESB800hveri (DS:0114h)1111h0114h2222h0116h3333h0118hekran (uzak işaretçi)0000hbağıl konumB800hkesimES:DI → B8000h:00BX0114hDI0001hAX3341hESB800hveri (DS:0114h)1111h0114h2222h0116h3333h0118hekran (uzak işaretçi)0000hbağıl konumB800hkesimES:DI → B8000h:'A'
Şekil 11.5: LEA ve LES programının dört anı. Üst sol: LEA AX, [BX+DI+2] yalnız adresi (0118h) verir. Üst sağ: aynı EA ile MOV içeriği (3333h) okur. Alt sol: LES DI, ekran uzak işaretçiyi yükler (DI = 0000h, ES = B800h). Alt sağ: STOSB 'A'yı B8000h'ye yazar ve DI'yı 1 artırır.

Birinci komut, emu8086'da üç baytlık MOV BX, 0114h olarak derlenmiştir; BX = 0114h olur ve bellek okunmaz. MOV DI, 2'den sonra iki komut aynı işleneni kullanır. EA = BX + DI + 2 = 0114h + 0002h + 0002h = 0118h. LEA bu sayıyı AX'e yazar. MOV ise DS:0118h'deki sözcüğü okur: veri'nin üçüncü elemanı olan 3333h. Fark, makine kodunda yalnız işlem kodundadır (8Dh ve 8Bh); ModR/M (41h = 01 000 001: [BX+DI+d8], REG = AX) ve yer değiştirme (02h) aynıdır.

LES DI, ekran komutunun ModR/M baytı 3Eh = 00 111 110b'dir: REG = 111 (DI), MOD = 00 ve R/M = 110 (doğrudan adres 011Ah). İşlemci DS:011Ah'den dört bayt okur: 00 00 00 B8. İlk sözcük 0000h DI'ya, ikincisi B800h ES'ye gider. Bu noktada DI'nın önceki değeri (2) kaybolur. MOV AL, 'A' yalnız AL'yi değiştirir; AH'de önceki MOV'dan kalan 33h durur ve AX = 3341h olur. STOSB, AL'yi ES:DI adresine yazar. Fiziksel adres B800h × 10h + 0000h = B8000h'dir; bu, metin modunda ekranın sol üst köşesindeki karakter hücresidir. DF = 0 olduğundan DI bir artar.

Sık yapılan hata

LDS ile DS'yi değiştirdikten sonra programın kendi değişkenlerine erişmeye devam etmek. LDS SI, ptr sonrasında DS başka bir kesimi gösterir; MOV AL, sayac gibi bir komut artık yanlış kesimden okur. Çözüm, DS'yi LDS'den önce PUSH DS ile saklayıp iş bitince POP DS ile geri almak ya da hedef için ES:DI kullanan LES'i tercih etmektir.

11.6Aritmetik, mantık ve kaydırma komutları: özet

Aritmetik ve mantık komutları ayrıntılarıyla ayrı bir konudur; burada yalnız komut kümesi içindeki yerleri ve kodlamalarının ortak düzeni özetlenir. Tablo 11.18 grupları verir.

Tablo 11.18: Aritmetik, mantık ve kaydırma komutları.
GrupKomutlarAçıklama
ToplamaADD ADC INCADC eldeyi (CF) de ekler; INC CF'yi değiştirmez
ÇıkarmaSUB SBB DEC NEG CMPSBB ödüncü (CF) de çıkarır; NEG = 0 − h; CMP sonucu yazmaz
ÇarpmaMUL IMULişaretsiz / işaretli; örtük AL ya da AX
BölmeDIV IDIVişaretsiz / işaretli; bölüm ve kalan
BCD düzeltmeDAA DAS AAA AAS AAM AADikili sonucu onluğa düzeltir
İşaret genişletmeCBW CWDAL → AX, AX → DX:AX
MantıkAND OR XOR NOT TESTbit temizleme, kurma, tersleme; TEST sonucu yazmaz
Kaydırma / döndürmeSHL SAL SHR SAR ROL ROR RCL RCRkaydırma sayısı 1 ya da CL; çıkan bit CF'ye

İki işlenenli sekiz aritmetik-mantık komutu (ADD, OR, ADC, SBB, AND, SUB, XOR, CMP) aynı kodlama kalıbını paylaşır. İşlem kodunun bit 5–3'ü hangi işlemin yapılacağını seçer: ADD = 000, OR = 001, ADC = 010, SBB = 011, AND = 100, SUB = 101, XOR = 110, CMP = 111. Yazmaç–r/m biçimleri 00ooo0dw düzenindedir: ADD için 00–03h, SUB için 28–2Bh, CMP için 38–3Bh. Anlık değerli biçimler ise 80h, 81h ve 83h'tir ve aynı üç bitlik işlem numarası bu kez ModR/M'nin REG alanına yazılır. Böylece SUB BX, 5 = 83 EB 05 (11 101 011: REG = 101 = SUB) ve CMP BX, 5 = 83 FB 05 olur. Bu düzen bir kez görülünce, sekiz komutun yirmi dört biçimi tek bir tabloya iner.

İşlemci toplama ve çıkarmayı tek bir toplayıcı devresiyle yapar ve sayının işaretli mi işaretsiz mi olduğunu bilmez. Bu yüzden her iki yorum için bayrak üretir: CF işaretsiz taşmayı, OF işaretli taşmayı gösterir. Hangi bayrağın anlamlı olduğuna programcı karar verir. Bu komutların hangi bayrağı nasıl değiştirdiği notun sonundaki başvuru tablosunda toplanmıştır.

11.7Dizgi komutları

11.7.1Dizgi komutları neden var?

Bellekte ardışık duran baytlar ya da sözcükler üzerinde aynı işi tekrar tekrar yapmak programlarda çok sık görülür. Örnekler: bir metni bir yerden ötekine kopyalamak, iki parolayı karşılaştırmak, bir metinde bir karakter aramak, ekranı boşlukla doldurmak. Bu işleri MOV, CMP ve INC ile de yapmak mümkündür. Ama her eleman için birkaç komut gerekir: kaynaktan oku, hedefe yaz, iki adresi artır, sayacı azalt, döngünün başına dön. Üstelik MOV bellekten belleğe kopyalayamadığı için her eleman bir ara yazmaçtan geçer.

8086 bu tür işler için beş dizgi komutu (string instructions) sunar. Bu komutların işlenenleri örtüktür ve komutun içinde yazılmaz. Kaynak her zaman DS:SI, hedef her zaman ES:DI'dır. Komut, işini yaptıktan sonra SI'yı ve DI'yı kendiliğinden bir sonraki elemana ilerletir. Bu sayede her komut tek bayttır ve bir döngünün bütün adres işlerini kendi içinde halleder. Bir yineleme önekiyle birlikte kullanıldığında, tek bir iki baytlık komut binlerce baytlık bir bloğu işleyebilir.

Tanım 11.6

Dizgi komutu, DS:SI ile gösterilen kaynakta ve/veya ES:DI ile gösterilen hedefte bir bayt (B sürümü) ya da bir sözcük (W sürümü) işleyen komuttur. Komut işini bitirince kullandığı indis yazmaçlarını yön bayrağına göre 1 ya da 2 artırır veya azaltır. Kaynağın kesimi bir kesim önekiyle değiştirilebilir; hedef her zaman ES'tedir.

11.7.2Beş dizgi komutu

Tablo 11.19 beş dizgi komutunu, yaptıkları işi, güncelledikleri yazmaçları, bayrak etkilerini, işlem kodlarını ve tek başına yürütüldüklerindeki sürelerini verir.

Tablo 11.19: Dizgi komutları. Kod sütununda bayt / sözcük sürümü; süre tek yürütme için saat turudur.
KomutİşlemGüncellenenBayrakKodSüre
MOVSB / MOVSW[ES:DI] ← [DS:SI]SI, DI–A4 / A518
CMPSB / CMPSW[DS:SI] − [ES:DI]SI, DICMP gibiA6 / A722
SCASB / SCASWAL/AX − [ES:DI]DICMP gibiAE / AF15
LODSB / LODSWAL/AX ← [DS:SI]SI–AC / AD12
STOSB / STOSW[ES:DI] ← AL/AXDI–AA / AB11

Komutları iki gruba ayırmak kolaylık sağlar. MOVS, LODS ve STOS veri taşır ve bayraklara dokunmaz. CMPS ve SCAS ise karşılaştırır: sonucu bir yere yazmadan bir çıkarma yapar ve bayrakları CMP gibi kurar. Bütün işlem kodları 1010 xxxw düzenindedir; son bit W'dir ve bayt ile sözcük sürümünü ayırır. Kodların yalnız tek bayt olması, dizgi komutlarının neden bu kadar sık kullanıldığını da açıklar: önden getirme kuyruğu tek okumada birkaç dizgi komutunu birden alabilir.

Donanım açısından bakınca dizgi komutları BIU'yu yoğun kullanır. MOVSB bir bellek okuma ve bir bellek yazma yol döngüsü gerektirir; her döngü en az dört saat turu sürer. CMPSB iki okuma, SCASB ve LODSB birer okuma, STOSB bir yazma yapar. MOVSW çift adreslerde aynı iki döngüde iki bayt taşır; bu yüzden büyük blokları sözcük sürümüyle kopyalamak yaklaşık iki kat hızlıdır. Kaynak ya da hedef tek adresteyse her sözcük iki bayt döngüsüne bölünür ve bu kazanç azalır.

Sözdizimi konusunda bir not: MASM ve emu8086, işlenenli yazımı da kabul eder. MOVS hedef, kaynak yazıldığında assembler değişkenlerin türüne bakarak MOVSB ya da MOVSW üretir. Kaynak işlenene bir kesim öneki yazılmışsa (MOVS hedef, ES:kaynak) o önek koda eklenir. İşlenenler yalnız boyutu ve öneki belirler; adresleri SI ve DI'ya yüklemek yine programcının işidir.

11.7.3Yön bayrağı ve adım

Dizgi komutunun SI ve DI'yı artırması mı, azaltması mı gerektiğini yön bayrağı (direction flag, DF) belirler. DF, bayrak yazmacının bit 10'udur ve bir kontrol bayrağıdır: aritmetik komutlar onu değiştirmez. İki komutla kurulur:

  • CLD (clear direction flag, FCh): DF = 0. Dizgi komutları SI ve DI'yı artırır; işlem düşük adresten yükseğe ilerler.
  • STD (set direction flag, FDh): DF = 1. Dizgi komutları SI ve DI'yı azaltır; işlem yüksek adresten düşüğe ilerler.

Adım büyüklüğü komutun sürümüne bağlıdır: B sürümünde ±1, W sürümünde ±2. Güncelleme her zaman işlemden sonra yapılır. Bu yüzden bir dizgi komutu bittikten sonra SI ve DI, işlenmiş son elemanı değil, bir sonrakini gösterir. Bir arama komutu bir eşleşme bulduğunda eşleşmenin adresi DI − 1 (bayt, DF = 0) olarak bulunur.

Sık yapılan hata

DF'nin değerini varsaymak. DOS programı başlatırken DF'yi genellikle 0 bırakır, ama başka bir prosedür, bir kütüphane ya da bir kesme işleyicisi DF'yi 1 yapmış olabilir. Dizgi komutu kullanan her kod parçası, işe başlamadan önce yönü açıkça CLD ya da STD ile kurmalıdır. STD kullanan bir prosedür de dönmeden önce CLD ile DF'yi geri almalıdır.

Dizgi komutlarının kaynağı ve hedefi farklı kesimlerde olabilir. .COM programlarında CS, DS, ES ve SS aynı kesimi gösterir; DS:SI ve ES:DI aynı belleğe bakar. .EXE programlarında DOS, ES'yi programın veri kesimine değil PSP'ye ayarlar. Bu programlarda DS gibi ES de açıkça kurulmalıdır:

mov ax, @data
mov ds, ax
mov es, ax       ; dizgi hedefi için

11.7.4Kod izleme: MOVSB ile kopyalama

Aşağıdaki program dört baytlık 'ABCD' metnini, nokta ile dolu dört baytlık bir alana kopyalar. Kopyalama bir LOOP döngüsüyle, her turda tek bir MOVSB ile yapılır; böylece komutun her yürütmesi ayrı ayrı izlenebilir.

org 100h
cld
lea si, kaynak     ; DS:SI kaynak
lea di, hedef      ; ES:DI hedef
mov cx, 4
kopya:
movsb              ; [DI]←[SI], SI++, DI++
loop kopya         ; = rep movsb
ret
kaynak db 'ABCD'
hedef  db 4 dup('.')

Kodun uzunluğu 14 bayttır: CLD (1), iki LEA (3 + 3), MOV CX (3), MOVSB (1), LOOP (2), RET (1). kaynak bu yüzden 010Eh'de, hedef 0112h'de başlar. LOOP'un kodu E2 FD'dir: FDh = −3, sonraki komutun adresi 010Dh'den 3 geri, yani MOVSB'nin adresi 010Ah'dir.

Tablo 11.20: MOVSB döngüsünün izlemesi. Bayraklar hiçbir adımda değişmez.
AdımKomutSIDICXhedefAçıklama
1CLD0000h0000h0000h....DF = 0: ileri yön
2LEA SI, kaynak010Eh0000h0000h....BE 0E 01
3LEA DI, hedef010Eh0112h0000h....BF 12 01
4MOV CX, 4010Eh0112h0004h....sayaç
5MOVSB010Fh0113h0004hA...[0112h] ← [010Eh] = 41h
6LOOP kopya010Fh0113h0003hA...CX ≠ 0 → atla
7MOVSB0110h0114h0003hAB..[0113h] ← 42h
8LOOP kopya0110h0114h0002hAB..atla
9MOVSB0111h0115h0002hABC.[0114h] ← 43h
10LOOP kopya0111h0115h0001hABC.atla
11MOVSB0112h0116h0001hABCD[0115h] ← 44h
12LOOP kopya0112h0116h0000hABCDCX = 0 → düş
13RET0112h0116h0000hABCDprogram biter

Her MOVSB adımında iki şey olur. BIU önce DS:SI'daki baytı okur, sonra onu ES:DI'ya yazar; bayt hiçbir genel yazmaçtan geçmez. Ardından SI ve DI birer artar. Son MOVSB'den sonra SI = 0112h olur. Bu, kaynağın hemen arkasındaki bayttır ve tesadüfen hedef'in başlangıcıdır. DI = 0116h ise hedefin bir ötesini gösterir. Bayrak yazmacı baştan sona değişmez. LOOP da bayraklara dokunmaz; CX'i azaltıp sıfır değilse atlar.

Aynı iş MOV CX, 4 ve REP MOVSB ile tek komutta yapılır ve sonuç yazmaçları aynı olur: SI = 0112h, DI = 0116h, CX = 0. Fark süredir. LOOP'lu sürüm her bayt için 18 tur MOVSB ve atlayan LOOP için 17 tur harcar; son LOOP atlamadığı için 5 tur sürer. Dört bayt için 4 × 18 + 3 × 17 + 5 = 128 tur eder. REP MOVSB ise 9 + 17 × 4 = 77 tur sürer. Fark, blok büyüdükçe iki katına yaklaşır.

11.7.5Kod izleme: LODSB ve STOSB ile büyük harfe çevirme

LODS ile STOS birlikte kullanıldığında bir dizgi okunur, her eleman bir yazmaçta işlenir ve sonuç yazılır. Bu kalıp, bir diziyi yerinde dönüştürmenin tipik yoludur. Aşağıdaki program 'mikro' metnini büyük harfe çevirir. ASCII'de küçük harf ile büyük harf arasındaki fark yalnız bit 5'tir ('m' = 6Dh = 0110 1101b, 'M' = 4Dh = 0100 1101b). AND AL, 0DFh bu biti temizler.

org 100h
cld               ; DF = 0: ileri
lea si, metin
mov di, si        ; aynı yere yaz
mov cx, 5
don:
lodsb             ; AL = [SI], SI++
and al, 0DFh      ; büyük harf
stosb             ; [DI] = AL, DI++
loop don
ret
metin db 'mikro'

Kod 16 bayttır ve metin 0110h'ye yerleşir. MOV DI, SI (8B FE) hedefi kaynakla aynı yere koyar. Bir .COM programında ES = DS olduğu için DS:SI ve ES:DI aynı baytı gösterir. LODSB okuma adresini bir ilerletir, STOSB bir adım arkadan gelerek aynı baytın üzerine yazar. Tablo 11.21 ilk turu ayrıntılı, sonraki turları özet olarak verir.

Tablo 11.21: LODSB / STOSB döngüsünün izlemesi.
KomutALSIDICXmetin
CLD, LEA SI, metin00h0110h0000h0000hmikro
MOV DI, SI, MOV CX, 500h0110h0110h0005hmikro
LODSB6Dh ('m')0111h0110h0005hmikro
AND AL, 0DFh4Dh ('M')0111h0110h0005hmikro
STOSB4Dh0111h0111h0005hMikro
LOOP don4Dh0111h0111h0004hMikro
2. tur sonu49h ('I')0112h0112h0003hMIkro
3. tur sonu4Bh ('K')0113h0113h0002hMIKro
4. tur sonu52h ('R')0114h0114h0001hMIKRo
5. tur sonu4Fh ('O')0115h0115h0000hMIKRO

Bu programda bayrakları yalnız AND değiştirir: CF ve OF'yi 0, SF ile ZF'yi sonuca göre kurar, PF'yi sonucun eşliğine göre ayarlar. 4Dh = 0100 1101b'de dört tane 1 olduğundan ilk turda PF = 1, 49h = 0100 1001b'de üç tane 1 olduğundan ikinci turda PF = 0 olur. LODSB, STOSB ve LOOP bu bayrakları olduğu gibi bırakır. Program büyük harfle başlayan bir metinde de doğru çalışır: 'M' zaten bit 5'i 0 olan bir harftir ve AND onu değiştirmez. Ama harf olmayan karakterlerde dikkat gerekir. Örneğin bir rakam ('1' = 31h) bu işlemden 11h olarak çıkar; gerçek bir programda önce karakterin 'a'–'z' aralığında olup olmadığı sınanır.

Her turun süresi LODSB (12) + AND AL, imm8 (4) + STOSB (11) + LOOP (17) = 44 turdur. Son turda LOOP 5 tur sürdüğü için toplam 5 × 27 + 4 × 17 + 5 = 208 saat turu eder. Bu döngüye REP konamaz, çünkü REP yalnız tek bir dizgi komutunu yineler; arada bir AND işlemi gerektiği için döngü LOOP ile kurulur.

11.7.6LODSW ile sözcük dizisinin toplamı

W sürümlerinde adım 2'dir. Aşağıdaki program dört sözcükten oluşan bir diziyi LODSW ile okuyup BX'te toplar.

org 100h
cld
lea si, dizi
mov cx, 4
xor bx, bx        ; toplam = 0
top:
lodsw             ; AX = [SI], SI += 2
add bx, ax
loop top
ret
dizi dw 100h, 200h, 300h, 400h
Tablo 11.22: LODSW toplamının izlemesi (her turun ADD sonrası durumu). dizi 010Fh'de başlar.
TurLODSW okunan adresAXSIBXCX (LOOP sonrası)
1010Fh0100h0111h0100h0003h
20111h0200h0113h0300h0002h
30113h0300h0115h0600h0001h
40115h0400h0117h0A00h0000h

Kod 15 bayt tuttuğu için dizi tek adreste, 010Fh'de başlar. Her LODSW, SI'yı 2 artırır ve dizinin bir sonraki sözcüğüne geçer. Sonuç BX = 0A00h'dir (256 + 512 + 768 + 1024 = 2560 = 0A00h). Dizi tek adreste durduğu için her LODSW iki ayrı bayt yol döngüsü gerektirir. Dizinin önüne EVEN yönergesi konsaydı dizi 0110h'ye kayar ve her sözcük tek döngüde okunurdu.

11.7.7CMPS ve SCAS: karşılaştırma

CMPS ve SCAS, CMP gibi bir çıkarma yapar, sonucu atar ve altı durum bayrağını kurar. İşlenenlerin sırası, sonuçtaki CF ve SF'nin yorumu için önemlidir:

  • CMPS: [DS:SI] − [ES:DI]. Kaynak eksi hedeftir. 'R' (52h) ile 'S' (53h) karşılaştırıldığında 52h − 53h = FFh çıkar: CF = 1, SF = 1, ZF = 0. CF = 1, işaretsiz yorumla kaynağın hedeften küçük olduğunu gösterir.
  • SCAS: AL (ya da AX) − [ES:DI]. Aranan değer eksi bellektir. AL = 'S' ve bellekte 'R' varken 53h − 52h = 01h olur: CF = 0, ZF = 0.

Bu sıra, Intel sözdiziminde hedefin solda yazıldığı genel kurala aykırı görünebilir: CMPS'de sol taraf kaynaktır. CMPS'den sonra JA, JB gibi işaretsiz atlamalar “kaynak, hedeften büyük/küçük” diye okunur. SCAS yalnız DI'yı ilerletir, çünkü kaynak işleneni bir yazmaçtır. CMPS ise MOVS gibi iki bellek dizgisini aynı anda adresler ve SI ile DI'yı birlikte ilerletir.

11.8Yineleme önekleri

11.8.1REP, REPE ve REPNE

Dizgi komutunun asıl gücü, bir yineleme önekiyle (repeat prefix) birlikte ortaya çıkar. Önek, ardından gelen dizgi komutunu CX sayacı sıfırlanana kadar ya da bir koşul bozulana kadar yineler. Tablo 11.23 üç öneki ve sürdürme koşullarını verir.

Tablo 11.23: Yineleme önekleri.
ÖnekBaytSürdürme koşuluKullanım
REPF3CX ≠ 0MOVS, STOS (LODS ile anlamsız)
REPE / REPZF3CX ≠ 0 ve ZF = 1CMPS, SCAS: “eşitken sürdür”
REPNE / REPNZF2CX ≠ 0 ve ZF = 0CMPS, SCAS: “eşit değilken sürdür”

REP ile REPE aynı bayttır (F3h). İşlemci farkı önekten değil, arkasından gelen komuttan anlar: MOVS, LODS ve STOS bayrak üretmediği için ZF koşulu bunlarda sınanmaz; CMPS ve SCAS'ta sınanır. F2h'nin bit 0'ı 0, F3h'ninki 1'dir ve bu bit, sürdürme için ZF'nin hangi değeri alması gerektiğini söyler. Önekli bir dizgi komutunun yürütülmesi şu sırayı izler:

  1. CX = 0 ise dur. Komut hiç yürütülmeyebilir.
  2. Bekleyen bir kesme varsa onu kabul et (dönüş adresi önekin adresidir).
  3. Dizgi komutunu bir kez yürüt; SI ve DI'yı güncelle.
  4. CX ← CX − 1. Bu azaltma bayrakları değiştirmez.
  5. REPE ya da REPNE ise ve komut CMPS ya da SCAS ise ZF'yi sına; koşul sağlanmıyorsa dur. Sağlanıyorsa 1. adıma dön.

İlk adımın önemi büyüktür. CX = 0 ile başlayan bir REP MOVSB hiç bayt kopyalamaz. LOOP ise bunun tersine davranır: CX = 0 iken girilen bir LOOP döngüsü önce CX'i FFFFh'ye indirir ve 65 536 tur döner. Bu fark, REP'in uzunluğu hesaplanan bir blokta güvenle kullanılabilmesini sağlar: uzunluk 0 çıkarsa hiçbir şey olmaz.

Tanım 11.7

Yineleme öneki, ardından gelen dizgi komutunu CX kez ya da bir ZF koşulu bozulana kadar yineleten önektir. Her yinelemede CX bir azalır. Komut bittiğinde CX, yapılmamış yineleme sayısını; SI ve DI işlenmiş son elemanın bir sonrasını gösterir.

11.8.2Blok kopyalama ve ekranı doldurma

REP'in en yaygın iki kullanımı bir bellek bloğunu kopyalamak ve bir bloğu tek bir değerle doldurmaktır. Aşağıdaki ilk parça 100 baytı kopyalar, ikincisi ekranın tamamını boşlukla doldurur.

; 100 baytlık blok kopyalama
cld
lea si, kaynak
lea di, hedef
mov cx, 100
rep movsb

; ekranı boşlukla doldur
mov ax, 0B800h
mov es, ax
xor di, di
mov ax, 0720h   ; ' ' + öznitelik
mov cx, 2000    ; 80×25
rep stosw

İlk parçada REP MOVSB yüz kez yürür. Sonunda CX = 0'dır, SI ve DI başlangıç değerlerinden 100 (64h) fazladır ve kaynak bloğun bir kopyası hedeftedir. Bu parça bir programa yerleştirilip emu8086'da çalıştırıldığında, örneğin kaynak 010Dh'de ve hedef 0171h'de başlıyorsa, sonunda SI = 0171h ve DI = 01D5h görülür. 8086 kılavuzuna göre süre 9 + 17 × 100 = 1709 saat turudur. Aynı blok sözcük sürümüyle (MOV CX, 50 / REP MOVSW) kopyalansaydı süre 9 + 17 × 50 = 859 tura iner; MOVSW'nin turu MOVSB'ninkiyle aynıdır ama her turda iki bayt taşır.

İkinci parça, metin modundaki ekran belleğini doldurur. Renkli metin ekranı B800:0000'dan başlar. Her karakter hücresi iki bayttır: çift adreste karakterin ASCII kodu, tek adreste öznitelik (renk) baytı. AX = 0720h'nin düşük baytı AL = 20h (boşluk), yüksek baytı AH = 07h'dir (siyah zemin üzerine açık gri). STOSW, AX'i little-endian olarak yazdığı için 20h çift adrese, 07h tek adrese düşer; yani her STOSW bir hücreyi tam olarak siler. 80 × 25 = 2000 hücre için CX = 2000'dir. Komut bittiğinde DI = 2000 × 2 = 4000 = 0FA0h, CX = 0 olur ve ekranın son hücresi B8F9Eh–B8F9Fh'de 20h, 07h değerlerini taşır. Süre 9 + 10 × 2000 = 20 009 turdur; 5 MHz'de yaklaşık 4 ms.

11.8.3Kod izleme: REPNE SCASB ile karakter arama

REPNE SCASB, bir dizgide bir karakteri arar: “eşit değilken sürdür” koşulu, karakter bulunduğunda ya da CX bittiğinde durur. Aşağıdaki program 'merhaba' metninde 'h' harfini arar ve bulduğu konumu (0'dan sayarak) DI'ya koyar.

org 100h
cld
lea di, metin
mov cx, 7          ; uzunluk
mov al, 'h'        ; aranan
repne scasb        ; ara
jne yok
dec di             ; bulunan konum
sub di, offset metin
yok:
ret
metin db 'merhaba'

Kod 19 bayttır ve metin 0113h'de başlar. REPNE SCASB iki bayttır (F2 AE). Tablo 11.24 komutun dört yinelemesini ayrı ayrı gösterir. Her yinelemede AL ile ES:DI'daki bayt karşılaştırılır, DI bir artar ve CX bir azalır.

Tablo 11.24: REPNE SCASB programının izlemesi. REPNE SCASB'nin her yinelemesi ayrı satırda gösterilmiştir.
KomutKarşılaştırmaDICXZFCFAçıklama
CLD0000h0000h00ileri yön
LEA DI, metin0113h0000h00BF 13 01
MOV CX, 70113h0007h00en çok 7 karşılaştırma
MOV AL, 'h'0113h0007h00AL = 68h
REPNE SCASB (1)68h − 'm' (6Dh)0114h0006h01eşit değil → sürdür
REPNE SCASB (2)68h − 'e' (65h)0115h0005h00sürdür
REPNE SCASB (3)68h − 'r' (72h)0116h0004h01sürdür
REPNE SCASB (4)68h − 'h' (68h)0117h0003h10eşit → dur
JNE yok0117h0003h10ZF = 1 → atlama yok
DEC DI0116h0003h00eşleşmenin adresi
SUB DI, OFFSET metin0003h0003h00'h' 3. indiste

Dördüncü yinelemede 'h' bulunur: 68h − 68h = 0 ve ZF = 1 olur. REPNE'nin koşulu (ZF = 0) bozulduğu için komut durur. DI, eşleşmenin bir sonrasını (0117h) gösterir, çünkü güncelleme karşılaştırmadan sonra yapılmıştır. CX = 3, dizgide taranmadan kalan karakter sayısıdır. Komutun bitmesinin iki nedeni olabileceği için ardından bir sınama gerekir. ZF = 1 ise karakter bulunmuştur. ZF = 0 ise CX sıfırlanmış ve dizginin tamamı boşuna taranmıştır; o durumda JNE yok atlar. CX = 0 sınaması tek başına yetmez: aranan karakter tam son konumdaysa hem CX = 0 hem ZF = 1 olur.

DEC DI, DI'yı eşleşmenin kendisine geri götürür: 0116h. SUB DI, OFFSET metin başlangıç adresini çıkarır ve 0116h − 0113h = 3 bulunur: 'h', 0'dan sayınca 3. indistedir. DEC ve SUB bayrakları kendi sonuçlarına göre değiştirir; sonuç sıfır olmadığı için ZF = 0'a döner. Komutun süresi 9 + 15 × 4 = 69 saat turudur.

11.8.4REPE CMPSB ile iki dizgiyi karşılaştırma

REPE CMPSB, iki dizgiyi “eşit oldukları sürece” karşılaştırır ve ilk farkta durur. Aşağıdaki program 'ANKARA' ile 'ANTALY' dizgilerinin ilk altı karakterini karşılaştırır ve ilk farkın indisini SI'da bırakır.

org 100h
cld
lea si, ad1
lea di, ad2
mov cx, 6
repe cmpsb
je ayni            ; tamamı eşit
dec si
sub si, offset ad1 ; ilk farkın indisi
ayni:
ret
ad1 db 'ANKARA'
ad2 db 'ANTALY'

Kod 20 bayttır; ad1 0114h'de, ad2 011Ah'de başlar. İlk yineleme 'A' ile 'A'yı karşılaştırır: fark 0, ZF = 1, SI = 0115h, DI = 011Bh, CX = 5. İkinci yineleme 'N' ile 'N'yi karşılaştırır: ZF = 1, CX = 4. Üçüncü yinelemede 'K' (4Bh) − 'T' (54h) = F7h çıkar. ZF = 0 olur ve REPE durur. Bu anda SI = 0117h, DI = 011Dh, CX = 3'tür. Sonucun işaretsiz yorumu için CF'ye bakılır: CF = 1, yani 'K' < 'T'. Bu, sözlük sırasında ANKARA'nın ANTALY'dan önce geldiğini söyler. JE ayni atlamaz; DEC SI ve SUB SI, OFFSET ad1 ile 0116h − 0114h = 2 bulunur: ilk fark 2. indistedir.

Burada da komutun bitmesinin iki nedeni vardır. Dizgiler tamamen eşit olsaydı altı yinelemenin hepsi ZF = 1 ile biter ve CX = 0 olurdu; o zaman JE ayni atlardı. Bir parola ya da komut adı karşılaştırması bu kalıpla yapılır.

11.8.5Örtüşen bloklar ve yön seçimi

Kaynak ile hedef bellekte üst üste biniyorsa kopyalama yönü sonucu değiştirir. Bir metni iki bayt sağa kaydırmak istediğimizi düşünelim: metin db '12345', 2 dup('.'). Hedef, kaynağın iki bayt sağındadır ve iki blok üç baytta örtüşür.

std                  ; sondan başa
lea si, metin+4      ; kaynağın son baytı
lea di, metin+6      ; hedefin son baytı
mov cx, 5
rep movsb

STD ile kopyalama sondan başa ilerler: önce '5' 6. konuma, sonra '4' 5. konuma, '3' 4. konuma yazılır. Her kaynak bayt, üzerine bir şey yazılmadan önce okunur. Sonuç '1212345' olur: metin iki sağa kaymıştır ve ilk iki konumda eski karakterler kalmıştır. Aynı iş CLD ile, SI = metin ve DI = metin+2 verilerek yapılsaydı ilk adım '1'i 2. konuma, yani '3'ün üzerine yazardı. Üçüncü adım bu yeni '1'i okur ve ileri taşır. Sonuç '1212121' olur ve metin bozulur. Tablo 11.25 iki sonucu karşılaştırır.

Tablo 11.25: '12345..' metninin iki bayt sağa kopyalanması. Program 0100h'den başladığında metin 010Dh'dedir.
YönBaşlangıç SI / DIBitiş SI / DISonuç
STD (doğru)0111h / 0113h010Ch / 010Eh1212345
CLD (yanlış)010Dh / 010Fh0112h / 0114h1212121
Kural (örtüşen kopyalama)

Hedef kaynağın sağında (daha yüksek adreste) örtüşüyorsa kopyalama sondan başa yapılır: STD, SI ve DI blokların son elemanını gösterir. Hedef kaynağın solunda örtüşüyorsa baştan sona yapılır: CLD, SI ve DI blokların ilk elemanını gösterir. Bloklar örtüşmüyorsa iki yön de doğrudur.

11.8.6Süre, kesmeler ve donanım

REP'li bir dizgi komutu, aynı işi yapan bir döngüden belirgin biçimde hızlıdır. Tablo 11.26 Intel 8086 kılavuzundaki sürelere göre birkaç karşılaştırma verir. Kazancın kaynağı, döngü denetiminin (CX azaltma, sınama, atlama) mikrokod içinde yapılması ve önden getirme kuyruğunun her turda boşaltılmamasıdır. Bir LOOP ya da JMP atladığında kuyruk boşaltılır ve BIU yeni adresten baştan okumak zorunda kalır; REP'li bir komut ise IP'yi hiç değiştirmez.

Tablo 11.26: Dizgi işlerinin süreleri (saat turu). REP'li komut için süre 9 + n × tur başına süredir.
İşDöngü ileREP ile
4 bayt kopyalamaMOVSB + LOOP: 128REP MOVSB: 77
100 bayt kopyalamaMOVSB + LOOP: 3488REP MOVSB: 1709; REP MOVSW (50 sözcük): 859
2000 sözcük doldurma–REP STOSW: 20 009
7 baytta 4. konumda bulunan karakteri arama–REPNE SCASB: 69

Uzun bir REP komutu binlerce tur sürebilir. 8086, bu süre boyunca kesmeleri bekletmez. Her yinelemenin başında bekleyen bir kesme olup olmadığına bakar. Varsa o ana kadarki işi bırakır; SI, DI ve CX o anki değerlerini korur. Yığına itilen dönüş adresi, önekin adresidir. Kesme işleyicisi IRET ile döndüğünde işlemci önekli komutu yeniden başlatır ve iş kalan CX değeriyle sürer. Bu düzen kesme gecikmesini kısa tutar ama yukarıda anlatılan sınırlamayı da getirir: 8086 yalnız bir öneki hatırladığından REP ile kesim öneki birlikte kullanılmamalıdır.

Sık yapılan hata

REP'i LODS ile kullanmak ya da REPE'yi MOVS ile kullanmak. REP LODSB geçerli bir koddur ama her yinelemede AL'nin üzerine yazar; sonunda AL'de yalnız son bayt kalır. REPE MOVSB ise REP MOVSB ile aynıdır, çünkü MOVS ZF üretmez. Önek ile komutun eşleşmesi: MOVS ve STOS için REP, CMPS ve SCAS için REPE ya da REPNE.

11.9Kontrol aktarma: JMP'nin kodlaması

11.9.1JMP türleri

Koşulsuz atlama JMP, IP'ye (uzak atlamada CS'ye de) yeni bir değer yükler. 8086'da JMP'nin beş kodlama biçimi vardır. Biçimler hedefin nasıl verildiğine göre ayrılır: komutun içinde bir uzaklık olarak, komutun içinde mutlak bir adres olarak ya da bir yazmaçta veya bellekte. Tablo 11.27 beşini ve sürelerini verir.

Tablo 11.27: JMP'nin kodlama biçimleri. Süreler saat turudur.
TürKodErimÖrnekSüre
Kısa (short)EB d8−128 … +127 baytJMP SHORT dongu15
Yakın (near)E9 d16aynı kesim, ±32 KJMP basla15
Uzak (far)EA IP CSherhangi bir kesimJMP FAR PTR hedef15
Dolaylı yakınFF /4IP ← yazmaç ya da bellekJMP BX, JMP tablo[BX]11 / 18 + EA
Dolaylı uzakFF /5CS:IP ← bellek (4 bayt)JMP DWORD PTR [SI]24 + EA

Kısa ve yakın atlamalar göreli kodlanır: komut hedefin adresini değil, hedefin bir sonraki komuta olan uzaklığını taşır. İşlemci bu uzaklığı IP'ye ekler. IP, komut yürütülürken zaten bir sonraki komutu gösterdiği için hesap şudur: hedef = sonraki komutun adresi + yer değiştirme. Kısa biçimde uzaklık tek bayttır ve işaretle 16 bite genişletilir. Uzak atlama ise hedefin mutlak adresini taşır: önce IP (2 bayt), sonra CS (2 bayt), ikisi de little-endian. Uzak JMP bu yüzden beş bayttır. JMP FAR PTR uzak komutu, hedef 0700:0256h ise EA 56 02 00 07 olarak kodlanır.

Dolaylı biçimler FFh ailesindendir ve hangi işlemin yapılacağını ModR/M'nin REG alanı söyler: /4 dolaylı yakın JMP, /5 dolaylı uzak JMP. JMP BX = FF E3 (11 100 011: MOD = 11, REG = 100, R/M = BX). JMP DWORD PTR [SI] = FF 2C (00 101 100: REG = 101, R/M = [SI]). Bu iki satırda REG alanının bir yazmacı değil, işlemin türünü gösterdiği açıkça görülür.

11.9.2Göreli uzaklığın hesabı

Şekil 11.6 bir kısa atlamayı gösterir. 0120h'deki EB 10 iki bayttır; bir sonraki komut 0122h'dedir. Hedef 0122h + 10h = 0132h olur.

0120: EB 1001220132: hedef+10h (sonraki komuttan sayılır)
Şekil 11.6: Göreli atlama: hedef = sonraki komutun adresi + yer değiştirme. 0120h'deki EB 10 için 0122h + 10h = 0132h.

Ters yöndeki hesap, assembler'ın her atlama için yaptığı iştir: yer değiştirme = hedef − sonraki komutun adresi. Sonuç negatifse ikiye tümleyenle yazılır. Üç örnek:

  1. Kendine atlama. 010Ah'deki iki baytlık bir kısa JMP kendine atlıyorsa sonraki komut 010Ch'dir. 010Ah − 010Ch = −2 = FEh. Kod EB FE'dir; bir programı olduğu yerde durduran sonsuz döngüdür.
  2. Geri atlama. 0150h'deki kısa JMP 0130h'ye atlıyorsa sonraki komut 0152h'dir. 0130h − 0152h = −22h. −22h'nin ikiye tümleyeni 100h − 22h = DEh'dir. Kod EB DE.
  3. Yakın atlama. 0122h'deki bir JMP, 0251h'deki etikete gidiyorsa uzaklık kısa erime sığmaz. Yakın biçim üç bayttır ve sonraki komut 0125h'dedir. 0251h − 0125h = 012Ch. Kod E9 2C 01.

Göreli kodlamanın önemli bir sonucu vardır: kod bellekte başka bir adrese taşındığında kısa ve yakın atlamaların baytları değişmez, çünkü atlayan komut ile hedef birlikte kayar. Bu, programın yükleneceği adresi bilmeyen DOS gibi bir sistem için kolaylıktır. Uzak atlama ise mutlak CS değeri taşıdığından, .EXE programlarında DOS'un yükleme sırasında düzeltmesi gerekir. 8086'da koşullu atlamaların (Jcc) yalnız kısa biçimi vardır. Hedef −128…+127 eriminin dışındaysa assembler koşulu tersine çevirir ve arkasına yakın bir JMP koyar.

Her atlama, süresinin yanında bir bedel daha öder. Atlama gerçekleştiğinde BIU'nun önden getirme kuyruğundaki baytlar artık geçersizdir, çünkü sıradaki komut başka bir yerdedir. Kuyruk boşaltılır ve BIU yeni CS:IP'den okumaya başlar. JMP'nin 15 turluk süresinin büyük kısmı bu yeniden doldurmadan gelir. Koşullu atlamaların atlamadığında yalnız 4 tur sürmesinin nedeni de kuyruğun korunmasıdır.

11.9.3Dolaylı atlama ve atlama tablosu

Dolaylı yakın JMP, hedefi çalışma anında seçmeyi sağlar. Bir menü ya da bir durum makinesi, olası hedeflerin adreslerini bir sözcük dizisinde tutar ve bir indisle bunlardan birine atlar. Bu yapıya atlama tablosu denir; yüksek düzeyli dillerdeki switch komutunun makine düzeyindeki karşılığıdır.

mov bx, 2           ; seçim (0, 1, 2)
shl bx, 1           ; sözcük indisi: × 2
jmp tablo[bx]       ; IP ← tablo[BX]
tablo dw s0, s1, s2
s0: mov dx, 0
    ret
s1: mov dx, 1
    ret
s2: mov dx, 2
    ret

Tablodaki her eleman iki bayttır; bu yüzden seçim numarası SHL ile ikiyle çarpılır. Program 0100h'den başladığında JMP tablo[BX] FF A7 09 01 olarak kodlanır: ModR/M A7h = 10 100 111b (MOD = 10, REG = 100 = /4, R/M = [BX+d16]), ardından tablonun adresi 0109h. BX = 4 iken işlemci DS:010Dh'deki sözcüğü okur; orada s2'nin adresi 0117h vardır. IP ← 0117h olur ve program DX = 2 ile biter. Seçim numarasının tablo boyutunu aşmadığı önceden sınanmalıdır; aksi hâlde tablonun arkasındaki kod baytları adres sanılır ve işlemci rastgele bir yere atlar.

11.10İşlemci kontrolü komutları

11.10.1Bayrak komutları

İşlemci kontrolü komutları (processor control instructions) hesap yapmaz ve veri taşımaz; işlemcinin durumunu değiştirir. Bunların bir kısmı tek bir bayrağı kurar ya da temizler. Hepsi tek bayttır ve 2 saat turu sürer. Tablo 11.28 komutların tamamını kodları ve süreleriyle verir.

Tablo 11.28: İşlemci kontrolü komutları.
KomutKodEtkiSüre
CLC / STCF8 / F9CF ← 0 / CF ← 12
CMCF5CF ← ¬CF2
CLD / STDFC / FDDF ← 0 / 1 (dizgi yönü)2
CLI / STIFA / FBIF ← 0 / 1 (INTR kesmeleri)2
HLTF4kesme ya da RESET gelene kadar dur2
WAIT9BTEST̅ ucu etkinleşene kadar bekle (8087 eşlemesi)3 + 5n
ESCD8–DFkomutu yardımcı işlemciye (8087) ilet2 / 8 + EA
LOCKF0önek: sonraki komut süresince LOCK̅ ucu etkin, yol kilitli2
NOP90hiçbir şey yapma3

Bayrak komutlarının her biri tek bir bayrağa dokunur; ötekiler değişmez. Böyle küçük komutların ayrı olarak bulunmasının nedeni, bu üç bayrağı (CF, DF, IF) programın kendisinin bilerek kurmasıdır. CF, çok sözcüklü toplamaya başlamadan önce temizlenebilir (CLC, ardından bir ADC zinciri). DF, dizgi komutlarından önce yönü seçer. IF ise kesmelerin kabul edilip edilmeyeceğini belirler. OF, SF, ZF, AF ve PF için ayrı kurma/temizleme komutu yoktur; bunlara SAHF ya da POPF ile yazılır.

CLC ve STC'nin yaygın bir kullanımı, bir prosedürden başarı ya da hata bilgisi döndürmektir. Prosedür hata bulursa STC, bulmazsa CLC ile döner. Çağıran taraf hemen ardından JC hata ya da JNC tamam ile karar verir. DOS'un INT 21h hizmetlerinin çoğu da hatayı bu yolla bildirir. Aşağıdaki prosedür AX'i BL'ye böler, ama BL = 0 ise bölme hatasına yol açmadan CF = 1 ile döner:

bol:
    cmp bl, 0
    jne tamam
    stc          ; hata: bölen sıfır
    ret
tamam:
    div bl       ; AL = bölüm, AH = kalan
    clc          ; başarı
    ret

AX = 7 ve BL = 0 ile çağrıldığında prosedür CF = 1 ile döner ve AX değişmez. AX = 7 ve BL = 3 ile çağrıldığında DIV, AL = 2 (bölüm) ve AH = 1 (kalan) üretir; AX = 0102h olur ve CF = 0 ile dönülür. DIV bütün bayrakları tanımsız bıraktığı için CLC burada gereklidir: o olmadan CF'nin değeri kestirilemez. RET bayrakları değiştirmediğinden CF çağırana olduğu gibi ulaşır.

11.10.2Kesme bayrağı: CLI ve STI

IF, işlemcinin INTR ucundan gelen maskelenebilir kesmeleri kabul edip etmeyeceğini belirler. CLI ile IF = 0 yapıldığında INTR yok sayılır; NMI ucundan gelen maskelenemez kesme ve INT n ile yazılımla çağrılan kesmeler ise yine çalışır. CLI ile STI arasındaki bölge, bir kesmenin yarıda bırakılmış bir veri yapısını görmemesi gereken kritik bölgedir. Tipik örnek yığının taşınmasıdır:

cli               ; kesmeler kapalı
mov ss, ax
mov sp, 0FFFEh
sti               ; kesmeler açık

SS değiştiği hâlde SP henüz eski değerindeyken bir kesme gelirse, işlemci FLAGS, CS ve IP'yi yanlış bir adrese iter ve belleği bozar. 8086, SS'ye yazan komuttan sonraki tek komut boyunca kesmeleri zaten bekletir. Ama bu koruma yalnız bir komutluktur; CLI/STI çifti niyeti açıkça gösterir ve arada daha çok komut olsa da güvenlidir. STI'nin de benzer bir özelliği vardır: kesmeler STI'dan sonraki komut bittikten sonra kabul edilir. Böylece bir kesme işleyicisinin sonundaki STI / IRET çiftinde, IRET yürütülmeden yeni bir kesme araya giremez.

11.10.3HLT, WAIT, ESC, LOCK ve NOP

Bu komutlar işlemcinin dış dünyayla ilişkisini yönetir ve doğrudan 8086'nın uçlarıyla bağlantılıdır.

  • HLT (halt) işlemciyi durma durumuna sokar. Komut yürütülmez, yol döngüsü başlatılmaz; işlemci bir sonraki komutu getirmeden bekler. Durumdan çıkmanın üç yolu vardır: INTR ucundan bir kesme (IF = 1 ise), NMI ucundan bir kesme ya da RESET. Kesme ile çıkıldığında yığına HLT'den sonraki komutun adresi itilir; işleyici döndüğünde program HLT'nin arkasından sürer. Bu yüzden HLT, “bir sonraki kesmeye kadar bekle” anlamında kullanılır. CLI'dan sonra bir HLT ise işlemciyi yalnız NMI ya da RESET ile uyandırılabilir hâle getirir.
  • WAIT, 8086'nın TEST̅ girişini yoklar. Uç 1 olduğu sürece işlemci beklemeye devam eder ve her beş saat turunda ucu yeniden örnekler. Bu uç genellikle 8087 kayan nokta yardımcı işlemcisinin BUSY çıkışına bağlanır. WAIT, 8087 bir hesabı bitirmeden 8086'nın onun sonucunu okumasını önler.
  • ESC (escape), 11011xxx (D8h–DFh) ile başlayan komutlardır. 8086 bu komutları kendisi yürütmez. Bellek işleneni varsa etkin adresi hesaplar ve o adresten bir okuma yapar. Yolu izleyen 8087 hem komutu hem adresi yakalar ve hesabı kendisi yapar. İşlem kodundaki üç bit ile ModR/M'nin REG alanı birleşerek 8087'nin altı bitlik komut kodunu verir.
  • LOCK bir önektir. Arkasından gelen komut yürütüldüğü sürece 8086'nın LOCK̅ çıkışı (en büyük modda) 0 tutulur. Yol hakem devresi bu süre boyunca başka bir işlemcinin ya da DMA denetleyicisinin yolu almasına izin vermez. Böylece okuma ve yazmadan oluşan bir komut, araya başka bir yol ustası girmeden tamamlanır.
  • NOP hiçbir şey yapmaz ve 3 tur sürer. Kodu 90h'dir, yani aslında XCHG AX, AX'tir. Zamanlama döngülerinde kısa gecikme için, kod hizalamada boşluk doldurmak için ve hata ayıklamada bir komutu silmeden etkisiz kılmak için kullanılır.

LOCK'un en tanınan kullanımı, birden çok işlemcinin paylaştığı bir kaynağı korumak için bir semafor kurmaktır. Bellekteki kilit baytı 0 ise kaynak boştur. Bir işlemci kaynağı almak için AL = 1 ile LOCK XCHG AL, kilit yürütür. Takas tek komutta yapılır ve LOCK yüzünden araya başka bir işlemci giremez. AL'ye gelen eski değer 0 ise kaynak alınmıştır; 1 ise başkası almıştır ve döngü yeniden dener:

bekle:
    mov al, 1
    lock xchg al, kilit   ; F0 86 06 …
    test al, al
    jnz bekle               ; başkası almış
    ; kritik bölge
    mov kilit, 0            ; kaynağı bırak

Okuma ve yazma iki ayrı komutla yapılsaydı (MOV AL, kilit / MOV kilit, 1), iki işlemci aynı anda 0 okuyup ikisi birden kaynağı aldığını sanabilirdi. Tek işlemcili bir sistemde aynı korumayı CLI/STI sağlar; LOCK çok işlemcili sistemler içindir.

11.11Bayrak etkileri

11.11.1Hesaplama komutları

Bir programın doğru çalışması çoğu zaman hangi komutun hangi bayrağı değiştirdiğini bilmeye bağlıdır. Koşullu atlamalar en son bayrak değiştiren komutun sonucuna bakar; araya giren her komut bu sonucu bozabilir. Tablo 11.29 aritmetik, mantık ve kaydırma komutlarının altı durum bayrağına etkisini verir.

Tablo 11.29: Hesaplama komutlarının bayrak etkileri. * sonuca göre değişir · 0 temizlenir · – değişmez · ? tanımsız · ¹ OF yalnız kaydırma sayısı 1 iken tanımlı. DF, IF ve TF bu komutlardan etkilenmez.
KomutOFSFZFAFPFCF
ADD ADC SUB SBB CMP NEG******
CMPS SCAS******
INC DEC*****–
MUL IMUL*????*
DIV IDIV??????
DAA DAS?*****
AAA AAS???*?*
AAM AAD?**?*?
AND OR XOR TEST0**?*0
SHL SAL SHR SAR*¹**?**
ROL ROR RCL RCR*¹––––*

Tablonun birkaç satırı ayrıca dikkat ister. INC ve DEC, CF'yi korur; bu özellik çok sözcüklü toplama döngülerinde bir sayacı ya da bir indisi artırırken ADC'nin eldesini bozmamak için tasarlanmıştır. MUL ve IMUL yalnız CF ile OF'yi anlamlı kurar: ikisi birlikte sonucun üst yarısının gerekli olup olmadığını söyler. DIV ve IDIV'den sonra hiçbir bayrağa güvenilmez. Mantık komutları CF ile OF'yi her zaman 0 yapar. TEST AX, AX bu yüzden sık kullanılan bir sınamadır: AX'i değiştirmez, ZF, SF ve PF'yi AX'e göre kurar ve CF'yi temizler. Döndürme komutları yalnız CF ve OF'yi değiştirir. Kaydırma ve döndürmede sayı 0 ise (CL = 0) hiçbir bayrak değişmez.

“Tanımsız” (?) bayrak, işlemcinin o bayrağa bir değer yazdığı ama bu değerin belgelenmediği anlamına gelir. emu8086'da ya da gerçek bir işlemcide bu bayrakta belirli bir değer görülür, ama başka bir işlemci modelinde farklı olabilir. Doğru yazılmış bir program tanımsız bir bayrağa bakarak karar vermez.

11.11.2Diğer komutlar

Tablo 11.30 geri kalan komutların bayraklara etkisini toplar. Bu tablonun ana mesajı, komutların büyük çoğunluğunun bayraklara hiç dokunmamasıdır.

Tablo 11.30: Diğer komutların bayrak etkileri.
KomutlarEtkilenen bayraklar
MOV XCHG LEA LDS LES XLAT PUSH POP IN OUT LAHF PUSHF CBW CWD NOThiçbiri
MOVS LODS STOS, REPhiçbiri
JMP Jcc LOOP LOOPE LOOPNE JCXZ CALL REThiçbiri (yalnız okunur)
SAHFSF ZF AF PF CF ← AH
POPF, IRETtümü (yığından)
INT n, INTOIF ← 0, TF ← 0 (eski FLAGS yığında)
CLC STC CMCCF
CLD STDDF
CLI STIIF

İki satır şaşırtıcı gelebilir. NOT bir mantık komutudur ama hiçbir bayrağı değiştirmez; bayrakları kurmak isteyen bir program onun yerine XOR AX, 0FFFFh kullanır. LOOP da CX'i azaltır ama DEC'in aksine bayraklara dokunmaz. LOOPE ve LOOPNE ZF'yi okur, yazmaz. INT n ise bayrakları değiştirmeden önce yığına iter; IRET onları geri yüklediği için kesme işleyicisinden dönünce programın bayrakları eski hâline gelir.

11.11.3CMP ile koşullu atlama arasına ne girebilir?

Bayrak tablolarının en sık kullanıldığı yer, bir karşılaştırma ile ona bağlı koşullu atlamanın arasıdır. Araya bayrak değiştiren bir komut girerse atlama karşılaştırmanın değil o komutun sonucuna bakar. Aşağıdaki parçada AL = 20 ve BX = FFFFh'dir:

cmp al, 10
add bx, 2       ; hata: bayrakları bozar
jb kucuk         ; AL < 10 ise
mov dx, 1
ret
kucuk:
mov dx, 2
ret

CMP, 20 − 10 hesabından CF = 0 üretir; AL 10'dan küçük değildir ve JB atlamamalıdır. Ama araya giren ADD BX, 2 FFFFh + 2 = 1 0001h hesaplar: sonuç 16 bite sığmaz ve CF = 1 olur. JB bu CF'ye bakar, atlar ve program yanlış dalda DX = 2 ile biter. ADD'nin yerine LEA BX, [BX+2] yazılırsa BX yine 0001h olur ama bayraklar değişmez; JB CMP'nin sonucunu görür, atlamaz ve DX = 1 doğru sonucu çıkar. Genel kural: CMP ile Jcc arasına yalnız MOV, LEA, XCHG, PUSH, POP gibi bayrak etkilemeyen komutlar girebilir. Bir sayacın artırılması gerekiyorsa ya CMP'den önceye alınır ya da LEA ile yapılır.

11.12Sık yapılan hatalar

Bu kesim, not boyunca karşılaşılan hataları bir arada listeler. Her birinin ardından neden ortaya çıktığı ve nasıl önleneceği verilmiştir.

  • D bitini “soldaki işlenen” sanmak. D = 1, REG alanındaki yazmacın hedef olduğunu söyler. İki yazmaçlı komutlarda iki kodlama da geçerlidir (8B C3 ve 89 D8); bellekli komutlarda D'yi belleğin hangi tarafta olduğu belirler.
  • W bitini atlayıp yanlış yazmacı okumak. REG = 100, W = 0 iken AH, W = 1 iken SP'dir. Çözmede önce W bakılır.
  • [BP]'yi MOD = 00 ile kodlamak. MOD = 00 ve R/M = 110 doğrudan adrestir. [BP], MOD = 01 ve d8 = 0 ile kodlanır ve üç bayttır.
  • Çözmeye yanlış adresten başlamak. Bir bayt kayması bütün sınırları değiştirir; çözme bir program başlangıcından ya da bilinen bir atlama hedefinden başlar.
  • Little-endian sırayı unutmak. Yer değiştirme, anlık değer ve uzak işaretçinin her sözcüğü düşük bayt önce yazılır. Uzak işaretçide önce bağıl konum, sonra kesim gelir.
  • LEA ile MOV'u karıştırmak. LEA AX, [BX+DI+2] adresi, MOV AX, [BX+DI+2] içeriği verir.
  • DF'yi varsaymak. Dizgi komutundan önce yön CLD ya da STD ile açıkça kurulur.
  • .EXE programında ES'yi kurmamak. Dizgi hedefi her zaman ES:DI'dır ve DOS, ES'yi veri kesimine ayarlamaz.
  • REPNE/REPE'den sonra yalnız CX'e bakmak. Komutun neden durduğunu ZF söyler; aranan son konumda bulunduğunda CX = 0 ve ZF = 1 birlikte olur.
  • Örtüşen bloklarda yanlış yön. Hedef kaynağın sağındaysa STD ile sondan başa kopyalanır.
  • CMP ile Jcc arasına bayrak bozan komut koymak. INC, ADD, SUB, AND gibi komutlar bayrakları yeniden kurar; araya yalnız bayrak etkilemeyen komutlar girer.
  • Tanımsız bayrağa güvenmek. MUL'den sonra ZF, DIV'den sonra herhangi bir bayrak, sayı 1'den büyük kaydırmadan sonra OF tanımsızdır.

11.13Alıştırmalar ve çözümleri

Bu kesimdeki dokuz alıştırma sunumun sonundaki alıştırmalardır. Her birinin çözümü, notta anlatılan yöntemle adım adım verilmiştir.

Alıştırma 1. MOV DX, [BP+DI+8] komutunu kodlayın (işlem kodu 100010, D = 1, W = 1).

Çözüm. Birinci bayt 100010 1 1 = 8Bh. Hedef DX, REG = 010. [BP+DI+d] biçiminin R/M kodu 011'dir. 8, tek bayta sığdığından MOD = 01. ModR/M = 01 010 011b = 53h. Yer değiştirme 08h. Kod 8B 53 08; BP içerdiği için varsayılan kesim SS'tir.

Alıştırma 2. DS = 1000h, BX = 0200h, AL = 05h iken XLAT hangi fiziksel adresteki baytı AL'ye yükler?

Çözüm. EA = BX + AL = 0200h + 05h = 0205h. Fiziksel adres 1000h × 10h + 0205h = 10205h.

Alıştırma 3. Bellekteki uzak dd değişkeninin baytları 34 12 00 B8. LES DI, uzak sonrası ES ve DI nedir?

Çözüm. Uzak işaretçide önce bağıl konum, sonra kesim saklanır. İlk sözcük 34 12 → 1234h, ikinci sözcük 00 B8 → B800h. DI = 1234h, ES = B800h.

Alıştırma 4. CX = 0 iken REP MOVSB kaç bayt kopyalar?

Çözüm. Hiç. REP her yinelemeden önce CX'i sınar; CX = 0 ise dizgi komutu bir kez bile yürütülmez. SI, DI ve bellek değişmez.

Alıştırma 5. CLD, CX = 5, SI = 0100h, DI = 0200h; REPE CMPSB 3. baytta fark buluyor. Sonra CX, SI, DI ve ZF nedir?

Çözüm. Üç karşılaştırma yapılır: ilk ikisi eşittir (ZF = 1), üçüncüsü farklıdır (ZF = 0) ve REPE durur. Her karşılaştırmada SI ve DI bir artar, CX bir azalır: CX = 2, SI = 0103h, DI = 0203h, ZF = 0. Farklı bayt SI − 1 = 0102h'dedir.

Alıştırma 6. 0150h adresindeki JMP SHORT komutu 0130h'ye atlıyor. Makine kodu nedir?

Çözüm. Komut iki bayttır; sonraki komut 0152h'dedir. Uzaklık 0130h − 0152h = −22h. Bir baytta −22h = 100h − 22h = DEh. Kod EB DE.

Alıştırma 7. Bir dizgideki boşlukları sayan kodu REPNE SCASB ile yazın.

Çözüm. REPNE SCASB bir boşluk bulduğunda durur; DI bulunan boşluğun arkasını, CX kalan uzunluğu gösterir. Sayaç artırılıp komut yeniden çalıştırılırsa arama kaldığı yerden sürer. Döngü, arama bulamadan bittiğinde (ZF = 0) ya da dizgi tükendiğinde (CX = 0) sona erer.

org 100h
cld
xor bx, bx         ; sayaç = 0
lea di, metin
mov cx, 19         ; uzunluk
mov al, ' '
ara:
repne scasb
jne bitti          ; boşluk kalmadı
inc bx             ; bir boşluk daha
jcxz bitti         ; dizgi bitti
jmp ara
bitti:
ret
metin db 'bu bir deneme metni'

emu8086'da çalıştırıldığında program BX = 3 ile biter: 'bu bir deneme metni' 19 karakterdir ve üç boşluk içerir. JCXZ sınaması, son karakterin boşluk olduğu durumu karşılar. O durumda REPNE SCASB boşluğu son yinelemede bulur; ZF = 1 ve CX = 0 olur. Sayaç artırılır ve JCXZ döngüden çıkar. Bu sınama olmasaydı CX = 0 ile yeniden giren REPNE hiç yürümez ve ZF = 1 olarak kalırdı; JNE atlamaz, sayaç durmadan artar ve döngü hiç bitmezdi. Metnin sonu 'met ' olarak değiştirildiğinde program dört boşluğu doğru sayar.

Alıştırma 8. 8A 1E 34 12 baytları hangi komuttur? Aynı baytlar bir bayt kaydırılarak (bağıl konum 1'den) çözülürse ne olur?

Çözüm. 8Ah = 100010 1 0: D = 1, W = 0. 1Eh = 00 011 110: MOD = 00, REG = 011 (BL), R/M = 110 → doğrudan adres. Ardından 34 12 → 1234h. Komut MOV BL, [1234h]. Konum 1'den çözüldüğünde 1Eh tek baytlık PUSH DS, ardından 34 12 iki baytlık XOR AL, 12h olur.

Alıştırma 9. MOV ES, DX ve MOV AL, [0020h] komutlarını kodlayın.

Çözüm. MOV ES, DX: 8Eh ailesi; ModR/M = 11 000 010b = C2h (REG = 0 + 00 = ES, R/M = DX). Kod 8E C2. MOV AL, [0020h]: AL ile doğrudan adres arasındaki kısa biçim A0h, ardından adres little-endian: A0 20 00.

11.14Özet

  • Komut kümesi: 8086'nın yaklaşık yüz komutu altı sınıfa ayrılır: veri taşıma, aritmetik, mantık-kaydırma, dizgi, kontrol aktarma, işlemci kontrolü. Anımsatıcı tek, işlem kodu çoktur.
  • Komut biçimi: önekler + işlem kodu (6 bit, D, W) + ModR/M (MOD, REG, R/M) + 0–2 bayt yer değiştirme + 0–2 bayt anlık değer. Öneksiz komut 1–6 bayttır; sayılar little-endian yazılır.
  • D ve W: W = 0 bayt, W = 1 sözcük; D = 1 REG hedef, D = 0 REG kaynak. İki yazmaçlı komutların iki geçerli kodu vardır: MOV AX, BX = 8B C3 ya da 89 D8; ADD AX, BX = 03 C3 ya da 01 D8.
  • ModR/M: MOD = 11 yazmaç, 00/01/10 bellek (0/1/2 bayt yer değiştirme). MOD = 00, R/M = 110 doğrudan adrestir; [BP] bu yüzden d8 = 0 ile kodlanır. BP içeren biçimlerin kesimi SS'tir. EA süresi 5–12 turdur.
  • Kısa biçimler: 40h+r, 50h+r, 90h+r, B0h+r, B8h+r, A0h–A3h, 04h/05h, 83h; aynı işin genel biçiminden bir-iki bayt kısadır. 80h/81h/83h ve FEh/FFh ailelerinde REG alanı işlemi seçer (/0 … /7).
  • Çözme: başlangıç noktasından ilk bayt, ModR/M, yer değiştirme ve anlık değer sırasıyla okunur. Yanlış adresten başlayan çözme aynı baytları başka komutlara çevirir; işlemci kodla veriyi ayırt etmez.
  • Veri taşıma: bayrakları değiştirmez (SAHF ve POPF hariç). XLAT: AL ← [DS:BX + AL]. LEA adresi, MOV içeriği verir; emu8086'da doğrudan adresli LEA üç baytlık MOV r16, imm16 olarak derlenir. LDS/LES uzak işaretçiyi (önce bağıl konum, sonra kesim) yükler.
  • Dizgi komutları: MOVS, CMPS, SCAS, LODS, STOS; kaynak DS:SI, hedef ES:DI. DF = 0 (CLD) artırır, DF = 1 (STD) azaltır; adım B'de 1, W'de 2. CMPS = [SI] − [DI], SCAS = AL/AX − [DI].
  • Yineleme önekleri: REP (F3h) CX ≠ 0 iken; REPE (F3h) ve REPNE (F2h) ZF koşuluyla. CX = 0 ise komut hiç yürümez. Bittikten sonra ZF komutun neden durduğunu söyler. Örtüşen bloklarda hedef sağdaysa STD kullanılır.
  • JMP: kısa (EB d8) ve yakın (E9 d16) göreli; hedef = sonraki komut + uzaklık. Uzak (EA IP CS) ve dolaylı (FF /4, FF /5) mutlak adres kullanır. Atlama önden getirme kuyruğunu boşaltır.
  • İşlemci kontrolü: CLC/STC/CMC, CLD/STD, CLI/STI tek bayrak; HLT, WAIT, ESC, LOCK işlemcinin uçlarıyla (INTR/NMI/RESET, TEST̅, 8087, LOCK̅) çalışır; NOP = XCHG AX, AX.
  • Bayrak etkileri: taşıma, dizgi taşıma, atlama ve NOT bayrak değiştirmez; INC/DEC CF'yi korur; mantık komutları CF = OF = 0 yapar; DIV sonrası bayraklar tanımsızdır. CMP ile Jcc arasına bayrak etkileyen komut girmemelidir.

11.15Kavram listesi

Terimlerin İngilizce karşılıkları ve öteki bölümlerdeki kullanımları terim sözlüğünde bulunur.

11.16Kendini Dene

  1. MOV [BX+SI+0Ah], DX komutunun makine kodu nedir?

    Cevap

    DX kaynaktır ve REG alanına girer: D = 0, W = 1 → 89h. REG = 010 (DX), R/M = 000 ([BX+SI+d]), 0Ah tek bayta sığdığından MOD = 01. ModR/M = 01 010 000b = 50h. Kod 89 50 0A.

  2. C6 46 FE 07 baytları hangi komuttur? Bellek işleneni hangi kesimdedir?

    Cevap

    C6h, anlık baytı r/m8'e taşıyan MOV'dur (REG alanı 000 olmalı). 46h = 01 000 110b: MOD = 01 (d8), R/M = 110 → [BP+d8]. d8 = FEh = −2, anlık değer 07h. Komut MOV BYTE PTR [BP−2], 7; BP içerdiği için kesim SS'tir. Bu, yığın çerçevesindeki bir yerel değişkene değer atamanın kalıbıdır.

  3. MOV AL, [BX] iki bayt iken MOV AL, [BP] neden üç bayttır?

    Cevap

    MOD = 00 ve R/M = 110 birleşimi doğrudan adrese ayrılmıştır. [BP] bu yüzden MOD = 01 ve d8 = 00 ile kodlanır: 8A 46 00. [BX] için böyle bir çakışma yoktur: 8A 07.

  4. 0200h'deki yakın JMP (E9) 0100h'ye atlıyor. Makine kodu nedir?

    Cevap

    Komut üç bayttır; sonraki komut 0203h'dedir. 0100h − 0203h = −103h; 16 bitte 10000h − 103h = FEFDh. Little-endian yazılır: E9 FD FE.

  5. kare db 0, 1, 4, 9, 16, 25, 36, 49, 64, 81 tanımlı. LEA BX, kare, MOV AL, 7, XLAT sonrası AL nedir?

    Cevap

    XLAT AL ← [DS:BX + 7] yapar; tablonun 7. elemanı (0'dan sayarak) 49'dur. AL = 49 = 31h. Tablo yöntemi, küçük sayıların karesini çarpma yapmadan, 11 turda verir.

  6. Bellekte ptr değişkeninin baytları 00 20 00 10'dur. LDS SI, ptr sonrası SI ve DS nedir? DS:SI hangi fiziksel adresi gösterir?

    Cevap

    İlk sözcük bağıl konum: SI = 2000h; ikinci sözcük kesim: DS = 1000h. Fiziksel adres 10000h + 2000h = 12000h. Bu andan sonra DS programın kendi verisini göstermez.

  7. CLD, SI = 0200h, DI = 0300h, CX = 3 iken REP MOVSW kaç bayt kopyalar? Sonunda SI, DI ve CX nedir?

    Cevap

    Üç sözcük, yani 6 bayt kopyalanır. Her yinelemede SI ve DI 2 artar: SI = 0206h, DI = 0306h, CX = 0.

  8. STD, DI = 0309h, CX = 10, AL = 'x'. Bellekte 0300h–0309h arasında '0123x5x789' var. REPNE SCASB sonrası DI, CX ve ZF nedir?

    Cevap

    Tarama sondan başa ilerler: 0309h '9', 0308h '8', 0307h '7', 0306h 'x'. Dördüncü karşılaştırmada eşleşme bulunur, ZF = 1 olur ve komut durur. DI her karşılaştırmadan sonra azaldığı için DI = 0305h, CX = 6, ZF = 1. Eşleşmenin adresi DI + 1 = 0306h'dir; STD ile tarama ilk 'x'i değil, en sondakini bulur.

  9. CMP AX, BX ile JE esit arasına şu komutlardan hangileri konabilir: INC CX, MOV CX, 0, XOR CX, CX, LEA CX, [BX+1], PUSH AX?

    Cevap

    MOV CX, 0, LEA CX, [BX+1] ve PUSH AX bayrakları değiştirmez ve konabilir. INC CX ZF, SF, OF, AF, PF'yi yeniden kurar; XOR CX, CX ZF = 1 yapar ve JE'yi her zaman atlatır. CX'i sıfırlamanın kısa yolu olan XOR'un bu yan etkisi burada hataya yol açar.

  10. REP STOSB ile MOV CX, 0 / dongu: STOSB / LOOP dongu arasında CX = 0 iken davranış farkı nedir?

    Cevap

    REP önce CX'i sınar; CX = 0 iken STOSB hiç yürümez ve bellek değişmez. LOOP'lu döngüde STOSB bir kez yürür, LOOP CX'i FFFFh'ye indirir ve döngü 65 536 kez döner; ES:DI'dan başlayan 64 KB'lık bir alan AL ile dolar.

11.17İleri okuma

  • Intel, The 8086 Family User's Manual (1979): komut kümesi bölümü her komutun bayrak etkisini ve saat turu süresini, komut kodlama bölümü işlem kodu tablolarını ve ModR/M düzenini birinci elden verir.
  • B. B. Brey, The Intel Microprocessors (8. baskı), veri taşıma ve dizgi komutları bölümleri: makine dili kodlaması, MOVS/STOS/LODS örnekleri ve sonraki x86 işlemcilere uzanan ayrıntılar.
  • K. R. Irvine, Assembly Language for x86 Processors (7. baskı): dizgi komutları ve dizi işleme bölümü; REPE CMPSB ile karşılaştırma ve tablo tabanlı arama için çok sayıda örnek.
  • emu8086 yardım belgeleri, “8086 instructions”: her komutun kısa tanımı, geçerli işlenen biçimleri ve bayrak tablosu; sunumdaki örneklerin denendiği ortamın kendi açıklamaları.

11.18Kaynaklar

  1. Intel, The 8086 Family User's Manual, 1979 — Bölüm 2: komut kümesi, Bölüm 4: komut kodlama.
  2. B. B. Brey, The Intel Microprocessors, 8. baskı, Pearson, 2009.
  3. K. R. Irvine, Assembly Language for x86 Processors, 7. baskı, Pearson, 2014.
  4. Ş. Ç. Öztürk, O. Ergin, Intel 8086 ile Mikroişlemci Programlamaya Giriş, 2009.
  5. emu8086 yardım belgeleri: “8086 instructions” ve “8086 assembler tutorial”.

İlgili sayfalar: Bölüm 11 sunumu · Çalışma soruları · Terim sözlüğü · © Sercan Külcü. Tüm hakları saklıdır.