x86 işlemci mimarisinde bayrak (flag) yapısı

13:34 ,


     Dünya genelinde hala en yaygın işlemci mimarisi olan x86 işlemci ailesinin bayrak (flag) yapısını öğrenmemiz assembly dilinde kod yazmak isteyenler için birazda zorunlu bir konu başlığıdır. En basitinden bir for döngüsünü yazabilmek için flag değerine başvurmamız gerekmektedir. Bayrak (flag) yazmaçları CPU'nun çalışmasını (real/protected mod gibi) belirlediği gibi çalışma sırasındaki durumlarını da öğrenmemizi sağlayan yazmaç değerleridir. İşlem sonucu 0 ise şunu yap şuraya dallan gibi işlemleri gerçekleştirebilmemiz için öğrenmemiz gerekmektedir. Şimdi sırası ile yazmaçları ve ne işe yaradıklarına kısaca bakalım;

Direction flag (DF)

Bir tane kontrol bayrağı (Control Flag) bulunmaktadır. Bu bayrak Direction flag (DF) bayrağıdır. String komutları denilen bir grup makine komutu bu bayrağa bakarak işlemin yönüne karar vermektedir (sağdan sola mı yoksa soldan sağa doğru mu). STD ( set direction flag ) ile set (yani 1) ve CLD (clear direction flag) ile reset (yani 0) işlemi yapılır. 

Durum bayrakları (Status Flag)

Aşağıda görüleceği üzere altı tane durum bayrağı (Status Flag) bulunmaktadır. Bunlar;

  • Carry flag (CF)
  • Parity flag (PF) 
  • Auxiliary carry flag (AF) 
  • Zero flag (ZF)
  • Sign flag (SF)
  • Trap flag (TF)
  • Interrupt flag (IF)
  • Overflow flag (OF)



Carry flag (CF)

İşaretsiz tamsayılar üzerinde işlemler yapılırken işlem sonucunda taşma ya da borç oluşursa bu bayrak set edilmektedir.

MOV         AH, 3F
MOV         AL, F4
ADD         AH, AL

Burada 3F ile F4 toplandığında sonuç 8 bite sığmamaktadır. Bu nedenle taşmadan dolayı carry flag set edilmiştir. İşaretsiz sayıların çıkartılması durumunda borç oluşursa da bu bayrak set edilmektedir. CF bayrağı işaretsiz düzeydeki çıkartma işleminde borç oluşuyorsa set edilir, borç oluşmuyorsa reset edilir.

MOV         AH, 3F
MOV         AL, F4
SUB         AH, AL

Yukarıda 8 bitlik çıkarma işlemi yapılmıştır. İşaretsiz düzeyde AH’taki değer AL’deki değerden (işaretsiz olarak) daha küçük olduğu için borç oluşur. Dolayısıyla işlem sonucunda CF bayrağı set edilecektir.

Parity flag (PF) 

Bir işlemin sonucundaki değerde 1 olan bitlerin sayısı çift ise PF bayrağı set (1) edilmektedir, Tek ise reset (0) edilmektedir. PF bayrağı “parity” denilen hata kontrol (error check) mekanizması için düşünülmüştür.

Auxiliary carry flag (AF) 

Bu bayrak 3’üncü bit'ten 4’üncü bit'e taşma oluşmuşsa set edilir, taşma yoksa reset edilir. Yani bu bayrak düşük anlamlı 4 bitteki taşmaya bakmaktadır.

Zero flag (ZF)

Son yapılan işlemin sonucu sıfır ise bu bayrak set edilir, sıfır değilse reset edilir. Örneğin:

MOV         EAX, 1
DEC         EAX

Buradaki DEC makine komutu EAX yazmacının içerisindeki değeri 1 eksiltir. EAX yazmacındaki değer 1 olduğuna göre DEC komutundan sonra işlem sonucu sıfır olduğu için ZF bayrağı set edilmektedir.

Sign flag (SF)

Bu bayrak işlem sonucunda elde edilen değerin en soldaki bitini (işaret bitini) tutar. Başka bir deyişle işlem sonucunda elde edilen değerin işaret bit'i (en soldaki biti) 0 ise bu bayrak reset edilir, 1 ise set edilir. Örneğin bu bayrak sayesinde biz son yapılan çıkarma veya işaretli toplama işlemden elde edilen değerin negatif olup olmadığını anlayabiliriz. Ayrıca CMP işleminden sonra kullanıldığında sayının büyük ya da küçük olup olmadığı kontrol edilebilir.

Trap flag (TF)

Trap flag debuggerlar için en önemli bayraktır. Set edilmişse (yani 1 değerinde ise) işlemci her bir makine komutundan sonra Single Step diye bilinen 1 numaralı kesmeyi çağırır. Set edildiğinde EI (Enable Interrupt); reset edildiğinde DI (Disable Interrupt) değerindedir. 

Interrupt flag (IF)

INTR girişini kontrol eder. Böylece IRQ kesmelerini kapatır ya da açar. 1 ise aktif 0 ise pasif durumdadır. Bu durumda iken gelen kesmelere cevap verilmez. Pasif durumdayken NMI (Non Maskable Interrupt) haricindeki bütün kesmeler iptal edilir. Reset etmek için CLI, set etmek için ise STI komutları kullanılır. Reset işleminden sonra DI (Disabled Interrupts), set edince EI (Enabled Interrupts) değerini alır. 

Overflow flag (OF)

Bu bayrak sıklıkla CF bayrağı ile karıştırılmaktadır. OF işaretli tamsayılarda bir taşma ya da borç oluştuğunda SET edilmektedir. Eğer bir işleme sokulan sayıların en soldaki bitleri (yani işaret biti) aynı ise fakat işlem sonucunda elde edilen değerin en soldaki biti bunlardan farklı ise OF set edilir aynıysa reset edilir. (İşleme sokulan sayıların işaret bitleri farklı ise OF bayrağının her durumda reset edildiğine dikkat ediniz.) Örneğin:

mov eax, 0xFFFFFFF0 ;       1111 1111  1111  1111 1111  1111 1111 0000
mov ebx, 0x000000FF ;       0000 0000 0000 0000 0000 0000 1111 1111
add  eax, ebx                 ; (1) 0000 0000 0000 0000 0000 0000 1110 1111

Yukarıdaki add işleminde OF bayrağı reset edilecektir. Çünkü sayıların işaret bitleri farklıdır. Ancak CF bayrağının set edileceğine dikkat ediniz. Örneğimizdeki 0xF0000000 sayısı işaretli olarak 10’luk sistemde -16’dır. 0x000000FF ise işaretli olarak 10’luk sistemde +255’tir. Toplama işleminin sonucunda 10’luk sistemde 239 sayısı elde edilecektir. Toplama işleminde işaretli düzeyde bir taşma olmadığına dikkat ediniz. 

Fakat örneğin:

mov eax, 0xFFFFFFF0 ;     1111 1111 1111 1111 1111 1111 1111 0000
mov ebx, 0x80000000 ;     1000 0000 0000 0000 0000 0000 0000 0000
add eax, ebx                 ;     (1) 0111 1111 1111 1111 1111 1111 1111 0000

Buradaki add komutunda sayıların işaret bitleri (yani en soldaki bitleri) 1’dir (yani aynıdır). Fakat işlem sonucunda elde edilen değerin işaret biti 0’dır. Bu durumda OF set edilecektir. (Ayrıca CF bayrağının da set edileceğine dikkat ediniz.) Pekiyi OF bayrağının programcı için anlamı nedir? OF bayrağı sayıların işaretli (signed) olduğu fikriyle işleme sokulması durumunda işlem sonucunda işaretli taşma ya da borç oluştuğunda set edilmektedir. OF bayrağı ile CF bayrağının birbirlerine benzediğine dikkat ediniz. CF bayrağı sayıların işaretsiz kabul edildiği durumda taşma ya da borç oluştuğunda set edilirken OF bayrağı sayıların işaretli kabul edildiği durumda taşma ya da borç olduğunda set edilmektedir.



Harvard & Von Neumann Mimarileri

Harvard mimarisi, veri ve komutların Merkezi İşlem Birimine ( MİB veya CPU ) giden kanallarının ayrılması ile oluşturulmuş bilgisayar tasarımıdır. Von Neumann mimarisi, veri ve komutları tek bir yığın (depolama) biriminde bulunduran bilgisayar tasarımıdır.



Von Neumann mimarisi

Verilerin ve program kodlarının aynı hafıza birimi üzerinde bulunduran tasarımdır. Von Neumann mimarisinin temel tasarımı aşağıdaki gibidir:


"von Neumann mimarisi" isim olarak John von Neumann'ın 1945 tarihli makalesine dayanır. Bellek ile Merkezi işlem biriminin (MİB) ayrılması von Neumann dar geçidi olarak bilinen soruna yol açmıştır. Bu sorun MİB ile bellek arası veri taşıma hızının, bellek miktarına göre çok düşük olmasından kaynaklanmaktadır. Bu nedenle, CPU zamanın büyük çoğunluğunu bellekten istenilen verinin gelmesini beklemekle geçirir. Son yıllarda CPU'ların hızları ile bellek erişim hızlarının arasındaki farkın açılması ile bu sorun daha da büyümüştür. Sorunu hafifletmek adına cache memory ve branch prediction geliştirilmiştir. von Neumann mimarisinin dar geçit sorunu dışında, en olumsuz yanı ise hatalı yazılımların (buffer overflow gibi) kendilerine, işletim sistemine ve hatta diğer yazılımlara zarar verebilme olasılığıdır.



Harvard mimarisi

Harvard mimarisi, ismini ilk kez bu mimariyi kullanan bilgisayar Harvard Mark I'den almıştır. Bu mimariyi kullanan makinalar, veriler ile komutlar arasında herhangi bir köprü bulundurmazlar. Veri adresi ile program (komut) adresinin adresleme boyutları farklıdır. Harvard mimarisinin temel tasarımı aşağıdaki gibidir:


Günümüz bilgisayarlarında tam anlamıyla kullanıldığı söylenilemez. Yine de Von Neumann mimarisi ve Harvard mimarisinden ortak özellikler günümüz teknolojisinde kullanılmaktadır.


Von Neumann vs Harvard


  • Von Neumann mimarisinde, işlemcinin doğası gereği ya komutlarla ya da verilerle uğraşmaktadır. Çünkü ikisi de aynı belleği paylaşmaktadır. İkisinin aynı anda olması durumu söz konusu değildir. 
  • Harvard mimarisini kullanan bir bilgisayarda ise komut ve veriler ayrı tutulduğu için, işlemci aynı esnada hem komutları değerlendirip hem verileri işleyebilir. Bir önbelleğe de gerek yoktur. Bu Harvard mimarisine bir avantaj sağlasa da; Von Neumann mimarisinde komutlar verilerle bir tutulduğundan, program kendi kendine değişim gösterebilir. 
  • Harvard mimarisinde komutlar ile veriler arasında bir kanal yoktur bu yüzden kodların içine veri gömülmüş programlar çalıştırılırken veya kendi kendine değişim gösterilecek programlar için Von Neumann mimarisi temel alınır.
  • Bellek adresleri açısından Harvard mimarisi iki ayrı adres kullandığından; boş komut adresi boş veri adresinden de farklı olacaktır. Von Neumann mimarisinde ise ikiside aynı adresi paylaşır.

Değiştirilmiş Harvard mimarisi

Değiştirilmiş Harvard mimarisi, Harvard mimarisindeki veri/komut bağlantısının eksikliğini gidermesi amacıyla yapılan tasarımsal düzenlemelere verilen isimdir. İşlemci halen veri ve komut erişimine de sahip olsa da aralarında bağlantılar mevcuttur. Bu düzenlemelerden en önemlisi aynı bellek adresi tarafından desteklenen iki ayrı önbellek kullanmasıdır. Biri komutları, biri verileri tutar. Önbellekte işlem yapılırken Harvard mimarisinin, asıl bellekte işlem yapılırken Von Neumann mimarisinin uygulandığını söyleyebiliriz. Günümüzde kullanılan yeni bilgisayar teknolojilerinde buna benzer mimariler sıkça kullanılmaktadır fakat isimlendirilmesinde ne Von Neumann ne de Harvard demek doğru değildir. Ayrıca komutları, okunabilir verilermiş gibi göstermek de değiştirilmiş Harvard mimarisine örnek gösterilebilir. 


Matematik İşlemci Nedir?


İlk üretilen mikroişlemciler (örneğin 8 bitlik işlemciler ve 16 bitlik işlemciler) yalnızca tamsayı işlemleri yapabiliyordu. Çünkü gerçek sayılarla işlemler için büyük mantık devreleri gerekiyordu. O zamanın teknolojileri buna fazlaca müsait değildi. O yıllarda matematiksel işlemler aslında arka planda tamsayı işlemleriyle, yani bir kod çalıştırılarak (başka bir deyişle fonksiyon çağrılarak) yapılıyordu. Örneğin 80’li yıllarında başında 8086 için zamanlarında aşağıdaki gibi bir kod yazmış olalım:

double a = 3.4, b = 67.8, c;
c = a + b;

İşte C derleyicileri bu tür işlemleri kendi kütüphane fonksiyonlarınu kullanarak adeta aşağıdaki gibi yapıyorlardı:

double a = 3.4, b = 67.8, c;
c = fadd(a, b);

Tabii gerçek sayı işlemlerinin tamsayı aritmetiğiyle emülasyon yoluyla yapılması yavaşlığa yol açıyordu. İşte Intel işlemleri hızlandırmak için 8087 isminde, 8086 işlemcisi ile bağlanarak koordineli çalışacak bir matematik işlemci (math coprocessor) de tasarladı. 8087 matematik işlemcisi gerçek sayı işlemlerini donanım yoluyla, yani elektrik devreleriyle yapıyordu. Böylece bu eski günlerde noktalı sayılarla bir işlem yaptığımızda eğer sistemimizde matematik işlemci yoksa bu işlemler emülasyon yoluyla, eğer sistemimizde matematik işlemci varsa matematik işlemcinin devreleriyle yapılmaktaydı. Intel 286’yı çıkartınca matematik işlemcisini de 80287 ismiyle güncelledi. Sonra 80386 çıktığında matematik işlemci 8037 oldu. İşte nihayet 80486 DX modeliyle birlikte artık Intel matematik işlemciyi de ana işlemciyla aynı entegre devre içerisine yerleştirmeye başladı. Bugünkü kullandığımız Intel işlemcilerinde yine matematik işlemci ayrı bir birim olarak vardır fakat bunlar aynı entegre devrenin içerisindedir. Intel’in matematik işlemci sistemi şöyle çalışmaktadır: Ana işlemci (yani tamsayı işlemcisi) komutu çektiğinde (fetch) onun başındaki byte’a bakar. O byte özel bir değerdeyse (bu tür byte’lara Intel terminolojisinde "prefix" denilmektedir). O komutun gerçek sayı işlemi yapan makine komutu olduğunu anlar. Komutu matematik işlemciye verir. Onu artık yardımcı işlemci çalıştırır. Intel bu konuda zaman içerisinde bazı optimizasyonlar yaptıysa da temel çalışma biçimi hala böyledir. Artık günümüzde tasarlanan yeni işlemciler kendi içlerinde gerçek sayı işlem birimini de içeriyorlar. Yani yeni tasarımlarda artık ayrı bir matematik işlemci diye kavram yoktur. Tasarımcı zaten işlemciyi gerçek sayı işlemlerini de yapacak biçimde tek parça olarak tasarlamaktadır. 

Notlar1: Bugün için hala küçük mikrodenetleyicilerin ve mikroişlemcilerin matematik işlemci modülleri yoktur. (Örneğin Microchip’in PIC16  mikrodenetleyicilerinin matematik işlemci birimleri yoktur. Bu mikrodenetleyicilerde noktalı sayılarla işlemler yine emülasyon yoluyla yapılmakatadır.)

Notlar2: Noktalı sayılarla işlemler özellikle IEEE 754 gibi kayan noktalı (floating point) formatlar zahmetlidir. Bu nedenle küçük mikrodenetleyicilerde programcılar sabit noktalı (fixed point) formatları tercih etmektedir. Çünkü sabit noktalı formatlarla işlemler tamsayılarla çok kolay emüle ederek halledebilmektedir.

Notlar3: Bugün modern kapasiteli mikroişlemcilerin hemen hepsi IEEE 754 kayan noktalı formatı kullanmaktadır. Kayan noktalı formatlar daha dinamik olduğu için daha verimlidir. Bu formatlarda sayı nokta yokmuş gibi ikilik sistemde saklanır. Sonra noktanın yeri sayı içerisinde bazı bitlerde tutulmaktadır.

Koşulsuz Jump Komutları

07:20

Jump komutları sembolik makine dillerinin mutlaka bilinmesi gereken komutlarındandır. Bazı işlemci ailelerinde bu komutlara branch (dallanma) komutları denilmektedir. Jump komutları olmadan yüksek seviyeli dillerdeki if, switch, while for gibi deyimler gerçekleştirilemez. Jump komutları koşulsuz (unconditional) ve koşullu (conditional) olmak üzere ikiye ayrılmaktadır. Koşulsuz jump komutları C’deki goto deyimi gibidir. Koşulsuz olarak EIP yazmacını belli bir değere çeker. Koşulsuz jump komutlarının doğrudan (direct) ve dolaylı (indirect) biçimleri de vardır. Koşulsuz jump komutları pek çok işlemcide olduğu gibi göreli uzaklık değerini operand olarak alır. Doğrudan koşulsuz jump komutlarının son byte’larından sonraki ilk byte göreli uzaklık için sıfır orijini belirtir. Negatif uzaklık "yukarıya", pozitif uzaklık "aşağıya" jump yapılacağı anlamına gelmektedir.

Koşulsuz doğrudan jump komutları operand olarak "göreli uzaklık" miktarını almaktadır. Göreli uzaklıkların sembolik makine dili programcısı tarafından hesaplanması çok zordur. Assembly derleyicileri etiket (label) yöntemi ile bu yükü bizim üzerimizden almaktadır. Sembolik makine dillerinde JMP komutlarının yanına bir etiket ismi verilmektedir. Sembolik makine dili derleyicileri de jmp komutunun sonundan o etiketin bulunduğu uzaklığı hesaplayarak makine komutu oluşturur.

Pekiyi jump komutları operand olarak neden mutlak adres yerine göreli uzunluk almaktadır? Çünkü bu sayede biz kodu bellekte başka yere yüklesek bile o jump komutları yine aynı yere atlamayı sağlayacaktır. Koşulsuz jump komutlarının 8 bit, 16 bit ve 32 bit göreli uzunluk alan biçimleri de vardır. Tabii programcı özel bir belirleme yapmakdıktan sonra derleyici zıplama miktarını hesaplayarak en uygun jump komutunu üretir. Intel sisteminde 8 bit göreli uzunluk alarak yapılan jump işlemlerine "short jump", 16 bit ve 32 bit göreli uzunluk alarak yapılan jump işlemlerine de "near jump" denilmektedir. Pek çok sembolik makine dili derleyicisinde short ya da near anahtar sözcüğü ile bunu isterse programcı belirleyebilmektedir. Örneğin:

. . .
jmp near NEXT ; ...
NEXT:
    call _ExitProcess@4
. . .

Eğer "short" ya da "near" anahtar sözcüklerinin hiçbiri kullanılmamışsa default durumda derleyici en uygun jump komutunu hesaplamaktadır. 32 bit sistemde 16 bit göreli uzunluk için komutta 0x66 ön eki gerekmektedir. Intel’deki koşulsuz jump komutlarının yazmaç ve bellek operandı alan biçimleri de vardır. Yazmaç operandı alan biçimi mutlak jump işlemi yapar. Yani yazmacın içerisindeki değer göreli uzunluk değil, bizzat jump edilecek yerin adrestir. Örneğin:
mov EAX, 0x123469
jmp EAX
Burada koşulsuz olarak 0x123469 adresine jump yapılmaktadır. Koşulsuz jump komutlarının bellek operandı alan biçimleri de vardır. Bu durumda önce o bellek bölgesindeki 32 bit değer çekilir. Oraya mutlak jump uygulanır. Örneğin:
jmp [EAX]
Burada EAX yazmacının içerisinde bulunan adresten 32 bit adres bilgisi çekilerek o adrese dallanma yapılmaktadır. Intel mimarisinde jmp komutlarının operandları yazmaç ya da bellek ise böyle jump’lara “indirect jump” denilmektedir. Örneğin:

[BITS 32]
SECTION .data
   ptrJmp dd EXIT
SECTION .text
global _start
extern _ExitProcess@4
_start:
jmp [ptrJmp]
EXIT:
xor eax, eax
push eax
   call _ExitProcess@4
Burada EXIT etiketinin adresi ptrJmp adresindeki bellek bölgesine yazılmıştır:

SECTION .data
jmpPoint dd EXIT
Sonra oraya aşağıdaki kod parçasında görüleceği üzere dolaylı jump işlemi indirect jump yapılmıştır:
jmp [jmpPoint]

 Bir sonraki yazımız dallanma konusunda daha önemli bir parçayı oluşturan koşullu dallanma konusunda olacaktır.

32 Bit Intel İşlemcilerde Koruma Mekanizması


Çok işlemli (multiprocessing) sistemlerde kullanılan modern ve güçlü mikroişlemcilerin çoğu bir koruma mekanizmasına (protection mechanisms) sahiptir. Intel 80286 ile birlikte segment tabanlı, 80386 ile birlikte de sayfa tabanlı koruma mekanizmasına sahip olmuştur. ARM işlemcilerinin pek çok modelinde, PowerPC, Itanium, SPARC gibi RISC tabanlı modern işlemcilerde de koruma mekanizması vardır.

Koruma mekanizmasının üç yönü vardır


  • Bellek Koruması (Memory Protection): Çok işlemli sistemlerde tüm prosesler aynı fiziksel bellek üzerinde çalışırlar. İşte böyle bir çalışma sırasında bir prosesin (yani çalışan programın) kendi alanı dışına çıkarak başka proseslerin kullandığı bellek bölgelerine erişememesi gerekir. Aksi takdirde bir proses başka bir prosesin bellek alanını bozabilir, değiştirebilir ya da oradaki verileri çalabilir. 



  • Komut Koruması (Instruction Protection): Her prosesin her makine komutunu kullanması sistem güvenliğini tehlikeye atabilmektedir. Çünkü bazı makine komutları eğer rastgele bir biçimde ya da özensiz olarak kullanılırsa sistemin çökmesine yol açabilir. Örneğin Intel işlemcilerindeki CLI (Clear Interrupt Flag) makine komutu işlemcinin kesme bayrağını reset'lemektedir. Bu durumda işlemci donanım kesmelerine yanıt vermez. Bu ise tüm sistemin hemen çökmesine yol açabilecek bir durum oluşturur. CLI komutunun dışında tehlikeli olabilecek başka makine komutları vardır. Bu komutların yetkisiz ve sıradan prosesler tarafından kullanılmaması gerekir. 


  • IO Koruması (IO Protection): Merkezi işlemci (CPU) pek çok yerel işlemciye bağlıdır ve onlara elektriksel olarak komutlar gönderebilmektedir. Yetkisiz ve sıradan proseslerin önemli IO portlarına komutlar göndermesi de sistemi çökertebilir. Bu nedenle sistem güvenliği açısından sıradan bir prosesin önemli olabilecek IO portlarına erişiminin engellenmesi gerekir.


Şüphesiz koruma mekanizması bazı proseslere uygulanıp bazılarına uygulanmayacak biçimde esnek olmalıdır. Örneğin işletim sisteminin kodları koruma mekanizmasının denetiminden muaf olmak zorundadır. Çünkü işletim sistemi bir kaynak yöneticisidir ve kaynakları yönetirken de her türlü işlemi yapabilecek durumda olmalıdır. Benzer biçimde aygıt sürücüleri ve çekirdek modülleri de yaptıkları işin gereği olarak koruma mekanizmasından muaf olmak durumundadır.

Intel işlemcileri koruma mekanizması için 4 dereceli bir yetkilendirme modeline sahiptir. Ancak 4 dereceli yetkilendirmenin pratikte pek kullanışlığı olduğu söylenemez.



Bu nedenle Intel işlemcilerini kullanan Windows - Linux gibi sistemler 4 yetki derecesi yerine yalnızca iki yetki derecesini kullanmaktadır. Benzer biçimde Intel dışındaki diğer işlemci aileleri de 2 dereceli bir yetki sistemine sahiptir. Bu yetki derecelerinin birine "çekirdek modu (kernel mode)" diğerine ise "kullanıcı modu (user mode)" denilmektedir. Pek çok ayrıntı söz konusu olsa da kabaca çekirdek modunda çalışan kodların hiçbir koruma engeline takılmadığını söyleyebiliriz. Ancak kullanıcı modunda çalışan kodlar için işlemciler katı bir koruma denetimi uygulamaktadır. İşletim sistemlerinin kodları, aygıt sürücüler, çekirdek modülleri "çekirdek modunda" çalışan kodlardır. Bunların dışındaki tüm programlar (örneğin Chrome, Word gibi programlar ya da bizim yazdığımız programlar) kullanıcı modunda çalışırlar.

Daha önceden de belirtildiği gibi Intel işlemcileri reset edildiğinde "gerçek mod (real mode)" denilen bir moddan çalışmaya başlar. Gerçek mod işlemcinin 1978 yılında tasarlanmış olan 8086 işlemcisi gibi çalıştığı moddur. (DOS işletim sisteminin ilk kez 8086 işlemcisi için yazılmıştır.) Gerçek modda koruma mekanizması kullanılamamaktadır. Koruma mekanizmasının kullanılabilmesi için işlemcinin korumalı moda (protected mode) geçirilmesi gerekir. Intel işlemcilerinin korumalı moda geçirilmesi CR0 isimli bir kontrol yazmacının en düşük anlamlı bitinin 1 yapılmasıyla sağlanır. Bu yazmaç tamamen koruma mekanizmasıyla ilgili işlemlere yönelik bitlere sahiptir. CR0 yazmacının bitleri şöyledir:



Intel’de CR0 ve diğer kontrol yazmaçları diğer yazmaçlar gibi aritmetiksel ve bitsel işlemlere sokulamazlar. Bu yazmaçlar ancak başka genel amaçlı yazmaçlar ile MOV işlemine sokulabilmektedir. O halde işlemciyi korumalı moda geçirme işlemini aşağıdaki gibi bir kodla yapabiliriz:

mov eax, cr0
or     eax, 1
mov cr0, eax

Intel işlemcileri korumalı moda geçirildiğinde çalışma biçimlerinde önemli farklılıklar oluşmaktadır. Bu nedenle bunları korumalı moda geçirmeden önce bizim bazı hazırlıkları yapmış olmamız gerekir. Ayrıca Intel işlemcilerinde koruma mekanizmasını yalnızca koruma amacıyla kullanılan bir mekanizma olarak düşünmek de doğru değildir. Tasarım gereği (geçmişe doğru uyumun korunması ile de ilgili olarak) bu işlemcilerin bazı özellikleri ancak koruma mekanizması aktive edildiğinde kullanılabilmektedir.

Fonksiyona stack yoluyla parametre aktarımı

05:07
Fonksiyonlara parametre aktarımı yapılırken yaygın olarak kullanılan yöntem yazmaçları kullanmak değil stack'i kullanmaktır. Yazmaç sayısı kısıtlı olması nedeniyle stack'i kullanmak biraz da zorunlu bir hal haline gelmektedir. Yazmaç kullanmak hızlı ama kısıtlı olmasından dolayı stack kaçınılmaz bir yöntemdir.


AddProc fonksiyonu işini yaparken parametrelerin stack'te olduğunu bildiği için o değerleri sırasıyla erişerek işlemini gerçekleştirmiş ve ret komutu ile geri dönülmüştür. Compile işlemi nasm ile link işlemi gcc ile gerçekleştirilmiş ve kod çalıştırıldığında sorunsuz bir şekilde çalıştığı görülecektir. Tabii ki bu şekilde kod yazma ne portable ne de başkası tarafından kullanılabilir bir kod örneğidir. ABI kavramıda zaten bu tür durumlar için ortak bir standart oluşturmak için belirlenmiştir. Stack kullanılırken stack'i iş bittikten sonra kimin düzenleyeceği ayrıca karar verilmesi gereken bir sorundur. Bu örneğimizde stack 'i çağıran taraf add esp,8 diyerek düzenlemiştir. Kendi alıştırmalarınızda stack veya bir önceki yazımızda anlattığımız gibi yazmaç yoluyla parametre aktarımı gerçekleştirebilirsiniz.

Fonksiyona yazmaç (register) yoluyla parametre aktarımı

05:07 ,
Eğer tüm işlemlerini assembly dili ile kendiniz yazacaksanız bu durumda genel amaçlı tüm register'ları fonksiyonlarınıza parametre aktarma için kullanabilirsiniz. Aşağıdaki örnekte eax, ebx, ecx register'ları bu amaçlarla kullanılmıştır.


AddProc fonksiyonu işini yaparken parametrelerin nerede olduğunu bildiği için o yazmaçlardan değerleri alarak işlemini gerçekleştirmiş ve ret komutu ile geri dönülmüştür. Compile işlemi nasm ile link işlemi gcc ile gerçekleştirilmiş ve kod çalıştırıldığında sorunsuz bir şekilde çalıştığı görülecektir. Tabii ki bu şekilde kod yazma ne portable ne de başkası tarafından kullanılabilir bir kod örneğidir. ABI kavramıda zaten bu tür durumlar için ortak bir standart oluşturmak için belirlenmiştir.